继承
接口继承和实现继承。
接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ESMAScript中无法实现接口继承。
ESMAScript只支持实现继承,而且其实现继承主要是依靠
原型链来实现的。
原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
基本概念:那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型对象的指针,相应地,另一个原型对象也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。
实现的本质是重写原型对象,代之以一个新类型的实例。
别忘记默认的原型
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的
根本原因。
确定原型和实例的关系
1.使用
instanseof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。
alert(instance instanceof Object);
2.使用
isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
alert(Object.prototype.isPrototypeOf(instance));
谨慎地定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,
给原型添加方法的代码一定要放在替换原型的语句之后(即先继承,再添加或重写方法)。
注意,在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做会重写原型链。
原型链的问题
1、最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数。
借用构造函数
在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做
借用构造函数的技术。
基本思想:在子类型构造函数的内部调用超类型构造函数。
注意:通过使用call()方法或apply()方法,在(未来将要)新创建的SubType实例的环境下调用SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了。
传递参数
相对于原型链而言,借用构造函数有一个很大的
优势:即可以在子类型构造函数中向超类型构造函数传递参数。
为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。
借用构造函数的问题
1、方法都在构造函数中定义,因此函数复用就无从谈起了;
2、在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
组合继承
也叫经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。
原型式继承
思路:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
可以在不必预先定义构造函数的情况下现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
寄生式继承
与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
寄生组合式继承
集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。