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); // 动物

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

(完)

留言(147条)

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

我还是不知道为什么要做
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这个属性呢?是不是写错了!

第四种利用空对象作中介的方法封装成extend函数的第三行应该改为 F.prototype = new Animal();不然得到的值为undefinded,不知道我这样说对不对

引用zhaorui的发言:

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

第二种方式有缺陷,会覆盖父类的prototype

引用this的发言:

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,哪里错了?

仔细看函数里面调用的是Parent.propertype啊,你要设置Animal.propertype.species = "动物" 才行啊,这个才是公有属性

在刚开始的地方,prototype模式的地方,
Cat.prototype = new Animal();
这个时候 它的constructor不是已经指向了Animal吗?
为什么还要给他加一个Cat.prototype.constructor = Cat?
然后你下面说这样才会指向Animal?

原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。

这段最后一个Animal和Cat位置是不是放反了?

谢谢老师的文章,对我这种初学者帮助很大。

最后一种方法类似于浅拷贝吧,prototype的属性等于数组或另一个对象的时候是否也存在被篡改的可能?

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

------
这句不敢苟同。
------

实例是没有 constructor 属性的(可以查看控制台输出)。

实例其实是构造函数的this对象的返回值。而this对象只有一个指向函数原型的对象的引用(__proto__ 属性),其它没有。constructor 属性是来自 __proto__ 属性的。

@Zhuangyh: 认同。

为什么第四条我使用你的源代码弹出的undefine

第三点直接继承那里:alert(Animal.prototype.constructor) 弹出的是Animal而不是Cat

第四种继承方法:添加个中介对象,相比第二种方法有什么优势呢?
而且第四种方法增加了作用域链的长度

引用杨冰的发言:

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

这篇文章是2010年的了,浏览器做了一些改进,不一样了

引用ifredom的发言:

这篇文章是2010年的了,浏览器做了一些改进,不一样了

不是还有吗?写一下测试一下不就知道了

第一种:只能继承构造器里的属性和方法
第二种:既能继承构造器里的属性和方法,也能继承原型上的属性和方法
第三种:只能继承原型上的属性和方法
第四种:只能继承原型上的属性和方法
第五种:只能继承原型上的属性和方法

@RedNax:

"构造函数应该是(根据参数)为实例添加特定成员而存在的。new出来作为prototype的话,父类的构造函数就已经跑过一次了,结果子类构造的时候还要在跑一次,就显得浪费了"
上面说的不是第五种方法吧?我怎么觉得你是在说第二种方法,而且第二种方法中子类构造的时候父类构造也只需跑一遍啊

@dong2590:

这样实现也可以,类似于prototype的实现方式(第二种),既可以继承构造器的属性和方法,也可以继承原型上的属性和方法.但是它比第二种差的地方在于:继承过来的属性和方法(无论是从构造器继承还是从原型上继承过来的)比第二种嵌套的要深,这就意味着遍历的时间要长.

空对象作为中介(第四种)中的extend方法中Child.uber = Parent.prototype,这里var cat = new Cat()出来的cat对象并取不到uber的值,因为它不像第五种中的extend那样,把Parent.prototype赋给Child.prototype.uber

@Jun:

只能继承原型上的属性和方法,构造器里的属性和方法是继承不到的,将function Animal() {this.species = '动物'} 改成function Animal() {} ;Animal.prototype.species = '动物'就可以了

 第二种方法中
alert(cat1.constructor == Cat.prototype.constructor); // true

我的控制台输出的却是false,峰峰大神解读一下哈!是不是你写错了呢

  alert(cat1.constructor == Animal); // true
那这个更谈不上输出true了吧,实例对象的constructor仍然指向构造函数,并没有因为构造函数的原型对象的constructor的指针指向Animal而发生改变,希望峰峰大神给解读一下哈!!!

@Bubble Andy:

建议你去看看原形链是怎么一回事,就不会提这个问题了

o.prototype.constructor = o;
在我的理解,第二个o应该是大写吧?

引用abusimbely的发言:

o.prototype.constructor = o;
在我的理解,第二个o应该是大写吧?

并不是 这俩o是要一样的 前面的o是啥后面的也要是啥

第4哥=个方法有问题,报的undefined.extend函数没有返回值

这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。
Cat、Animal写反了

为什么没有分页?我还以为这篇文章很长,原来评论占了一大页

有个疑问,这样Cat.prototype对象里面本来有属性,比如Cat.prototype.voice ="喵喵喵",为了继承Animal,Cat.prototype对象被赋给了一个新对象Animal.prototype,它是继承了Animal的原型方法,但是自己的原型方法不就没了么?

引用我思故我在、、、的发言:

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


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

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

因为这个 Person.prototype={} 做这一步的时候,是生成了一个实例对象的,Person.prototype.constructor也就指向了这个生成的对象,所以要constructor: Person,把指针调回来。

第5中,会覆盖子对象的原型吧??

看你的文章总能让人恍然大悟,茅塞顿开,请容我叫一声阮老师

引用noBar的发言:

第5中,会覆盖子对象的原型吧??

并不会. 亲测

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

为什么它会指向构造函数Animal,我说说我的见解
alert(cat1.constructor == Animal); // true


1:【cat1是一个对象,查找对象的属性会先在对象本身查找】
2:cat1它有没有添加constructor属性-------没有
3:那到cat1的一个隐形__proto__属性查找,cat1.__proto__这个属性指向构造cat1这个对象,的构造函数的原型,就是Cat.prototype,Cat的prototype属性是个指针,指向了一个对象,本来指向原型对象
【结果Cat.prototype=new Animal-----Cat的prototype属性指向new Animal了】
4:new Animal是一个对象,又有自己的属性,先在new Animal本地查找有没有constructor属性-----没有
5:那到new Animal的一个隐形__proto__属性查找,指向构造new Animal这个对象,的构造函数的原型,就是Animal.prototype,Animal的prototype属性是个指针,指向了一个对象,指向原型对象

6:既然指向原型对象,原型对象自带一个constructor属性----【那找到了】

7:找到的是Animal.prototype.constructor是Animal

所以cat1.constructor == Animal,

Cat.prototype.constructor==Animal也是上面那样找【Cat.prototype】Cat的prototype属性是个指针,指向了一个对象,本来指向原型对象
【结果Cat.prototype=new Animal-----Cat的prototype属性指向new Animal了】---------到5:6:

@元走高飞:

这里应该是笔误。。那个哥们没理解错

感觉还是第一种构造函数绑定的方法比较轻松,但是违反了封装性,直接把父对象的属性也继承过来了

感觉面试前需要全部背一遍啊

一个小小问题~
extend 引入 F做媒介后
Animal.prototype.aaa = ... ;
extend(Cat,Animal);
extend(Dog,Animal);
Cat.prototype.aaa 和 Dog.prototype.aaa 是不是 还是存在内存浪费 问题,
只不过 因为 是 函数原型 所以浪费不多~~~
暂时不知道如何判断 引用是否相同

如果 对于函数来说 ==号判断的就是 引用的话,那么我自己试了下是true
function a(){this.a="a";this.prototype.a={}};
function b(){};function c(){};
extend (b,a);extend (c,a);
b.prototype.a == c.prototype.a

所有都是文章都是反过来讲,只有阮一峰老师正过来讲。。

出视频教程吧。。文字看的真心累。。hiahiahiahia

引用Ruan YiFeng的发言:

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

不加 o.prototype.constructor = o; 这一句 instanceof 也会返回正确的结果呀!

引用闪电的发言:

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

我也看出来了,评论里已经有人给出答案了,递归

能不能将Cat.prototype = new Animal();改成Object.assign(Cat.prototype,new Animal())
这样不会改变constructor;

为什么好几个我跑出来都是undefined呢?qiuzh

感觉方法三直接继承父类prototype是为了解决方法二里面创建父类实例,节省内存提高性能才做的,结果方法四利用空对象作为中介的方法还再建立一个新的对象实例化岂不是用了更加多的内存?虽然解决了三中会更改父类原型的问题,但是导致方法二里面内存耗费的问题又出现了吧?

阮老师,我谷歌输出实例,发现没有constructor,而是再__proto__里面,但是__proto__指向原型对象,是不是再实例中并没有constructor呢?

原型继承的方式不应该是使用这样的语法吗?为什么都是直接分配父类的实例作为子类的原型,然后重新修改原型的contructor。
Cat.prototype.prototype = Animal.prototype

根据我的了解,es6语法中extends语法的实现也是我写的这种,子类原型的原型是父类的原型。
不知道为什么大多数教程里都没这么写,是有什么其他问题吗?

上面写错了,是 Cat.prototype.__proto__ = Animal.prototype

引用leeli的发言:

为什么好几个我跑出来都是undefined呢?qiuzh

要在parent.prototype设置公用属性,这样子类才能访问到父类。

大家请注意第四种方法
即使用一个已经替换prototype的空对象f的实例来作为子类的prototype的时候,你此时更新子类的prototype.constructor会发现你也更换了f的constructor,但是由于此时f仅作为中间对象使用所以并无大碍,这也是较为安全的一种继承方法。

我要发表看法

«-必填

«-必填,不公开

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