分类

Javascript面向对象编程(二):构造函数的继承

作者: 阮一峰

日期: 2010年5月23日

这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例。

今天要介绍的是,对象之间的"继承"的五种方法。

比如,现在有一个"动物"对象的构造函数。


  function Animal(){

    this.species = "动物";

  }

还有一个"猫"对象的构造函数。


  function Cat(name,color){

    this.name = name;

    this.color = color;

  }

怎样才能使"猫"继承"动物"呢?

一、 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

  function Cat(name,color){

    Animal.apply(this, arguments);

    this.name = name;

    this.color = color;

  }

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

二、 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 = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。

  alert(Cat.prototype.constructor == Animal); //true

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。

  alert(cat1.constructor == Cat.prototype.constructor); // true

因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!

  alert(cat1.constructor == Animal); // true

这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。

这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,

  o.prototype = {};

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

  o.prototype.constructor = o;

三、 直接继承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

四、 利用空对象作为中介

由于"直接继承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

我们将上面的方法,封装成一个函数,便于使用。

  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属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

五、 拷贝继承

上面是采用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); // 动物

(本系列未完,请继续阅读第三部分《非构造函数的继承》。)

(完)

珠峰培训

简寻

留言(99条)

关注很久了,第一次沙发,顺便带上自己的链接

我还是不知道为什么要做
o.prototype.constructor = o;
是为了维护正确的继承回朔链?来保证形如this.constructor.prototype.constructor.prototype....这类回朔的正确性吗?
那么是不是说如果程序本来就不打算回朔的话其实也就没必要加这个了?

引用RedNax的发言:

o.prototype.constructor = o;
是为了维护正确的继承回朔链?

基本上就是这个目的,还有就是instanceof运算符能返回正确的结果。

看这个系列,我应该可以温习一下javascript

在原型对象绑定中

Animal.apply(this, arguments);

这一句里面的arguments 好像应该是 Animal?

另外,我觉得第二种 prototype 的方式看上去比较简洁有效,后面的几种有其他的优点么?

很棒~学习了~
深入浅出,容易理解~

个人倾向于“5. prototype模式的封装函数”这个方法~觉得还蛮优雅的~

受教了。
第5种方法确实不错,构造函数应该是(根据参数)为实例添加特定成员而存在的。new出来作为prototype的话,父类的构造函数就已经跑过一次了,结果子类构造的时候如果必要还要在跑一次,就显得浪费了。
不过debug和用代码回朔的时候可能会比较麻烦,两个prototype中间会隔着一个空类。

啊,抱歉想错了。
第5种方法不会增加空类的,那个new F()和new Parent()地位一样,只是不跑构造函数而已。
好方法……

  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;

  }

不错,支持!

 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方法,

引用RedNax的发言:

啊,抱歉想错了。
第5种方法不会增加空类的,那个new F()和new Parent()地位一样,只是不跑构造函数而已。
好方法……

new F() 这里不还是 new 出一个实例么, 按作者的意思, 这里因为是 new 一个空对象, 资源消耗相对较小.

最后一种通过拷贝的方法来实现继承是有问题的. 这里博主用的是浅拷贝的方法, 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;
}
})();

不好意思, 上面这样写当遇到数组时是不行的, 参考峰兄的代码, 修改了一下:

var obj = (o.construcotr === Array)?[]:();

期待峰兄能写一本js的书籍出来,现在市面上即便是翻译的国外的书籍,依然是有些晦涩难懂。但是感觉看峰兄的描述,感觉像在读一个故事,很容易理顺。

引用Ruan YiFeng的发言:

基本上就是这个目的,还有就是instanceof运算符能返回正确的结果。

将Cat的prototype对象指向一个Animal的实例(它相当于完全删除了Cat prototype对象原先的所有值,然后赋予它新的值,新值即Animal的属性,而Animal它自己本身有constructor属性,所以说Cat又继承得到了constructor,能否这样理解呢??

看书跟看你的文章还是有豁然开朗的感觉啊

5.prototype模式的封装函数

不知是作者的表达有误,还是确实没有经过测验,

按第五种的模式,父级是必须要实现prototype方法,相当于公共方法,可供子级访问

或重写!
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物。。。。。。实际会弹出undefined!

不过还是感谢作者的阐述!!

大侠们,我也想深入学习下js面向对象思想,谁给推介本书啊。

感觉把面向对象的东西在js上面实现一遍, 增大代码了维护的难度, 而不是减小了维护的难度. 正在思索, 这么做是不是有必要呢?

js的面向对象写法是在是有点不好选择。在平时的项目中我还是倾向于函数式编程

我们在前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。这就是第二行的意思。

“所以新的prototype对象没有constructor属性”这句应该不对吧,Animal的实例也是有constuctor属性的,指向Animal,所以
Cat.prototype = new Animal();
这时的constructor应该是Animal

你好,我想问一下,关于第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

Douglas Crockford的专著《Javascript语言精粹》这本书没被你翻译真是一大悲剧,这段时间网上下了个《Javascript语言精粹》PDF电子版的感觉翻译的人翻译的真的挺差。

Child.uber = Parent.prototype;
这样写有什么好处... 不是很理解

引用Jun的发言:

Child.uber = Parent.prototype;
这样写有什么好处...不是很理解

因为当请求一个方法时,先会从本地对象搜索,没有的话,就遵从原型链寻找,直到最后对象是Object时还找不到就会返回undefined。 当知道属性是继承而来的,用uber方法调用比普通调用效率高。(少了一次遍历过程)

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\
取不到"动物" 值

这是另一个教程里面的例子,没有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" );

引用dindog的发言:


因为当请求一个方法时,先会从本地对象搜索,没有的话,就遵从原型链寻找,直到最后对象是Object时还找不到就会返回undefined。
当知道属性是继承而来的,用uber方法调用比普通调用效率高。(少了一次遍历过程)

除了上面说的,想想,因为子对象当另外定义了同名方法时,要调用父对象的方法,也只能通过这样(Firefox下支持非标准方法__proto__去调用上级对象)

弱弱的问一句,第五种方法明显有问题。但怎么改呢?我是初学者不知道,求教啊!

引用Jun的发言:

取不到"动物" 值

原因在这里:
function Animal() {
this.species = "动物";
}

应该写为:
function Animal(){}
Animal.prototype.speies="动物";


你理解错了作者第五种方法的意思,第五种是是从第三种和第四种方法延伸而来的。

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。
  Cat.prototype = new Animal();
它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。


想请问下 在《javascript高级程序设计》第六章讲原型动态性时,里面关于重写整个原型对象后,以前的prototype对象原先的值是仍然存在得 这是为什么啊、、、
function Person () {}

var person = new Person();
Person.prototype = {
constructor: Person,
name : "name",
age : "18"
}

说到底,每种继承方式都有缺陷。

我们在前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。这就是第二行的意思。

能解释下加这一句和不加这一句对于继承的影响吗?谢谢!

第3、4种方式怎么没有继承到Animal

想问一下3.4.5这几种方法只能继承Animal的不变属性 不能继承可变属性?

引用初学者的发言:

想问一下3.4.5这几种方法只能继承Animal的不变属性不能继承可变属性?

@初学者 确实是有这个问题的,可以参考这个方法,就解决了这个问题 http://www.codeproject.com/Articles/303796/How-to-Implement-Inheritance-in-Javascript
Inheritance_Manager = {};

Inheritance_Manager.extend = function(subClass, baseClass) {
function inheritance() { }
inheritance.prototype = baseClass.prototype;
subClass.prototype = new inheritance();
subClass.prototype.constructor = subClass;
subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;
}

Manager = function(id, name, age, salary) {
Manager.baseConstructor.call(this, id, name, age);
this.salary = salary;
alert('A manager has been registered.');
}

Inheritance_Manager.extend(Manager, Person);


感觉第二种方法和第四种方法之间没什么区别啊
第二种
function Animal(){};
function Cat(){};

Cat.prototype = new Animal;
Cat.constructor = Cat;
第四种
function Animal(){};
function Cat(){};
function F(){};

F.prototype = Animal.prototype;
Cat.prototype = new F;
Cat.constructor = Cat;

这个F()的存在没什么意义啊,到最后还是new一个对象替换prototype,然后修改prototype的constructor

引用水中月明的发言:

感觉第二种方法和第四种方法之间没什么区别啊
第二种
function Animal(){};
function Cat(){};

Cat.prototype = new Animal;
Cat.constructor = Cat;
第四种
function Animal(){};
function Cat(){};
function F(){};

F.prototype = Animal.prototype;
Cat.prototype = new F;
Cat.constructor = Cat;

这个F()的存在没什么意义啊,到最后还是new一个对象替换prototype,然后修改prototype的constructor

其实不然。你这样写Cat.constructor = Cat;是不对的。应该Cat.prototype.constructor = Cat;你再尝试的理解理解第二、第四种写法。应该能理解通吧?

其实,我觉得看第一和第二就行了,如果不是非常 的精通 又去看三 四五方法,只会增加自己的负担使自己迷糊,

function Animal(){}

Animal.prototype.species = "动物";
  
  function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat; 
  
  var cat1 = new Cat("大毛","黄色");
  alert(Cat.prototype.constructor == Animal); //为什么我这里是false ???

第一种:构造函数绑定,即用call(apply)把父对象的this指向改为子对象
缺点:不能继承原型上的属性和方法;

第二种:prototype模式,即把子对象的prototype对象指向Animal的一个实例;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
注意:当改了prototype对象的constructor时,记得改回来,否则将造成继承链紊乱;

第三种:直接继承prototype,即child.prototype = parent.prototype;
优点:相比第二种效率更高,比较省内存;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
且子对象的prototype对象修改后父对象的prototype也会被修改;

第四种:利用空对象作为中介,第三种的升级版;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
且子对象的prototype对象修改后父对象的prototype也会被修改;

第五种:拷贝继承
缺点:只能继承原型上的属性和方法;
优点:如果子对象的prototype对象上有属性或方法时,不会被清除,
且子对象的prototype对象修改后父对象的prototype不会被修改;

总结:继承加在原型上的属性和方法时用第五种,而继承写在构造函数里的属性和方法则用第一种,两则结合用

@youlong:

必须是false啊。因为你之前已经有更改Cat.prototype.constructor的值了。

function Animal(){}
Animal.prototype.specise='动物';

function Cat(name,color){this.name=name;this.color=color;}
Cat.prototype=new Animal();
Cat.prototype.constructor=Cat;
cat1=new Cat('小小','白');

为什么不介绍这种。不用空对象做中介,直接new父亲。

还有一种继承没有说到

  function Cat(name,color){
this.animal=Animal; //生成一个变量指向父类
this.animal(name,color);//执行一次父类
 
  }

非常受用,最近在学JS,LZ的文章简直是醍醐灌顶。。

NICE!

Javascript面向对象的操作太不直观了,这点也能理解,OO毕竟不是她的强项。

你上面说的第二种方法“prototype模式”,有一个地方写错了。当令子类的原型对象指向父类的实例的时候,子类的原型对象的constructor并没有指向父类哦,而是指向了Object。相应的,子类的实例的constructor当然也没有指向父类。如果我说错了,请指正哈!

收回我的话哈。当令子类的原型对象指向父类的实例的时候,父类原型对象的constructor指向哪里,子类原型对象的constructor就指向哪里哈(当父类原型对象没有指定constructor属性的时候,默认的constructor是指向Object的,所以才犯了刚刚的误解)!

var cat1 = new Cat();
cat1可以调用Cat.prototype中的变量,但不可以修改
“二、prototype模式”中
“原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。”
这时候的Cat.prototype指向了Animal实例,其本身就没有constructor属性了,Cat.prototype.constructor=Cat会在Cat.prototype中新增一个constructor属性。
看你的文章研究了半天,终于有点明白了,感谢^_^
要是能配上内存图解就更好了

引用youlong的发言:

function Animal(){}

Animal.prototype.species = "动物";
  
  function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat; 
  
  var cat1 = new Cat("大毛","黄色");
  alert(Cat.prototype.constructor == Animal); //为什么我这里是false ???

你就Cat.prototype = new Animal()完了之后再alert,就true了,你没读懂阮哥的意思。

引用JS的发言:

第一种:构造函数绑定,即用call(apply)把父对象的this指向改为子对象
缺点:不能继承原型上的属性和方法;

第二种:prototype模式,即把子对象的prototype对象指向Animal的一个实例;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
注意:当改了prototype对象的constructor时,记得改回来,否则将造成继承链紊乱;

第三种:直接继承prototype,即child.prototype = parent.prototype;
优点:相比第二种效率更高,比较省内存;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
且子对象的prototype对象修改后父对象的prototype也会被修改;

第四种:利用空对象作为中介,第三种的升级版;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
且子对象的prototype对象修改后父对象的prototype也会被修改;

第五种:拷贝继承
缺点:只能继承原型上的属性和方法;
优点:如果子对象的prototype对象上有属性或方法时,不会被清除,
且子对象的prototype对象修改后父对象的prototype不会被修改;

总结:继承加在原型上的属性和方法时用第五种,而继承写在构造函数里的属性和方法则用第一种,两则结合用

第四种方法子对象的prototype修改后,中介F的prototype会修改,父对象不会。

引用cocoa的发言:

var cat1 = new Cat();
cat1可以调用Cat.prototype中的变量,但不可以修改
“二、prototype模式”中
“原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。”
这时候的Cat.prototype指向了Animal实例,其本身就没有constructor属性了,Cat.prototype.constructor=Cat会在Cat.prototype中新增一个constructor属性。
看你的文章研究了半天,终于有点明白了,感谢^_^
要是能配上内存图解就更好了

没加上Cat.prototype.constructor=Cat这句,console.log(cat1),发现cat1是没有constructor属性的。

关于第二点,不同FF版本表现不一致。指定Cat.prototype = new Animal();,然后直接var cat1 = new Cat("大毛","黄色") 后,Firebug一下console.log(cat1.constructor) ,有的版本是undefined(xp系统24或25,具体忘了),有的指向Animal(),现在win7下FF25。

总的来说,指定Cat.prototype.constructor = Cat;会很好的避免这种状况。

第不知几次看这个文章,以自己现在的对JS的了解,想请教阮老师几个问题:
第三个和第四个通过原型继承的问题我觉得还是有些bug,也不能说bug,应该说不够完美,举个例子:第四个问题的通过空对象继承,我写了例子:
function Cat(){
this.testA = "cat1";
}
function Dog(){
this.testB = "Dog B";
}
Dog.prototype.test = function(){
console.log("test Dog");
}
var F = function(){};

F.prototype = Dog.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
cat1.test();
console.log(cat1.testB);//undefined

-------------------------------------------------
阮老师上面第三和第四个继承方法,确实是都继承了原型里面的方法,但是无法继承对象自身的属性,例如上面的Dog.testB这个属性我们就获取不到。

但是如果改成原型直接继承实例的情况就可以达到完全继承了,例子:
Cat.prototype = new Dog();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
cat1.test();//test Dog
console.log(cat1.testB);//Dog B

通过这个方法继承,不止继承Dog原型方法,还继承了对象自身的属性和方法。

补充一下我们上面的问题,虽然通过我上面的方法继承,但是这个方法还是有缺点的是当Cat继承了Dog后获取了所有的Dog的方法和属性,但是Cat如果自身如果有属性和方法的话,就会被全部干掉,这种继承并不够完美,只能针对空对象继承,最好的方法还是阮老师写的最后那个拷贝继承,但是老师写的那个我觉得还是有缺点,缺点依旧是通过原型继承无法继承对象本身属性,只能继承prototype扩展的属性和方法,所以我稍作修改下:
function extend2(Child, Parent) {
var p = new Parent;
var c = Child.prototype;

for (var i in p) {
c[i] = p[i];
}
c.constructor = Child;
c.uber = p;
}

第四种方法,Cat.prototype.type 和 Cat.prototype.eat 会丢失,因此结果中虽然可以得到Animal的species ,但Cat对象的type和eat都为undefined。

function extend(Child, Parent) {
var F = function () { };
F.prototype = Parent.prototype; //如果改成Child.prototype,Animal的species就会被干掉
Child.prototype = new F(); //Child.prototype被指向F,自己的type和eat属性被干掉了
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}

这样修改后,type和eat就不会丢失:
function extend(Child, Parent) {
var F = function () { };
var c = Child.prototype; //将Child.prototype暂存到变量c
F.prototype = Parent.prototype;
Child.prototype = new F(); //重新开辟内存,Child.prototype改变不会影响c
Child.prototype.constructor = Child;
//遍历c,添加原来Child.prototype的属性
for (var i in c) {
console.debug('=>%s,%s,%s', i, Child.prototype[i], c[i]);
Child.prototype[i] = c[i];
}
Child.uber = Parent.prototype;
}

言简意骇,很是形象,对继承的理解顿时豁然开朗了

二、 prototype模式
原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal
按照这种说法第二行应该是:Cat.prototype.constructor = Animal;?

第四个,利用空对象作为中介,new F()之后Animal的contructor就丢失了

请问 Child.uber 如何使用?我测试的时候是undefined

写的非常棒,还有博客写的非常好~~大赞

第四种 利用空对象作为中介中的
F.prototype = Parent.prototype;
是有问题的,alert(cat1.species);弹出的是undefined
应该改为F.prototype = new Parent();吧?

看你的文章总是能学到很多东西,谢谢分享。

通俗易懂,非常好

引用zhangyq的发言:

其实不然。你这样写Cat.constructor = Cat;是不对的。应该Cat.prototype.constructor = Cat;你再尝试的理解理解第二、第四种写法。应该能理解通吧?

子对象的prototype更改后,父对象的prototype不变

引用youlong的发言:

function Animal(){}

Animal.prototype.species = "动物";
  
  function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat; 
  
  var cat1 = new Cat("大毛","黄色");
  alert(Cat.prototype.constructor == Animal); //为什么我这里是false ???

  Cat.prototype.constructor = Cat;
是否应该为  Cat.prototype.constructor = Animal;

貌似如果不加上 Cat.prototype.constructor=Cat;
Cat实例的constructor是指向Object????

  var F = function(){};
  F.prototype = Animal.prototype;
  Cat.prototype = new F();
  Cat.prototype.constructor = Cat;

这样跟Cat.prototype = new Animal()有什么区别,F这个中间对象感觉有点多余。

关于Js继承,我原来不是很懂,后来看了博主的文章,看了些资料,自己整理了一下,不知道对不对,请各位大神指点一下,谢谢啦~~

http://www.coolwubo.com/work/55649165e68f14d12eb02bda

阮先生您好,我是一名正在学习JavaScript的高校生,很感谢您能写出如此通俗易懂的博客。让我们这些小白受益匪浅。
但是,我在看的过程中发现一些自己不能理解的问题。也具体做过一些测试,测试过程中,发现测试结果与您的结论有些出入,想请教一下您:
1、阮先生您在“prototype模式”中说:“每个实例都有自己的一个constructor属性,默认是调用prototype的属性。”所以我进行了一下这个测试:
var cat = new Cat();
console.info( "constructor" in cat );//true
console.info( cat.hasOwnProperty("constructor") );//false

这表明constructor属性是存在的,但只是属于prototype,并不属于cat本身。那“每个实例都有自己的一个constructor属性”该怎么理解呢?


2、在“利用空对象作为中介”中,Cat.prototype = new F(),然后Cat.prototype.constructor = Cat。这里的constructor是属于谁的呢?是属于实例对象F的吗?
我曾试着这样去理解:假设实例中并没有constructor属性,Cat.prototype.constructor相当于F.constructor,但是F是实例,并没有constructor,那么就到F的原型中去找,F的原型就是Animal的原型,那样说白了修改Cat.prototype.constructor还是等于修改Animal.constructor。这就跟实际相违背了。
但是假设实例中有constructor属性,那么实例F中就有constructor,第一个疑问中所做的测试为什么又反应实例中不存在constructor属性呢?

引用JS的发言:
第四种:利用空对象作为中介,第三种的升级版; 缺点:如果子对象的prototype对象上有属性或方法时,将被清除; 且子对象的prototype对象修改后父对象的prototype也会被修改;

第四种,子对象的prototype属性改变,父对象的prototype属性并不会被改变,亲测:
function Animal() {
}
Animal.prototype.species = "Animal";

function Cat() {
this.name = "Cat";
}

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();

console.log(cat1.species);
Cat.prototype.species = "Small tiger";
var animal1 = new Animal();
console.log(animal1.species);
console.log(message);

javascript太散乱啦,需要来规整一下

四、 利用空对象作为中介
这个方法的好处:文章说是F是空对象,所以几乎不占内存。但是Cat.prototype = new F();这句还不是创建F()的实例,那和二、 prototype模式相比感觉没什么优势呀

是因为F对象只有Animal的原型对象,而Animal的对象作为构造函数时,有的属性更多,这样Animal的实例还是比F的实例多占一些内存?

引用翰弟的发言:

是因为F对象只有Animal的原型对象,而Animal的对象作为构造函数时,有的属性更多,这样Animal的实例还是比F的实例多占一些内存?

忘了补充,把方法2的Animal对象改成
function Animal(){ }
  Animal.prototype.species = "动物";

四、 利用空对象作为中介,有点幌子的意思,就这一操作Cat.prototype=new Animal();和var F=function(){},定义空对象,把F.prototype=Animal.prototype;Cat.prototype=new F();看着两次方法,哪一个更好一些?
F是作为介质了,但是并没有解决直接继承prototype的问题,只是通过让Cat.prototype指向对象prototype有species的实例做到的。

Cat.prototype = new Animal()

cat1 = new Cat('das','das') >>

Cat {name: "das", color: "das"}color: "das"name: "das"
__proto__: Objectspecies: "动物"__proto__: Animal

cat1.constructor >> Animal()

不设置Cat.prototype.constructor = Cat 为什么构造cat1的时候还是会调用Cat函数,按这里说的不是应该调用Animal()么?

我对最后一个继承方法有疑问,就是extend2继承的时候没有吧Parent.prototype里面的constructor给赋值过去?是因为constructor不可遍历么?

Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal
作者好像说错了哦


应该是
Cat.prototype.constructor是指向Animal的;
加了这一行(Cat.prototype.constructor = Cat;
)以后,Cat.prototype.constructor指向Cat

引用jaychang的发言:

Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal
作者好像说错了哦


应该是
Cat.prototype.constructor是指向Animal的;
加了这一行(Cat.prototype.constructor = Cat;
)以后,Cat.prototype.constructor指向Cat


作者没有说错,是你理解错了。你仔细读两遍,这句话“如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。”
是如果没有,如果没有,如果没有"Cat.prototype = new Animal();"这一行。我来解释一下,因为Cat的prototype并没有继承Animal的任何属性。因此Cat.prototype.constructor当谈还是指向Cat了。现在有了Cat.prototype = new Animal()这一行以后,Cat.prototype.constructor就会指向Animal了,作者说的没错的。你明白了吗?

第一种方法和第二种方法可以结合使用,利用apply()继承属性,利用原型链继承方法。这样比较好。

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;
  Child.uber = Parent.prototype;
}
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
问题:并没有提示cat1.species,哪里错了?

引用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

你仔细看看extend的实现,Child原来的protyoType被覆盖了,所以肯定找不到print2啊

引用Keivn的发言:

貌似如果不加上 Cat.prototype.constructor=Cat;
Cat实例的constructor是指向Object????

不是 是Animal

引用RedNax的发言:

受教了。
第5种方法确实不错,构造函数应该是(根据参数)为实例添加特定成员而存在的。new出来作为prototype的话,父类的构造函数就已经跑过一次了,结果子类构造的时候如果必要还要在跑一次,就显得浪费了。
不过debug和用代码回朔的时候可能会比较麻烦,两个prototype中间会隔着一个空类。

第5种方法,如果原型的属性是类似'大黄','小花'这样的字符串,拷贝自然没事。
但是,如果属性是对象,这样拷贝是否会有遗留问题?因为JavaScript中的对象是引用的。

二、 prototype模式
Cat.prototype.constructor = Cat;
应该是
Cat.prototype.constructor = Animal;
否则这里是不可能为true的
alert(Cat.prototype.constructor == Animal); //true

其实用二 、三、四方法时。可在
Cat.prototype = new Animal();
后再加上子类自己的原型属性。这样子类的原型属性就不会被清除。

引用Loliner的发言:

1、阮先生您在“prototype模式”中说:“每个实例都有自己的一个constructor属性,默认是调用prototype的属性。”所以我进行了一下这个测试:
var cat = new Cat();
console.info( "constructor" in cat );//true
console.info( cat.hasOwnProperty("constructor") );//false

这表明constructor属性是存在的,但只是属于prototype,并不属于cat本身。那“每个实例都有自己的一个constructor属性”该怎么理解呢?

关于第一个问题,我的理解是实例上的constructor属性是继承自构造器的prototype.constructor,所以有cat.constructor= = =Cat.prototype.constructor为true,关于第二个问题我没看明白你说的什么,不知道我的理解对不对

@阮一峰,第二个方式和第四个方式的区别,我的理解是:
第二个方式:

function Animal() {
this.species = '动物';
}
Animal.prototype.say = function () {
console.log('hello');
}
function Cat(name, color) {
this.name = name;
this.color = color;
};

Cat.prototype=new Animal();
Cat.prototype.constructor=Cat;

var cat1=new Cat();

这种方式cat1继承了Animal自身的属性species和prototype上的属性和方法;

第四个方式:

function Animal2() {
this.species = '动物';
}
Animal2.prototype.say = function () {
console.log('hello');
}
function Cat2(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;
Child.uber = Parent.prototype;
};

Extend(Cat2, Animal2);

var cat2=new Cat2();

这种方式cat2只继承了Animal的prototype上的属性和方法;

所以两者的区别是会不会继承Animal自身的属性和方法,
其他的区别貌似没有了
我的理解不知道对不对,?

但我奇怪的是第四种方式中的Cat2.uber是指向Animal2.prototype的,
Cat2.hasOwnProperty('uber')也是true,这说明Cat2是拥有uber属性的
但是为什么new Cat2()生成的cat2没有uber属性呢?

空对象做中介的实质是,对构造函数的实例的constructor进行改变时,不会改变原构造函数的constructor,也就是说,我们想让一个构造函数A来继承另一个构造函数B的属性时,只需要创一个B的实例,然后用构造函数A来继承B的实例,再改变构造函数A的constructor即可。比如:
  function Animal(){ }
  Animal.prototype.species = "动物";
  function Cat(name,color){
    this.name = name;
    this.color = color;
  }
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黄色");
console.log(Animal.prototype.constructor == Animal)
console.log(Cat.prototype.constructor == Cat)
console.log(cat1.species);

封装函数:

function extend(Child, Parent) {
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }

阮老师您好,你在文中提到:



更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。



  alert(cat1.constructor == Cat.prototype.constructor); // true


我认为“每一个实例也有一个constructor属性”这个说法是有问题的,事实上只有构造函数本身有prototype.constructor属性,通过构造函数new生成的实例本身是没有这个属性的,之所以alert(cat1.constructor == Cat.prototype.constructor)的结果为true,是因为cat1.__proto__指向了Cat.prototype,当访问cat1.constructor,在本地属性中找不到constructor,因此对原型链上的属性进行搜索,并最终通过cat1.__proto__.constructor访问到了Cat.prototype.constructor。

引用youlong的发言:

function Animal(){}

Animal.prototype.species = "动物";
  
  function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat; 
  
  var cat1 = new Cat("大毛","黄色");
  alert(Cat.prototype.constructor == Animal); //为什么我这里是false ???

作者在這裡犯了點小錯誤,也許是“typo”吧。 實際上“Cat.prototype = new Animal();” 使得Cat.prototype的constructor就已經是“Animal"了.

我这里有一个疑问。这样的继承只能继承父构造函数.prototype的属性和方法,那父构造函数函数体里定义的属性是不是继承不了啊。
比如父构造函数
function (name){
this.name=name;
}
那么这个name不体现在父构造函数的prototype里,上述的所有继承方法子对象都没有这个name属性了???

就是方法一二是可以继承构造函数中定义的普通属性,方法三开始只能继承共享属性以及方法了。对吧?

感觉最后一种方法(拷贝属性)和第二种方法(prototype)类似,继承后都会新建子对象的prototype,会暂用内存,一般不会用到最后一种方法吧?

第二种方法是不是有问题,实例化的cat的具有属性constructor并指向构造函数,而构造函数Cat的原型怎么会有constructor这个属性呢?是不是写错了!

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接