面向對象中原型以及原型鏈非常重要,複習面向對象,學習一下原型和原型鏈,創建對象部分可以看前一個文章,傳送門,繼承部分可以看後一個文章,傳送門 什麼是原型: 原型有兩種形式:prototype和__proto__;對應的呈現方式不同。 prototype:是函數的一個屬性,這個屬性的值是一個對象。所以 ...
面向對象中原型以及原型鏈非常重要,複習面向對象,學習一下原型和原型鏈,創建對象部分可以看前一個文章,傳送門,繼承部分可以看後一個文章,傳送門
什麼是原型:
原型有兩種形式:prototype和__proto__;對應的呈現方式不同。
prototype:是函數的一個屬性,這個屬性的值是一個對象。所以一切的函數都有原型,這個原型就是prototype。
__proto__:是對象的一個屬性,同樣的屬性值也是一個對象。(但__proto__不是一個規範屬性,只是部分瀏覽器實現了此屬性,對應的標準屬性是[[prototype]])所以一切對象都有標準屬性。
ps:函數沒有__proto__屬性,同樣的,對象也沒有prototype屬性。
var obj = {name:'peter'};
console.log(obj.__proto__) // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
var fn = function(){this.name = 'peter'};
console.log(fn.prototype) // {constructor: ƒ}
所有的函數的預設原型都是Object的實例,因此預設原型都會包含一個內部指針,指向Object.prototype,所以所有的自定義類型都含有toString(),valueOf()等預設方法的根本原因。
理解原型對象:
在高程中,有句原話:無論什麼時候,只要創建了一個函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在預設情況下,所有的原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。
簡單來說:只要創建了一個函數,都會有一個prototype屬性,屬性值就是一個對象,這個對象里有一個屬性constructor,這個值就是該函數。
constructor是一個構造器。一般指向的是其構造函數。
var fn = function(){this.name = 'peter'};
console.log(fn.prototype) // {constructor: ƒ}
console.log(fn.prototype.constructor == fn) // true
對於__proto__,只要是對象,都有constructor屬性。在創建的方式中,constructor所指向的構造函數不一樣。
1 字面量創建對象:
字面量創建對象,其中__proto__的constructor屬性指向的構造函數是原始對象函數Object()。
var obj = {name:'peter'}
console.log(obj.__proto__.constructor) // ƒ Object() { [native code] }
console.log(obj.__proto__.constructor == Object); // true
console.log(obj.constructor == Object ) // true
console.log(obj.__proto__.constructor == obj.constructor) // true
2 構造函數創建對象:
所謂的構造函數:就是先創建一個函數,new實例化一個對象,函數里的this指向這個對象。
構造函數是個函數,有prototpe屬性,值是個對象,對象里有個constructor屬性,指向這個函數Preson
實例化的對象peter是一個對象,有__proto__屬性,值是個對象,對象里constructor屬性,指向這個構造函數Person。
兩個constructor所對應的構造函數是相等的,因此實例化的__proto__ 和 構造函數的prototype是相等的。
function Person(name,age){
this.name = name;
this.age = age;
this.sayHi = function (){
console.log(this.name);
}
}
var peter = new Person('peter','male');
console.log(Person.prototype.constructor == peter.__proto__.constructor) // true
console.log(Person.prototype == peter.__proto__) // true
3 Object.create() 創建對象
Object.create()是es5新增的一個方法,創建一個新對象,使用現有的對象來提供新創建的對象的__proto__。
ps!這個創建的對象的__proto__和原對象的__proto__不一樣,這說明瞭新創建的對象只是複製願對象的原型,但是是一個獨立的原型。
var obj = {};
var male = Object.create(obj);
console.log(male.__proto__.constructor) // ƒ Object() { [native code] }
male.name = 'tom';
console.log(male); // {name:'tom'}
console.log(obj); // {name:'peter'}
console.log(male.__proto__ == obj.__proto__); // false
原型鏈:
其基本思想是:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的__proto__(即它的構造函數的prototype)中尋找,如果該__proto__上沒有這個屬性,就去__proto__的屬性上去找(__proto__.__proto__),依次往下找,找到就使用,找不到就繼續往下找,到最上層都沒有找到就返回null。
這樣的形式就叫原型鏈。 總結如下:由於__proto__是任何對象都有的屬性,所以會形成一條__proto__連起來的鏈條,遞歸訪問__proto__,到最後若沒有找到,則返回一個null。
例子如下:(為了強調原型鏈,所以採用了混合模式創建對象)
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log(this.name);
}
var peter = new Person('peter','male');
peter.sayName = function(){
console.log('我叫17號')
}
peter.sayName();
peter.sayHi();
peter.toString();
peter.toGo();
這個例子要調用四個方法。先描述怎麼拿到該方法:
1.peter.sayName(); // 我叫17號
對象本身寫的方法,是掛載了對象的屬性上,所以不用通過原型鏈的方式直接就能拿到該方法。
2.peter.sayHi(); // peter
該對象上沒有sayHi的方法,=> 該對象的__proto__去找 => peter.__proto__(由於peter.__proto__ = Person.prototype) => 該prototype上有sayHi屬性,調用,查找結束。
3.peter.toString(); // "[object Object]"
該對象上沒有toString的方法,=> 該對象的__proto__去找 => peter.__proto__ => 該__proto__上沒有該屬性 => peter.__proto__.__proto__ => 在該原型上找到toString(),調用,結束。
4.peter.toGo();
該對象上沒有toGo的方法,=> 該對象的__proto__去找 => peter.__proto__ => 該__proto__上沒有該屬性 => peter.__proto__.__proto__ => 在該原型上沒有該屬性 => peter.__proto__.__proto__.__proto__ => 返回null => 沒有該屬性,直接報錯。
從上面的例子就可以得知,原型鏈就是通過__proto__連起來的鏈子,可以依次查找,找到就調用,找不到直接undefined或報錯。這就是原型鏈。
後記: 在複習面向對象中,由於篇幅過長,將知識點分成了三部分,面向對象--創建對象,面向對象--原型與原型鏈,面向對象--繼承,對應的知識點我放在了github里,有需要的可以去clone學習,覺得好的話,給個star。 文章有不對或者不理解的地方,請私信或者評論,一起討論進步。
參考資料: javascript高級程式設計(第三版)