《javascript 高级程序设计》——继承
JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型,使用原型链的继承方式。
prototype和length是每一个函数类型自带的两个属性,prototype属性是一个指针,指向它的原型对象。
原型对象内部也有一个指针(constructor属性)指向构造函数:Person.prototype.constructor = Person;
Js中的所有对象(undefined、null等特殊情况除外)都有一个内置的[[Prototype]]属性,指向它“父类”的prototype,这个内置属性在ECMA标准中并没有给出明确的获取方式,但是许多Js的实现(如Node、大部分浏览器等)都提供了一个proto属性来指代这一[[Prototype]],ES5中增加了一个方法,Object.getPrototypeOf(),返回proto的值
JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
- 原型链
重写原型对象,代之以一个新类型的实例
原型链:原型链的形成真正是靠proto,在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。
1 | function SuperType(){ |
prototype是函数对象上预设的对象属性,原型是对象上的原型,指向构造器的prototype 属性
注意instance.constructor现在指向的是SuperType
通过原型链实现继承的时候,不能使用对象字面量创建原型方法
原型链的问题
最主要的问题来自包含引用类型值的原型。包含引用类型的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上回变成另一个类型的实例。于是,原先的实例属性也就变成了原型的属性。
在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
借用构造函数
在子类型的构造函数内部调用超类型构造函数组合继承
将原型链和借用构造函数的技术组合在一起,使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承
1 | function Shape() { |
组合式继承是js最常用的继承模式,但组合继承的超类型在使用过程中会被调用两次;一次是创建子类型的时候,另一次是在子类型构造函数的内部
- 原型式继承(ES5 新增了Object.create()规范了原型式继承)
- 寄生式继承
寄生组合式继承
最有效的继承方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info("Shape moved.");
};
// Rectangle - subclass
function Rectangle() {
Shape.call(this); //call super constructor.
}
Rectangle.prototype = Object.create(Shape.prototype);
var rect = new Rectangle();
rect instanceof Rectangle //true.
rect instanceof Shape //true.
rect.move(1, 1); //Outputs, "Shape moved."
//Polyfill
if (typeof Object.create != 'function') {
Object.create = (function() {
function Temp() {}
var hasOwn = Object.prototype.hasOwnProperty;
return function (O) {
if (typeof O != 'object') {
throw TypeError('Object prototype may only be an Object or null');
}
Temp.prototype = O;
var obj = new Temp();
Temp.prototype = null;
if (arguments.length > 1) {
// Object.defineProperties does ToObject on its first argument.
var Properties = Object(arguments[1]);
for (var prop in Properties) {
if (hasOwn.call(Properties, prop)) {
obj[prop] = Properties[prop];
}
}
}
return obj;
};
})();
}
总结一下继承实现方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/* method 1 缺点: 父类实例属性为引用类型时,不恰当地修改会导致所有子类被修改
创建父类实例作为子类原型时,可能无法确定构造函数需要的合理参数,这样提供的参数继承给子类没有实际意义,当子类需要这些参数时应该在构造函数中进行初始化和设置*/
Rectangle.prototype = new Shape();
// method 2 缺点:父类构造函数原型与子类相同。修改子类原型添加方法会修改父类
Rectangle.prototype = Shape.prototype;
// method 3 需要兼容性
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.area = function () {
// do something
};
**在子类构造函数中调用父类构造函数实现实例属性初始化**
function Rectangle() {
Shape.call(this); //call super constructor.
}
//polyfill
if (typeof Object.create !== 'function') {
Object.create = function(o, props) {
function F() {}
F.prototype = o;
if (typeof(props) === "object") {
for (prop in props) {
if (props.hasOwnProperty((prop))) {
F[prop] = props[prop];
}
}
}
return new F();
};
}
hasOwnProperty
为了判断一个对象是否包含自定义属性而不是原型链上的属性, 我们需要使用继承自 Object.prototype 的 hasOwnProperty 方法。
hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。确定原型与实例的关系
instanceof
isPrototypeOf()