JavaScript高级程序设计(第4版)读书分享笔记记录
适用于刚入门前端的同志
原型链
ECMA-262
把
原型链
定义为
ECMAScript
的主要继承方式。
原型链词义:每个构造函数都有一个原型对象(prototype),原型对象有一个属性(constructor)指回构造函数,而实例有一个内部指针(__proto__)指向原型对象。如果原型是另一个类型的实例呢(我们让原型对象等于另一个类型的实例)?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。
例子:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
let instance = new SubType();
如此层层递进,就构成了实例与原型的链条,形成了原型链
默认原型
实际上,原型链中还有一环。默认情况下,所有引用类型都继承自 Object
,这也是通过原型链实现的。任何函数的默认原型都是一个 Object
的实例,这意味着这个实例有一个内部指针指向
Object.prototype
。
补充了Object的原型链关系图:
原型与继承关系
原型与实例的关系可以通过两种方式来确定:
第一种方式是使用 instanceof 操作符,如果一个实例的原型链中出现过相应的构造函数,则 instanceof 返回 true
。
还是以上面代码为例子:
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
确定这种关系的第二种方式是使用
isPrototypeOf()方法,只要原型链中包含这个原型,这个方法就返回
true
:
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true
原型链的问题
主要问题出现在原型中包含引用值的时候。前面在谈到原型的问题时也提到过,原型中包含的引用值会在所有实例间共享,这也是为什么属性常会 在构造函数中定义而不会定义在原型上的原因
例子:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
// 继承 SuperType
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black"
盗用构造函数
为了解决原型包含引用值导致的继承问题
基本思路很简单:在子类 构造函数中调用父类构造函数。可以使用 apply()和 call()方法以新创建的对象为上下文执行构造函数。
例子:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承 SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
组合继承
组合继承
(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来
基本的思路:是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。
组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式
例子:
function SuperType(name){
//实例属性
this.name = name;
this.colors = ["red", "blue", "green"];
}
//原型链属性,方法
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age){
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27
原型式继承
原型继承例子:
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
这里使用到的object()方法,是会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。
ECMAScript 5 通过增加
Object.create()
方法将原型式继承的概念规范化了。这个方法接收两个 参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的 object()
方法效果相同。
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住, 属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
寄生式组合继承
组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次:一次在是
创建子类原型时调用,另一次是在子类构造函数中调用,所以诞生了寄生式组合继承
例子:
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
};