一个关于原型链继承的基础问题

最近在研究javascript原型链继承这一块碰到一个细节问题,引起了我的好奇,但是一直想不明白是为什么,请求大神解答:

 function Shape(){
  this.name='shape';
  this.toString=function(){
      return this.name;
  }
 }

 function TwoDShape(){
     this.name='2dshape'
 }

 function Triangle(side,height){
     this.name='triangle';
     this.side=side;
     this.height=height;
     this.getArea=function(){
         return this.side*this.height/2;
     }
 }

 Triangle.prototype=new TwoDShape();
 TwoDShape.prototype=new Shape();

 Triangle.prototype.constructor=Triangle;
 TwoDShape.prototype.constructor=TwoDShape;

如上段代码所示,我建立了一个继承链,从Shape到TwoDShape,再由TwoDShape到Triangle,当我再执行以下代码时:

var shape1=new Triangle(10,9);
console.log(TwoDShape.prototype.isPrototypeOf(shape1));//输出false

程序输出false,我的理解,是isPrototypeOf寻找的是shape1最直接的那个原型,即Triangle.prototype,而这里TwoDShape.prototype不是其直接原型,所以输出了false;

然鹅,我在《javascript面向对象编程指南》这本书上看到的实践则恰恰相反:

书上在实现继承时是这样做的:

function Shape(){}

Shape.prototype.name='shape';
Shape.prototype.toString=function(){
    return this.name;
}

function TwoDShape(){};

TwoDShape.prototype=new Shape();
TwoDShape.prototype.constructor=TwoDShape;

TwoDShape.prototype.name='2d shape';

function Triangle(side,height){
    this.side=side;
    this.height=height;
}

Triangle.prototype=new TwoDShape();
Triangle.prototype.constructor=Triangle;

Triangle.prototype.name='triangle';
Triangle.prototype.getArea=function(){
    return this.side*this.height/2;
}

在执行了这段代码后再console.log(TwoDShape.prototype.isPrototypeOf(shape1));此时则会输出true,我不明白的,是这两种继承方式,不都是通过原型链继承吗?为什么两者在这个点上的输出会不一样呢?

我还有个问题,就是在学习javascript时常常碰到这样理解起来很匪夷所思的问题,应该怎样更好的理解这门语言呢?

阅读 3.6k
5 个回答

关键在于__proto__或者叫[[Prototype]]和prototype是不一样的,prototype动态的改变不会改变__proto__或[[Prototype]]的改变。在new的时候实例中的__proto__已经固定
执行:Triangle.prototype=new TwoDShape()
重写了Triangle.prototype,和下面的constructor结合得到

constructor:function Triangle(side, height)
name:"2dshape"
__proto__:Object

而因为Triangle.prototype相当于TwoDShape的实例,于是Triangle.prototype的__proto__指向此时TwoDShape的原型对象。__proto__指向如下

constructor:function TwoDShape()
__proto__:Object
此时TwoDShape的__proto__指向Object

当你执行TwoDShape.prototype=new Shape()。TwoDShape.prototype的原型被重写(和下面的结合),为

constructor:function TwoDShape()
name:"shape"
toString:function ()
__proto__:Object
__proto__指向Object

而重要的是实例中的__proto__在new 的时候已经固定,你的第二个new不会影响Triangle.prototype.__proto__的指向。你可以自己打印下,实在难写。这样写就是正确的
console.log(Triangle.prototype.__proto__.isPrototypeOf(shape1)),
而他的原型链中压根就没有Shape什么事

而下面的是先把TwoDshape和Shape做了链接,再用Triangle和TwoDShape做链接,Triangle中的链条才会有Shape,所以返回true

顺便说下prototype只有函数才有此属性,而__proto__或[[Prototype]]每一个对象都有。
简单的说下关系如下
function A(){}
var a = new A()

a只有__proto__,A有prototype 和__proto__(他的这个不重要,自己console.log下)
关系如下:
a.__proto__  ----> Prototype(A的原型对象) <----  A.prototype
A的原型对象有有constructor指向A本身
A的__proto__指向Object

原理和下面的代码一样的

function A() {}
A.prototype.x = 10;
var a = new A();
console.log(a.x); //10
A.prototype = {
  constructor: A,
  y: 100
};  
var b = new A();
// b的__proto__指向A的新原型对象
console.log(b.x); // undefined
console.log(b.y); // 100
console.log(a.x); // 10 
console.log(a.y); //undefined
a的__proto__不会改变,还是原来的

一楼已经说得够多了,我就简介一点。你唯一的区别就是 Triangle.prototype=new TwoDShape();
TwoDShape.prototype=new Shape();这两句没有错误,但其中包含重写原型链的问题, 在TwoDShape.prototype=new Shape()这句之前,TwoDShape.prototype的原型对象是Object,而Triangle也是这时继承的TwoDShape。而TwoDShape.prototype=new Shape(),这句话之后,TwoDShape经历了一次重写原型链的过程,此时其原型对象由Object变为了shape;但这并不会改变Triangle的原型指向,其仍指向老的TwoDShape原型构造出来的对象;所以,所以TwoDShape.prototype.isPrototypeOf(shape1)打印false没有问题,因为shape1的构造函数Triangle的原型TwoDShape已经不再是哪个单纯的Object了,已不再单纯。大清早说这么绕的话,也是累

1楼写的很完善了,稍微补充一点,1楼最后举的例子出现这样的结果是因为prototype被覆盖掉了,如果你要给原型添加方法的时候需要注意写法要统一,以下2种写法不能混用,如果混用了就会被覆盖。

let A = () => {};

//写法一,如果还要添加新方法只能是 A.prototype.getName = () => {};
A.prototype.addName = () => {}

//写法二
A.prototype = {
    addName: () => {},
    getName: () =>{}
}

1.先分析你的代码

// 追溯 shape1 原型链

/* 由 */ var shape1 = new Triangle(10,9);
/* 得 */ shape1.__proto__ === Triangle.prototype;// true

// Triangle.prototype 的原型链
/* 由 */ Triangle.prototype = new TwoDShape();
/* 得 */ Triangle.prototype.__proto__ === TwoDShape.prototype;// 然而此时该表达式值为 false

// 因为在 Triangle.prototype = new TwoDShape(); 语句后执行了 TwoDShape.prototype = new Shape();
// 连起来看
Triangle.prototype.__proto__ === TwoDShape.prototype;
TwoDShape.prototype = new Shape();
// 于是
Triangle.prototype.__proto__ === TwoDShape.prototype;// false
// 所以
TwoDShape.prototype.isPrototypeOf(shape1);// false

注:若有对象赋值疑问,请参考下段代码

var a = { n: 1 }, b;
b = a;// b 代表 Triangle.prototype.__proto__
a = { n: 2 };// a 代表 TwoDShape.prototype
b === a;// false

2.分析原书代码
Triangle.prototype = new TwoDShape(); 该语句会先执行 new TwoDShape(),然后将返回值赋值给Triangle.prototype

// 可理解为先执行
TwoDShape.prototype = new Shape();
// 在执行
Triangle.prototype.__proto__ === TwoDShape.prototype;
// 所以
TwoDShape.prototype.isPrototypeOf(shape1);// true

关于更好地理解JS的问题,客观来说因人而异。建议上网多看看各方面的意见(比如sf,zhihu,cnblogs,jianshu...),多尝试来寻找适合自己的方法。
其实发现问题就是更好理解JS的一个有效途径。再就推荐一下MDN吧。

推荐问题