详解 javascrip 中的继承

关于继承

继承是 00语言 中的一个最为人津津乐道的概念。许多 00语言 都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

原型链

每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针。

原型既然作为对象,属性的集合,自然可以自定义许多属性。比如对象的原型:

当然我们也可以自己在原型对象上自定义方法或新增自己的属性,比如:
         				function superType() {
         					superType.prototype.name = 'Anani';
         					superType.prototype.getYear = function() {
         						console.log(2018);
         					}
         				}
         			
结果就变成这样了:

而实例都包含一个指向原型对象的内部指针。当我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。

原型链存在的问题:1、包含引用类型值的原型属性会被所有实例共享。2、在创建子类型的实例时,不能向超类型的构造函数中传递参数(其实是有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)。

继承

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。
用具体的代码来讲:
	         				function SuperType() {
	         					this.property = 'something';
	         				}
	         				SuperType.prototype.getSuperValue = function() { return this.property; };

	         				function SubType() {
	         					this.subProperty = 'other something';
	         				}
	         				SubType.prototype = new SuperType();
	         				SubType.prototype.getSubValue = function() { return this.subProperty; };

	         				var instance = new SubType();
	         				alert(instance.getSuperValue());
	         			
以上的代码:通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype,使得 SubType 继承了 SuperType,实现的本质是重写原型对象(没有使用 SubType 默认提供的原型,而是给它换了一个新原型),代之以一个新类型的实例。最终结果就是这样的:instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型。getSuperValue() 方法仍然还在 SuperType.prototype 中,但 property 则位于 SubType.prototype 中。这是因为 property 是一个实例属性(SubType.prototype 现在是 SuperType 的实例),而 getSuperValue() 则是一个原型方法。另外 instance.constructor 现在指向的是 SuperType,这是因为原来 SubType.prototype 中的 constructor 被重写了的缘故。

通过实现原型链,本质上扩展了原型搜索机制。当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。就上面的例子来说,调用 instance.getSuperValue() 会经历三个搜索步骤:1)搜索实例;2)搜索 SubType.prototype;3)搜索 SuperType.prototype,最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行直到原型链末端。

默认的原型

所有引用类型默认都继承了 Object,而这个继承也是通过原型链实现的。需要注意的是:所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype 。这也正是所有自定义类型都会继承 toString()、valueOf() 等默认方法的根本原因。

每个对象都有一个隐藏的属性:__proto__,可称为隐式原型,其指向创建该对象的函数的 prototype。但是 Object.prototype 是一个特例——它的 __proto__ 指向的是 null。

确定原型和实例的关系

在检测数据类型时,我们可以通过 typeof 判断,当遇到 string/number/boolean/undefined 都很顺利。但是如果这个值是对象或 null,返回"object"。如果这个值是函数,返回"function"。此时我们需要用到 instanceof 操作符。在此我们也用 instanceof 操作符来判断原型和实例的关系,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。以下几行代码就说明了这一点:

另外我们也可以使用 isPrototypeOf() 方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf() 方法也会返回 true。