JavaScript學習筆記(面向對象編程一)

我們先回憶一下JS中的數據類型

String 字符串
Number 數值
Boolean 布爾值
Null 空值
Undefined 未定義類型
以上類型屬於基本數據類型,以後我們看到的如果不是以上的5種,全都是對象類型

JavaScript的所有數據都可以看成對象,那是不是我們已經在使用面向對象編程了呢?

當然不是。如果我們只使用Number、Array、string以及基本的{…}定義的對象,還無法發揮出面向對象編程的威力。

面向對象的兩個基本概念:

1.類:類是對象的類型模板,例如,定義Student類來表示學生,類本身就是一種類型,Student表示學生類型,但不表示任何具體的某個學生;
2.實例:實例是根據 類型創建 的對象,例如,根據Student類可以創建出xiaoming、xiaolan等多個實例,每個實例表示一個具體的學生,他們全都屬於Student類型。

對象的分類:

1.內建對象
由ES標準中定義的對象,在任何的ES的客觀中都可以使用
例如:Math String Number Boolean Function Object…
2.宿主對象
由JS的運行環境提供的對象,目前來講主要指由瀏覽器提供的對象
比如 BOM DOM
3.自定義對象
由開發人員自己創建的對象

但是在JavaScript中,不區分類和實例的概念,而是通過原型(prototype)來實現面向對象編程。
原型是指當我們想要創建xiaoming這個具體的學生時,我們並沒有一個Student類型可用。那怎麼辦?恰好有這麼一個現成的對象:(對象的基本操作)

//創建一個Object對象
var obj = new Object();
//放入屬性
obj.name = '孫悟空';
obj.gender = '男';
obj.age = 18;
console.log(obj);

//取出對象的屬性
console.log(obj.name+','+obj.gender+','+obj.age);
//修改對象的屬性,也就是覆蓋值 
obj.name = 'hk';
//刪除對象的屬性
delete obj.name;
console.log(obj.name);


var obj = new Object();
//放入屬性,使用的是變量的形式去操作屬性,這樣做會更加的靈活
obj['name'] = '孫悟空';
obj['gender'] = '男';
obj['age'] = 18;
console.log(obj);//{ name: '孫悟空', gender: '男', age: 18 }

var n = 'name';
var g = 'gender';
var a = 'age';

console.log('obj[n]:'+obj[n]);//孫悟空
console.log('obj[g]:'+obj[g]);//男
console.log('obj[a]:'+obj[a]);//18

var obj2 = new Object();
obj2.name = '小張三';

obj.test = obj2;//給obj裏面賦值爲另一個對象

console.log(obj.test);//{ name: '小張三' }
console.log(obj.test.name)//小張三

JS中的變量都是保存到棧內存中的
基本數據類型的值直接在棧內存中存儲
值與值之間是獨立存在,修改一個變量不會影響其他的變量

對象是保存在堆內存,這裏兩個對象指的是同一個堆內存空間所以obj1改變時,obj2也會改變!

var c = 10;
var d = 10;
//如果兩個變量的值相等,那麼這兩個變量也是相等的
console.log(c === d);

var obj3 = new Object();
var obj4 = new Object();

obj3.name = '沙和尚';
obj4.name = '沙和尚';
//兩個對象雖然值是一樣的,但還是兩個對象
//新建一個對象就相當於在堆內存中開闢一個內存塊,每創建一個對象都會新建
console.log(obj3 === obj4);

在這裏插入圖片描述
這兩個對象的內存地址不一樣!
使用對象字面量來定義對象:(邊定義邊賦值)

var Student = {
    name:'Robot',
    height:150,
    run: function () {  
        console.log(this.name+' is running....');
    }
};

var hk = {
    name:'gg'
};
//把hk的原型指向了對象Student
hk.__proto__ = Student;

console.log(hk.name);
console.log(hk.run());

hk有自己的name屬性,但並沒有定義run()方法。不過,由於hk是從Student繼承而來,只要Student有run()方法,hk也可以調用:
在這裏插入圖片描述
JavaScript的原型鏈和Java的Class區別就在,它沒有“Class”的概念,所有對象都是實例,所謂繼承關係不過是把一個對象的原型指向另一個對象而已。
如果你把hk的原型指向其他對象:

var Bird = {
    fly: function () {  
        console.log(this.name + ' is flying...');
    }
};

var hk = {
    name:'gg'
};
//把hk的原型指向了對象Bird
hk.__proto__ = Bird;
console.log(hk.name);//gg

console.log(hk.fly());//gg is flying...

創建 對象

JS對每個 創建的對象都會設置現代戰爭原型,指向 它的原型對象。
當我們用Obj.xxx訪問一個對象的屬性時,JS引擎先在當前 對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果沒有找到,就一起上溯到Object.prototype對象,最後,如果還沒有找到,就只能返回undefined。
例如,創建一個Array對象:

var arr = [1,2,3];

其原型鏈是:

arr --->Array.prototype --->Object.prototype  ------>null

Array.prototype定義了indexOf()、shift()等方法,因此你可以在所有的Array對象上直接調用這些方法。

function foo(
	return 0;
}

函數的原型鏈:

arr --->Function.prototype --->Object.prototype  ------>null

注意:
如果原型鏈很長,那麼訪問一個對象的屬性就會因爲花更多的時間查找而變得更慢,因此要注意不要把原型鏈搞得太長。

構造函數

除了直接用{…}創建一個對象外,JS還可以用一種構造函數的方法來創建對象,它的用法 是,先定義一個構造函數:

function Student(name) {  
    this.name = name;
    this.hello = function(){
        console.log("hello baby ");
    }
}
//在js中,用關鍵字new可以來調用函數 ,並返回一個對象
var hk = new Student('凱凱');
console.log(hk.name);//凱凱
console.log(hk.hello());//hello baby 

新創建的hk的原型鏈是:

hk --->hk.prototype --->Object.prototype --->null
function Student(name) {  
    this.name = name;
    this.hello = function(){
        console.log("hello baby ");
    }
}
//在js中,用關鍵字new可以來調用函數 ,並返回一個對象
var hk = new Student('凱凱');
var zs = new Student('zs');

console.log(hk.name);//凱凱
console.log(hk.hello());//hello baby 

console.log(zs.name);
console.log(zs.hello());

console.log(hk.constructor === Student.prototype.constructor);//true
console.log(Student.prototype.constructor === Student);//true


console.log(Object.getPrototypeOf(hk) === Student.prototype);//true

console.log(hk instanceof Student);//true

在這裏插入圖片描述紅色箭頭是原型鏈,Student.prototype指向的對象就是hk、zs的原型對象,這個原型對象自己還有個屬性constructor,指向Student函數本身。
另外,函數Student恰好有個屬性prototype指向hk、zs的原型對象,但是hk、zs這些對象沒有prototype這個屬性,不過可以用__proto__這個非標準用法 來看。
現在我們就認爲hk、zs這些對象“繼承”自Student。
不過還有一個小問題,注意觀察:

xiaoming.name; // '小明'
xiaohong.name; // '小紅'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

hk和zs各自的name不同,是爲了區分
但是它們的hello函數,代碼和名稱都是一樣的!
如果我們通過new Student()創建了很多對象,這些對象的hello函數實際上只需要共享同一個函數 ,這樣可以節省很多的內存。
要讓創建的對象共享一個hello函數,根據對象的屬性查找原則,我們只要把hello函數移動到hk、zs這些對象共同的原型上就可以了,也就是Student.prototype:
在這裏插入圖片描述
修改後的代碼:

function Student2(name) {  
    this.name = name;
}
//直接指定Student2.prototype
Student2.prototype.hello = function () {  
    console.log("hello Student2.prototype.hello ");
}
var ls = new Student2('ls');
var ww = new Student2('ww');

console.log(ls.name);//ls
console.log(ls.hello());//hello Student2.prototype.hello 

console.log(ww.name);//ww
console.log(ww.hello());//hello Student2.prototype.hello 
console.log(ww.hello === ls.hello);//true

js中的工廠模式:(可以不用new對象,直接在內部封裝所有的new操作).

function Student(props) {  
    this.name = props.name || '匿名';//默認值 爲'匿名'
    this.grade = props.grade || 1;//默認值爲1
}

Student.prototype.hello = function () {  
    console.log('Hello ,'+this.name +'!');
}
function createStudent(props) {  
    return new Student(props || {})
}
//創建對象
var hk = createStudent({
    name:'凱凱',
    grade: 20
});
console.log(hk.grade+','+hk.name);//20,凱凱

如果創建的對象有很多屬性,我們只需要傳遞的某些屬性,剩下的屬性可以用默認值。由於參數是一個Object, 我們無需記憶參數的順序。如果恰好從JSON拿到了一個對象,就可以直接創建出hk。

例子:

function Cat(name) {  
    this.name = name;
}
Cat.prototype.say = function () {  
    return `Hello,${this.name}`
}
var kitty = new Cat('kitty');
console.log(kitty.say());//Hello,kitty

原型繼承

在傳統的基於Class的語言如Java、C++中,繼承的本質是擴展一個已有的Class,並生成新的Subclass.
但是在JS中由於採用的是原型繼承,無法直接擴展一個Class,根本不存在Class這種類型。
現在我們要基於Student擴展出PrimaryStudent,可以先定義出PrimaryStudent:

function Student(props){
    this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {  
    console.log('hello'+this.name+'!');
}

function PrimaryStudent(props) {  
    //調用Student構造函數,綁定this變量:
    Student.call(this.props);
    this.grade = props.grade || 1;
}

但是,調用了Student構造函數不等於繼承了Student,PrimaryStudent創建的對象的原型是:

new PrimaryStudent() ----》 PrimaryStudent.prototype -----》Object.prototype ----->null

必須把原型修改爲:

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null

我們可以通過一個空函數 F來實現:

function Student(name) {  
    this.name = name;
}

Student.prototype.hello = function () {  
    console.log('hello'+this.name+'!');
}

function PrimaryStudent(props) {  
    //調用Student構造函數,綁定this變量:
    Student.call(this.props);
    this.grade = props.grade || 1;
}

//空函數F
function F() {  

}

//把F的原型指向Student.prototype:
F.prototype =  Student.prototype;

//把PrimaryStudent的原型指向一個新的F對象,F對象的原型正好指向 Student.prototype
PrimaryStudent.prototype = new F();

//把PrimaryStudent原型的構造函數修復爲PrimaryStudent
PrimaryStudent.prototype.constructor = PrimaryStudent;

//繼續在PrimaryStudent原型(就是new F()對象)上定義方法
PrimaryStudent.prototype.getGrade = function () {  
    return this.grade;
};

//創建hk
var hk = new PrimaryStudent({
    name:'凱凱',
    grade:18
});

console.log(hk.name);
console.log(hk.grade);
console.log(hk.hello());//

//驗證原型:
console.log(hk.__proto__ === PrimaryStudent.prototype);//true
console.log(hk.__proto__.__proto__ === Student.prototype);//true

//驗證繼承關係
console.log(hk instanceof PrimaryStudent);//true
console.log(hk instanceof Student);//true

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章