原型链
构造函数、原型和实例之间的关系:
每个构造函数都有一个原型对象(prototype),原型也有一个属性(constructor)指向构造函数,而实例有一个内部指针(__proto__)指向原型。
如果一个构造函数的原型是另一个类型的实例,则这个原型本身有一个内部指针指向另一个原型,相应地,另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
function Father() {
this.property = true;
}
Father.prototype.getFatherProperty = function() {
return this.property;
}
function Son() {
this.sonProperty = false;
}
Son.prototype = new Father();
Son.prototype.getSonProperty = function() {
return this.sonProperty;
}
let instance = new Son();
console.log(instance.getFatherProperty());//true
//搜索getFatherProperty方法经历了3步,instance、Son.prototype和Father.prototype。
//搜索不到会一直持续到原型链的末端,通常是Object.prototype
原型与实例的关系
- 使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true
console.log(instance instanceof Object);//true
console.log(instance instanceof Father);//true
console.log(instance instanceof Son);//true
- 使用isPrototypeOf()方法,只要原型链中包含这个原型,就返回true
console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(Father.prototype.isPrototypeOf(instance));//true
console.log(Son.prototype.isPrototypeOf(instance));//true
原型链的问题
如果Father构造函数定义了一个引用类型的属性,则Son通过原型继承Father后,Son.prototype是Father的一个实例,因而也有这个引用类型的属性。所以现在所有的Son的实例都会共享这个属性,即其中一个实例修改了这个属性值也会反映到别的实例上。
盗用构造函数(经典继承)
在子类构造函数中调用父类构造函数,使用apply()或call()方法以子类新创建的对象为上下文this来执行父类构造函数。
function Father() {
this.hobbies = ['music','football','swimming'];
this.sayHi = function() {
console.log('Hi!');
}
}
function Son() {
Father.call(this);
}
let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!
let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]
instance2.sayHi();//Hi!
盗用构造函数的问题
在构造函数中定义的方法,不能重用,每次创建新实例,都会给该实例添加一遍方法属性,造成资源浪费。而且使用这种方式继承,子类不能访问父类原型上的属性和方法,只能使用构造函数模式。
组合继承(伪经典继承)
综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法,通过构造函数继承实例属性。这样可以把方法定义在原型上得以重用,每个实例有属于自己的属性。
function Father() {
this.hobbies = ['music','football','swimming'];
}
Father.prototype.sayHi = function () {
console.log('Hi!');
}
function Son() {
Father.call(this);
}
Son.prototype = new Father();
let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!
let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]
instance2.sayHi();//Hi!
组合继承应该是使用最多的继承方式,但两次调用父类构造函数,让子类原型中存在多余的属性(子类实例中已经存在的属性),导致效率低下。
原型式继承
有这么一个函数,先创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时构造函数的一个实例。本质上,是对传入的对象执行了一次浅复制。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
Object.create()接收两个参数,一个是作为新对象原型的对象,另一个是给新对象定义额外属性的对象(可选)。
在只有一个参数时,效果与object()方法相同。
let house = {
furniture: ['fan','refrigerator','table']
}
let house1 = Object.create(house);
house1.furniture.push('chair');
let house2 = Object.create(house);
house2.furniture.push('cabinet');
console.log(house.furniture);//["fan", "refrigerator", "table", "chair", "cabinet"]
Object.create()的第二个参数与Object.defineProperties()的第二个参数一样,通过各自的描述符来描述,这样添加的属性会遮蔽原型对象上的同名属性。
let house = {
color: 'yellow',
furniture: ['fan','refrigerator','table']
}
let anotherHouse = Object.create(house, {
color: {
value: 'green'
}
})
console.log(anotherHouse.color);//green
原型式继承适合不需要创建构造函数,但需要在对象间共享信息的场合,但属性中包含引用值的会在相关对象间共享。
寄生式继承
创建一个实现继承的函数(比如前面用到的object函数),以某种方式增强对象,然后返回这个对象。
function createAnother(original){
let clone = object(original);
clone.getColor = function() {
console.log(this.color);
};
return clone;
}
let house = {
color: 'yellow',
furniture: ['fan','refrigerator','table']
}
let anotherHouse = createAnother(house);
anotherHouse.getColor();//yellow
object()函数不是一定的,任何返回新对象的函数都可以使用
这里添加的函数是难以重用的,与构造函数类似
寄生式组合继承
寄生式组合继承通过盗用构造函数继承属性,使用混合式原型链继承方法。
这里不通过调用父类构造函数给子类原型赋值,而是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
function inheritPrototype(subType, superType) {
function F() {};
F.prototype = superType.prototype;
let prototype = new F();
prototype.constructor = subType;
subType.prototype = prototype;
}
function Father() {
this.hobbies = ['music','football','swimming'];
}
Father.prototype.sayHi = function () {
console.log('Hi!');
}
function Son() {
Father.call(this);
}
inheritPrototype(Son, Father);
let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!
let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]
这里只调用了一次父类构造函数,避免了组合继承中子类的原型上不必要的属性,因此这个例子的效率最高,可以算是引用类型继承的最佳模式。
本文深入探讨JavaScript中的六种继承实现:原型链、构造函数、组合继承、原型式继承、寄生式继承和寄生组合继承。每种方式的优缺点、工作原理及应用场景均有详细阐述,帮助理解JavaScript中对象间的继承机制。

被折叠的 条评论
为什么被折叠?



