原型與原型鏈
基本概念
關(guān)于原型和原型鏈的知識详瑞,首先來理解下以下幾個知識點:
-
所有引用類型(
Object
掂林、Array
、Function
坝橡、Date
泻帮、RegExp
)都是對象。
對象都有__proto__
屬性计寇。 所有構(gòu)造函數(shù)都有
prototype
屬性锣杂。-
prototype
可以理解成一套模板,它用于保存一些方法番宁。
其中:constructor
是構(gòu)造器(創(chuàng)建當前對象的函數(shù))元莫, 也就是用來查看自己定義了什么構(gòu)造方法。
__proto__
屬性的作用是溯源蝶押,指向構(gòu)造它的那個構(gòu)造函數(shù)的prototype
踱蠢。
(也就是說,可以用它來尋找到構(gòu)造它的那個構(gòu)造函數(shù)的模板是啥棋电。)prototype
本身也是一個對象茎截,所以也有__proto__
屬性苇侵。(也就是第一點提到的,所有對象都有__proto__
屬性)Object
是原始對象企锌,所以為了避免死循環(huán)榆浓,規(guī)定Object.prototype.__proto__
會指向null
。
而null
和undefined
都沒有__proto__
屬性了霎俩,即最高層哀军。-
Object
和Function
的關(guān)系有點像是雞和蛋的關(guān)系,所以Object.__proto__=== Function.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype
例子說明
結(jié)合上面的基本概念打却,我們來看一下這個例子:
function Person(name) {
this.name = name
}
const Lee = new Person('Lee')
Lee.__proto__ === Person.prototype // true
// ↑ 對應(yīng)第4點杉适,Lee的__proto__(溯源作用)指向構(gòu)造它的那個構(gòu)造函數(shù)(Person)的prototype
Person.__proto__ === Function.prototype // true
// ↑ Person的__proto__指向構(gòu)造它的構(gòu)造函數(shù)(Function)的prototype
Person.toString // ? toString() { [native code] }
// ↑ Person里面并沒有toString方法,但是卻可以引用到
// 因為繼承了Object的一套“模板” ↓↓↓
Person.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // 到達最頂層了柳击!
// 第3點提到猿推,prototype可以理解成一套模板,它用于保存一些方法捌肴。其中constructor是構(gòu)造器(創(chuàng)建當前對象的函數(shù))蹬叭,也就是用來查看自己定義了什么構(gòu)造方法。
// 所以:↓↓↓
Lee.constructor === Person // Lee的構(gòu)造方法是Person
Person.constructor === Object
// Object和Function的關(guān)系有點像是雞和蛋的關(guān)系
Function.prototype.__proto__ === Object.prototype // true
Object.__proto__ === Function.prototype
如果用圖片的形式來表現(xiàn)状知,就是下圖這樣:
繼承
原型鏈繼承
function Parent() {
this.names = ['Debra', 'Patamo']
}
function Child() {}
Child.prototype = new Parent()
const child1 = new Child()
child1.names // ['Debra', 'Patamo']
缺點:
引用類型的屬性被所有實例共享秽五。
child1.names.push('Pikachu')
child1.names // ['Debra', 'Patamo', 'Pikachu']
const child2 = new Child()
child2.names // ['Debra', 'Patamo', 'Pikachu']
在創(chuàng)建 Child 的實例時,不能向Parent傳參饥悴。
借用構(gòu)造函數(shù)(經(jīng)典繼承)
function Parent() {
this.names = ['Debra', 'Patamo']
}
function Child() {
Parent.call(this)
}
const child1 = new Child();
child1.names.push('Pikachu');
console.log(child1.names); // ['Debra', 'Patamo', 'Pikachu']
const child2 = new Child();
console.log(child2.names); // ['Debra', 'Patamo']
優(yōu)點:
- 避免了引用類型的屬性被所有實例共享
- 可以在 Child 中向 Parent 傳參
缺點:
- 只能繼承父類的實例屬性和方法坦喘,不能繼承原型屬性/方法
- 無法實現(xiàn)復(fù)用,每個子類都有父類實例函數(shù)的副本西设,影響性能
組合繼承
function Parent() {
this.name = 'Debra'
}
Parent.prototype.getName = function() { return this.name }
function Child() {
Parent.call(this) // 繼承屬性
}
Child.prototype = new Parent() // 繼承方法
const child = new Child()
child.getName() // Debra
優(yōu)點:
- 彌補了原型鏈和結(jié)構(gòu)構(gòu)造函數(shù)的不足瓣铣,是JavaScript中使用最多的繼承模式。
- 保留了instanceof操作符和isPrototypeOf()方法識別合成對象的能力贷揽。
缺點:
調(diào)用了兩次父類構(gòu)造函數(shù)棠笑,影響性能。
一次是設(shè)置子類型實例的原型的時候:
Child.prototype = new Parent();
一次在創(chuàng)建子類型實例的時候:
var child1 = new Child();
回想下 new 的模擬實現(xiàn)禽绪,其實在這句中蓖救,我們會執(zhí)行:
Parent.call(this, name);
在這里,我們又會調(diào)用了一次 Parent 構(gòu)造函數(shù)丐一。
原型繼承
function createObj(o) {
function F() {}
F.prototype = o;
return new F();
}
就是 ES5 Object.create
的模擬實現(xiàn)藻糖,將傳入的對象作為創(chuàng)建的對象的原型。
缺點:
包含引用類型的屬性值始終都會共享相應(yīng)的值库车,這點跟原型鏈繼承一樣巨柒。
var person = {
name: 'kevin',
friends: ['daisy', 'kelly']
}
var person1 = createObj(person);
var person2 = createObj(person);
person1.name = 'person1';
console.log(person2.name); // kevin
person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]
注意:修改person1.name
的值,person2.name
的值并未發(fā)生改變,并不是因為person1
和person2
有獨立的 name 值洋满,而是因為person1.name = 'person1'
晶乔,給person1
添加了 name 值,并非修改了原型上的 name 值牺勾。
寄生式繼承
創(chuàng)建一個僅用于封裝繼承過程的函數(shù)正罢,該函數(shù)在內(nèi)部以某種形式來做增強對象,最后返回對象驻民。
function createObj (o) {
var clone = Object.create(o); // 重點
clone.sayName = function () {
console.log('hi');
}
return clone;
}
缺點:跟借用構(gòu)造函數(shù)模式一樣翻具,每次創(chuàng)建對象都會創(chuàng)建一遍方法。
****寄生組合式繼承(最佳)****
function object(o) {
let F = function () {}
F.prototype = o
return new F()
}
function prototype(child, parent) {
let prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
優(yōu)點:
- 只調(diào)用了一次 Parent 構(gòu)造函數(shù)回还,并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的裆泳、多余的屬性。
- 原型鏈還能保持不變柠硕;因此工禾,還能夠正常使用 instanceof 和 isPrototypeOf。