这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例。
今天要介绍的是,如何生成一个"继承"多个对象的实例。
比如,现在有一个"动物"对象的构造函数,
function Animal(){this.species = "动物";
}
还有一个"猫"对象的构造函数,
function Cat(name,color){this.name = name;
this.color = color;
}
怎样才能使"猫"继承"动物"呢?
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的实例。
Cat.prototype = new Animal();
它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?
Cat.prototype.constructor = Cat;
原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。也就是说,Cat.prototype 这个对象的constructor属性,是指向Cat的。
我们在前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。这就是第二行的意思。
总之,这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,
o.prototype = {};
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
o.prototype.constructor = o;
3. 直接继承prototype
由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
现在,我们先将Animal对象改写:
function Animal(){ }
Animal.prototype.species = "动物";
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
所以,上面这一段代码其实是有问题的。请看第二行
Cat.prototype.constructor = Cat;
这一句实际上把Animal.prototype对象的constructor属性也改掉了!
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对象。
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库如何实现继承的方法。
另外,说明一点。函数体最后一行
Child.uber = Parent.prototype;
意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。这等于是在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
6. 拷贝继承
上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?
首先,还是把Animal的所有不变属性,都放到它的prototype对象上。
function Animal(){}
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); // 动物
未完,请继续阅读第三部分《非构造函数的继承》。
(完)



朴素少年 说:
关注很久了,第一次沙发,顺便带上自己的链接
2010年5月23日 22:04 | 档案 | 引用
RedNax 说:
我还是不知道为什么要做
o.prototype.constructor = o;
是为了维护正确的继承回朔链?来保证形如this.constructor.prototype.constructor.prototype....这类回朔的正确性吗?
那么是不是说如果程序本来就不打算回朔的话其实也就没必要加这个了?
2010年5月23日 22:51 | 档案 | 引用
Ruan YiFeng 说:
基本上就是这个目的,还有就是instanceof运算符能返回正确的结果。
2010年5月24日 00:13 | 档案 | 引用
zhaorui 说:
看这个系列,我应该可以温习一下javascript
在原型对象绑定中
Animal.apply(this, arguments);
这一句里面的arguments 好像应该是 Animal?
2010年5月24日 00:15 | 档案 | 引用
zhaorui 说:
另外,我觉得第二种 prototype 的方式看上去比较简洁有效,后面的几种有其他的优点么?
2010年5月24日 00:18 | 档案 | 引用
Micky 说:
很棒~学习了~
深入浅出,容易理解~
个人倾向于“5. prototype模式的封装函数”这个方法~觉得还蛮优雅的~
2010年5月24日 09:32 | 档案 | 引用
RedNax 说:
受教了。
第5种方法确实不错,构造函数应该是(根据参数)为实例添加特定成员而存在的。new出来作为prototype的话,父类的构造函数就已经跑过一次了,结果子类构造的时候如果必要还要在跑一次,就显得浪费了。
不过debug和用代码回朔的时候可能会比较麻烦,两个prototype中间会隔着一个空类。
2010年5月24日 21:27 | 档案 | 引用
RedNax 说:
啊,抱歉想错了。
第5种方法不会增加空类的,那个new F()和new Parent()地位一样,只是不跑构造函数而已。
好方法……
2010年5月24日 21:32 | 档案 | 引用
dong2590 说:
function extend(Child, Parent) {
var F = function(){};
//这里应该是new Parent()吧
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
2010年5月25日 15:15 | 档案 | 引用
wiky 说:
不错,支持!
2010年5月29日 11:39 | 档案 | 引用
555555555 说:
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
这个好像parent必须实现prototype方法,
2010年12月27日 14:10 | 档案 | 引用
1w 说:
2011年1月12日 11:44 | 档案 | 引用
1w 说:
最后一种通过拷贝的方法来实现继承是有问题的. 这里博主用的是浅拷贝的方法, c[i] 是指向到 p[i], 而非赋值, 这样如果改动到 c[i], 会影响到 p[i]. 例如:
function fn1() {};
function fn2() {};
fn2.prototype.arr = [1,2];
var c = fn1.prototype, p = fn2.prototype;
for(var i in p) {c[i] = p[i]};
fn1.prototype.arr.push(3);
fn2.prototype.arr // [1,2,3]
建议用深拷贝的方法来实现:
(function(o) {
if(typeof o != "object") return o;
var obj = {};
for(var p in o) {
obj[p] = arguments.callee(o[p]);
return obj;
}
})();
2011年1月12日 12:18 | 档案 | 引用
1w 说:
不好意思, 上面这样写当遇到数组时是不行的, 参考峰兄的代码, 修改了一下:
var obj = (o.construcotr === Array)?[]:();
2011年1月13日 13:14 | 档案 | 引用
wolf18387 说:
期待峰兄能写一本js的书籍出来,现在市面上即便是翻译的国外的书籍,依然是有些晦涩难懂。但是感觉看峰兄的描述,感觉像在读一个故事,很容易理顺。
2011年6月 7日 13:59 | 档案 | 引用
kk 说:
将Cat的prototype对象指向一个Animal的实例(它相当于完全删除了Cat prototype对象原先的所有值,然后赋予它新的值,新值即Animal的属性,而Animal它自己本身有constructor属性,所以说Cat又继承得到了constructor,能否这样理解呢??
2011年6月10日 15:39 | 档案 | 引用
htmlcssjs 说:
看书跟看你的文章还是有豁然开朗的感觉啊
2011年6月14日 18:16 | 档案 | 引用
cazhfe 说:
5.prototype模式的封装函数
不知是作者的表达有误,还是确实没有经过测验,
按第五种的模式,父级是必须要实现prototype方法,相当于公共方法,可供子级访问
或重写!
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物。。。。。。实际会弹出undefined!
不过还是感谢作者的阐述!!
2011年7月27日 12:15 | 档案 | 引用
风无情 说:
大侠们,我也想深入学习下js面向对象思想,谁给推介本书啊。
2011年7月29日 17:18 | 档案 | 引用
Tyler Long 说:
感觉把面向对象的东西在js上面实现一遍, 增大代码了维护的难度, 而不是减小了维护的难度. 正在思索, 这么做是不是有必要呢?
2011年7月29日 22:02 | 档案 | 引用
dkgll 说:
js的面向对象写法是在是有点不好选择。在平时的项目中我还是倾向于函数式编程
2011年9月 1日 12:01 | 档案 | 引用
yaolf 说:
“所以新的prototype对象没有constructor属性”这句应该不对吧,Animal的实例也是有constuctor属性的,指向Animal,所以
Cat.prototype = new Animal();
这时的constructor应该是Animal
2011年9月16日 14:26 | 档案 | 引用
YQ君 说:
你好,我想问一下,关于第5种继承方法。子类继承了父类的数据和prototype方法,但之后子类貌似无法再使用自己的prototype方法了,因为prototype方法在extend()函数中已经被改写了。
莫非是父类使用prototype方法后,子类就无法使用?难道这种情况下,子类只能通过this添加新方法?这样的话我new多个子类就会造成代码重复吧?求解答。
function Parent() {
this.a = 'a';
}
Parent.prototype = {
print: function() {
alert(this.a);
}
}
function Child() {
Parent.apply(this, arguments);
}
Child.prototype = {
print2: function() {
alert('d');
}
}
extend(Child, Parent);
var obj = new Child();
obj.print();//输出'a'
obj.print2();//报错,找不到function
2011年10月31日 14:14 | 档案 | 引用
Yoya 说:
Douglas Crockford的专著《Javascript语言精粹》这本书没被你翻译真是一大悲剧,这段时间网上下了个《Javascript语言精粹》PDF电子版的感觉翻译的人翻译的真的挺差。
2011年11月29日 18:53 | 档案 | 引用
Jun 说:
Child.uber = Parent.prototype;
这样写有什么好处... 不是很理解
2011年12月28日 14:17 | 档案 | 引用
dindog 说:
2012年1月 4日 01:35 | 档案 | 引用
Jun 说:
function Animal() {
this.species = "动物";
}
function Cat(name, color) {
this.name = name;
this.color = color;
}
function extend(Child, Parent) {
var F = function() { };
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
extend(Cat, Animal);
var cat1 = new Cat("大毛", "黄色");
alert(cat1.species); // 结果是undefined\
取不到"动物" 值
2012年1月 4日 11:47 | 档案 | 引用
dindog 说:
这是另一个教程里面的例子,没有Child.prototype.constructor=Child,但后面看到依然是Child的实例。。。为什么会这样?
http://ejohn.org/apps/learn/#76
function Person(){}
Person.prototype.dance = function(){};
function Ninja(){}
// Achieve similar, but non-inheritable, results
Ninja.prototype = Person.prototype;
Ninja.prototype = { dance: Person.prototype.dance };
assert( (new Ninja()) instanceof Person, "Will fail with bad prototype chain." );
// Only this maintains the prototype chain
Ninja.prototype = new Person();
var ninja = new Ninja();
assert( ninja instanceof Ninja, "ninja receives functionality from the Ninja prototype" );
assert( ninja instanceof Person, "... and the Person prototype" );
assert( ninja instanceof Object, "... and the Object prototype" );
2012年1月 4日 17:01 | 档案 | 引用
dindog 说:
2012年1月 4日 17:38 | 档案 | 引用
闪电 说:
弱弱的问一句,第五种方法明显有问题。但怎么改呢?我是初学者不知道,求教啊!
2012年1月14日 23:40 | 档案 | 引用
diguanianzhu 说:
原因在这里:
function Animal() {
this.species = "动物";
}
应该写为:
function Animal(){}
Animal.prototype.speies="动物";
你理解错了作者第五种方法的意思,第五种是是从第三种和第四种方法延伸而来的。
2012年2月 2日 14:04 | 档案 | 引用