原型
- 概念:
- 原型就是一個對象
- 每個函數(shù)都有一個prototype的屬性崖飘,該屬性指向的是當(dāng)前函數(shù)的顯示原型對象
- 每個實例對象都有一個____proto____屬性,該屬性指向當(dāng)前實例對象的隱式原型對象
- 構(gòu)造函數(shù)的顯示原型對象 === 當(dāng)前構(gòu)造函數(shù)的實例對象的隱式原型對象
let Person = function() {};
// 1. 每個函數(shù)都偶有一個屬性prototype
console.log(Person.prototype); // 空對象 ------> 原型對象(隱式原型對象)
let person1 = new Person(); // 生成實例對象
// 2. 每個實例對象身上都有一個屬性__proto__杈女,該屬性指向當(dāng)前實例對象的原型對象(隱形原型對象)
// 3. 構(gòu)造函數(shù)的顯示原型對象 === 當(dāng)前構(gòu)造函數(shù)實例對象的隱式原型對象
Person.prototype === person1.__proto__; // true
- 對象:
- 實例對象(只可用「對象.屬性」調(diào)用)
- 函數(shù)對象(可用「對象.屬性」調(diào)用朱浴,也可用「對象()」的方式調(diào)用)
原型鏈
- 當(dāng)使用 「對象.屬性」 的時候,先從自身去查找有無該屬性达椰。如果沒有翰蠢,會沿著____proto____原型鏈去找,直到找到Object(Object是一個函數(shù)對象)的原型對象啰劲,如果還沒有則返回undefined梁沧。
- ____proto____這條鏈就是原型鏈。
a.b
// 找a會沿著 作用域 找蝇裤,找b(對象的屬性)會沿著原型鏈找
原型拓展
拓展一:instanceof
運算符
instanceof
是如何判斷的?
- 表達式:A
instanceof
B - 如何B函數(shù)的顯示原型對象在A對象的原型鏈上廷支,返回
true
,否則返回false
let Person = function() {}; // 構(gòu)造函數(shù)
let person1 = new Person(); // 創(chuàng)建實例對象
person1 instanceof Person; // true (person1.__proto__ === Person.prototype)
person1 instanceof Object; // true (Person.__proto__.__proto__ === Object.prototype)
person1 instanceof Function; // false
Person instanceof Function; // true
手動封裝一個 instanceof
- Object.getPrototypeOf() 方法返回指定對象的原型(內(nèi)部[[Prototype]]屬性的值)栓辜。
function myInstanceof(left, right) {
// 獲取對象的原型
let proto = Object.getPrototypeOf(left)
// 獲取構(gòu)造函數(shù)的 prototype 對象
let prototype = right.prototype;
// 判斷構(gòu)造函數(shù)的 prototype 對象是否在對象的原型鏈上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果沒有找到恋拍,就繼續(xù)從其原型上找,Object.getPrototypeOf方法用來獲取指定對象的原型
proto = Object.getPrototypeOf(proto);
}
}
拓展二:Object.getPropertyNames()
藕甩、Object.keys()
與for..in
由上圖可見芝囤,for..in
可以遍歷對象上所有的屬性,包括原型屬性;而
Object.getPropertyNames()
與 Object.keys()
只能遍歷對象中可枚舉的屬性悯姊。
另羡藐,obj.hasOwnProperty(propName)
方法會返回一個布爾值,指示對象自身屬性中是否具有指定的屬性(也就是悯许,是否有指定的鍵)仆嗦,不包括原型鏈上的屬性。故先壕,一般與for..in
配合使用瘩扼。
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 處理自身屬性
}
}
拓展三:ES5的繼承模式
1. 原型鏈繼承
// 父類
function Animal(name, age) {
this.name = name;
this.age = age;
this.attrObj = {
type: 'animal'
};
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃東西');
}
// 子類
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype = new Animal(); // 讓子類的原型 成為 父類的實例對象
let tom = new Cat('tom', 3);
tom.eat(); // tom在吃東西
-
缺點
i) 無法向父類的構(gòu)造方法傳參
ii) 所有Cat的實例對象的原型鏈都會指向同一個Animal實例, 因此對某個Cat實例的來自父類的引用類型變量修改會影響所有的Cat實例
let momo = new Cat('momo', 3);
momo.attrObj.type = '波斯貓';
let kate = new Cat('kate', 2);
kate.attrObj.type // 波斯貓
2. 構(gòu)造函數(shù)繼承
// 父類
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃東西');
}
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改變this指向
}
let tom = new Cat('tom', 3, 'boy');
-
缺點
繼承不到父類原型上的屬性和方法
3. 組合繼承
// 父類
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃東西');
}
// 子類
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改變this指向
}
Cat.prototype = new Animal(); // 讓子類的原型 成為 父類的實例對象
let tom = new Cat('tom', 3, 'boy');
tom.eat(); // tom在吃東西
-
缺點
每次創(chuàng)建子類實例都執(zhí)行了兩次構(gòu)造函數(shù)(Animal.call和 new Animal()),雖然這并不影響對父類的繼承垃僚,但子類創(chuàng)建實例時集绰,原型中會存在兩份相同的屬性和方法,這并不優(yōu)雅谆棺。
4. 寄生式組合繼承
解決構(gòu)造函數(shù)被執(zhí)行兩次的問題, 我們將指向父類實例改為指向父類原型, 減去一次構(gòu)造函數(shù)的執(zhí)行栽燕。
- Object.create() 方法用于創(chuàng)建一個新對象,使用現(xiàn)有的對象來作為新創(chuàng)建對象的原型(prototype)
// 父類
function Animal(name, age) {
this.name = name;
this.age = age;
this.attrObj = {
type: 'animal'
};
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃東西');
}
// 子類
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改變this指向
}
// 與組合式繼承的區(qū)別在于將Cat.prototype = new Animal(); 替換成 Cat.prototype = Object.create(Animal.prototype);
// Cat.prototype = Object.create(Animal.prototype)的意義為: Cat.prototype.__proto__ === Animal.prototype
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
let tom = new Cat('tom', 3, 'boy');
tom.eat(); // tom在吃東西
tom.attrObj.type = '波斯貓';
let kate = new Cat('kate', 2, 'girl');
console.log('kate.attrObj.type: ', kate.attrObj.type); // animal
new
關(guān)鍵字創(chuàng)建的對象與Object.create(proto, propertiesObject)
的區(qū)別
- new Object()生成的對象會繼承原型上的方法和屬性并且會繼承構(gòu)造函數(shù)的本地的屬性改淑。
- Object.create(proto, propertiesObject)只會繼承原型鏈上的方法和屬性(參數(shù)
proto
:是新創(chuàng)建對象的原型對象碍岔,必填。)
let Person = function() {};
let p1 = new Person(); // 生成實例對象
p1.__proto__ === Person.prototype; // true
// 當(dāng)我們不希望執(zhí)行構(gòu)造函數(shù)朵夏,而只需要繼承父類的原型上的方法和屬性時
let p2 = Object.create(Person.prototype);
p2.__proto__ === Person.prototype; // true