本文深入解析JavaScript中构造函数继承的多种实现方式,包括构造函数绑定、prototype模式、直接继承prototype、空对象中介、封装函数及拷贝继承。详细对比各方法的优缺点,重点分析prototype修改对constructor的影响及解决方案,帮助开发者掌握JS面向对象编程的核心技巧。
JavaScript构造函数继承概述
二、JavaScript 面向对象编程:构造函数的继承
本节重点阐述如何生成同时继承多个对象的实例。
例如,假设存在一个表示“动物”的构造函数,
function Animal(){
this.species = "动物";
}
以及另一个表示“猫”的构造函数,
function Cat(name,color){
this.name = name;
this.color = color;
}
如何实现“猫”对“动物”的继承?
构造函数绑定与prototype模式
1. 构造函数绑定
最简便的方式是利用 call 或 apply 方法,将父类构造函数绑定至子类,即在子类构造函数内部添加如下代码:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
2. prototype 模式
更为常见的做法是借助 prototype 属性。
若“猫”的 prototype 对象指向一个 Animal 实例,则所有“猫”的实例均可继承 Animal 的属性。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
代码首行将 Cat 的 prototype 对象指向一个 Animal 实例。
1. Cat.prototype = new Animal();
此举完全替换了 prototype 对象的原有值,但第二行代码的作用何在?
1. Cat.prototype.constructor = Cat;
每个 prototype 对象均具备一个 constructor 属性,指向其对应的构造函数。因此,Cat.prototype 的 constructor 属性应指向 Cat。
前一步骤已移除原 prototype 对象的值,导致新 prototype 对象缺失 constructor 属性。若不手动补全,后续“继承链”将出现故障。此即第二行代码的含义。
此为关键原则,编程时务必遵循。下文均依此原则执行,即若替换了 prototype 对象,
1. o.prototype = {};
则下一步必须为新的 prototype 对象添加 constructor 属性,并将其指回原构造函数。
1. o.prototype.constructor = o;
直接继承prototype及其缺陷
3. 直接继承 prototype
鉴于 Animal 对象中不变属性可直接写入 Animal.prototype,Cat() 亦可跳过调用 Animal(),直接继承 Animal.prototype。
首先改写 Animal 对象:
1. function Animal(){ } 2. Animal.prototype.species = "动物";
随后,将 Cat 的 prototype 对象指向 Animal 的 prototype 对象,从而完成继承。
Cat.prototype = Animal.prototype;
CatCat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
相较于前一种方法,此举优势在于效率更高(无需执行及创建 Animal 实例),且节省内存。然而,Cat.prototype 与 Animal.prototype 现指向同一对象,对 Cat.prototype 的任何修改均会反映至 Animal.prototype。
因此,上述代码存在缺陷,请留意第二行:
1. Cat.prototype.constructor = Cat;
该语句实际上也修改了 Animal.prototype 对象的 constructor 属性!
1. alert(Animal.prototype.constructor); // Cat
空对象中介与封装函数
4. 利用空对象作为中介
鉴于“直接继承 prototype”存在上述缺陷,可采用空对象作为中介。
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F 为空对象,几乎不占用内存。此时,修改 Cat 的 prototype 对象不会影响 Animal 的 prototype 对象。
1. alert(Animal.prototype.constructor); // Animal
5. prototype 模式的封装函数
将上述方法封装为函数,以方便调用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
使用时,方法如下:
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
此 extend 函数即为 YUI 库实现继承的方式。
此外说明一点,函数体最后一行:
1. Child.uber = Parent.prototype;
意为为子对象设置 uber 属性,该属性直接指向父对象的 prototype 属性。这相当于在子对象上开辟一条通道,以便直接调用父对象方法。该行代码仅用于确保继承的完备性,属备用性质。
拷贝继承实现
6. 拷贝继承
前述方法采用 prototype 对象实现继承。亦可换用思路,纯粹通过“拷贝”方式实现继承。简言之,若将父对象的所有属性与方法拷贝至子对象,是否也能实现继承?
首先,将 Animal 的所有不变属性置于其 prototype 对象上。
1. function Animal(){} 2. Animal.prototype.species = "动物";
随后,编写一个函数以实现属性拷贝的目的。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
该函数旨在将父对象 prototype 对象中的属性逐一拷贝至 Child 对象的 prototype 对象。
使用时,写法如下:
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
以上关于深入剖析 JavaScript 面向对象编程的内容即为小编分享的全部,希望能提供参考,也希望大家多多支持本站。