企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 6.3 继承 由于函数没有签名,在ECMAScript中无法实现接口继承,ECMAScript只支持**实现继承**,而且其实现继承主要是依靠**原型链**来实现的。 ### 6.3.1 原型链 基本思想:利用原型让一个引用类型继承另一个引用类型。 ~~~ function SuperType(){ this.property = true; } SuperType.property.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); //继承了SuperType SubType.prototype.getSubValue = function(){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue) //true ~~~ 此时`instance.constructor`现在指向的是`SuperType`,这是因为`SubType`的原型指向了另一个对象`SuperType的原型`,而这个原型对象的constructor属性指向的是SuperType。 **1. 别忘记默认的原型** 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个**内部指针**,指向`Object.prototype`,这正是所有自定义类型都会继承toString()、valueOf等默认方法的根本原因。 **2. 确定原型和实例的关系** 通过两种方式来确定原型与实例之间的关系。第一种方式是使用`instanceof`操作符;第二种方式是使用`isPrototypeOf()`方法。 ~~~ alert(SubType.prototype.isPrototypeOf(instance)); //true; ~~~ **3. 谨慎地定义方法** 给原型添加方法的代码一定要放在**替换**原型的语句**之后**。 提醒:在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会**重写原型链**。 **4. 原型链的问题** * 包含引用类型值的原型,其属性会被所有实例共享。 * 在创建子类型的实例时,不能向超类型的构造函数中传递参数。 ### 6.3.2 借用构造函数(伪构造对象或经典继承) **基本思想:在子类型构造函数的内部调用超类型构造函数。** ~~~ function SuperType(){ this.colors = ['red','yellow','blue']; } function SubType(){ SuperType.call(this); //继承SuperType } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green" ~~~ 通过使用`call()或者apply()`方法,我们实际上是在(未来将要)创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType()对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本。 **1. 传递参数** 相对于原型链而言,借用构造函数有一个很大的优势,即**可以在子类型构造函数中向超类型构造函数传递参数。** ~~~ function SuperType(name){ this.name = name; } function SubType(){ SuperType.call(this,'ken'); this.age = 28; } ~~~ **2. 借用构造函数的问题** * 方法都在构造函数中定义,无法函数复用 * 在超类型的原型中定义的方法(使用prototype定义的),对于子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。 ### 6.3.3 组合继承(伪经典继承) 思路:**使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承**。这样,既在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。 ~~~ function SuperType(name){ this.name = name; this.colors = ['red','blue'.'green]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age) { //继承属性 SuperType.call(this,name); this.age = age; } //继承方法 SubType.prototype = new SuperType(); //将SuperType的实例复制给SubType的原型 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType('ken',28); instance1.colors.push('black); alert(instance1.colors); //'red,blue,green,black' instance1.sayName(); //'ken' instance1.sayAge(); //28 var instance2 = new SubType('Greg',27); alert(instance2.colors); //'red,blue,green' instance2.sayName(); //'Greg' instance2.sayAge(); //27 ~~~ 组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。 **缺点**:由于多次调用超类型构造函数而导致低效率。 ### 6.3.4 原型式继承 原型式继承没有严格意义上的使用构造函数,它是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。 ~~~ function object(o){ function F(){} F.prototype = o; return new F(); } ~~~ 在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的新实例。从本质上讲,object()对传入其中的对象执行了一次**浅复制**。例: ~~~ var person = { name: 'Nicholas', friends: ['wesley','fox'] }; var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); var yetAnotherPerson = object(person); yetAnotherPerson.name = 'Linda'; yetAnotherPerson.friends.push('Barbie'); alert(person.friends); //'wesley,fox,Rob,Barbie' ~~~ ES5通过新增`Object.create()`方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。 注:第二个参数格式与`Object.defineProperties()`方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。 ~~~ var person = { name: 'Nicholas', friends: ['wesley','fox'] }; var anotherPerson = Object.create(person, { name:{ value: 'Greg' } }); ~~~ 在没有必要兴师动众地创建构造函数,而只是想让一个对象与另个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,**包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。** ### 6.3.5 寄生式继承 寄生式继承是与原型式继承密写相关的一种思路:**创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象**,最后返回对象。 ~~~ function object(o){ function F(){} F.prototype = o; return new F(); } function createAnother(original) { var clone = object(original); clone.sayHi = function(){ alert('hi'); }; return clone; } ~~~ object(o)函数不是必须的,任何能够返回新对象的函数都适用于此模式。 这样创建的新对象不仅具有original的所有属性和方法,而且还有自己的sayHi()方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。 ### 6.3.6 寄生组合式继承(最有效理想) 寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。 思路是:不必为了指定子类型的原型调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是**使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型**。 ~~~ function inheritPrototype(subType,SuperType){ var prototype = object(subType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name){ this.name = name; this.colors = ['red','blue'.'green]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age) { //继承属性 SuperType.call(this,name); this.age = age; } //继承方法 inheritPrototype(subType,SuperType); SubType.prototype.sayAge = function(){ alert(this.age); } ~~~ 寄生组合式继承与组合继承的区别在于,**前者不将超类型中的实例属性继承为子类型的原型属性。**