首页 > 我想知道这两种JS原型继承的定义方式的区别

我想知道这两种JS原型继承的定义方式的区别

今天想再深入理解一下原型继承,发现以下这两种继承方式都没什么问题,请问大牛们,这两种方式有区别吗,区别是啥?

function People(){}
People.prototype = {role : 'user'}
function Male(){}
Male.prototype = People.prototype;
var m = new Male()
m.role // 输出 user
m instanceof People // 输出 true
function People(){}
People.prototype = {role : 'user'}
function Male(){}
Male.prototype = new People(); // 这里跟上面定义方式不同
var m = new Male()
m.role // 输出 user
m instanceof Male

经我思考,Male.prototype = People.prototype 的方式不可用,因为Male继承自People,但是Male还需要具备自身所拥有的属性,如果直接使用 Male.prototype = People.prototype 的方式,未来会污染到父类原型链,比如我给 Male.prototype.role = 'male user',那么所有的People.prototype都被污染了!

因为 Male.prototype 是一个原型引用,改变引用类型,所有的引用都会改变。

因此,Male.prototype = new People() 才是正统!


一个网友建议使用:Male.prototype = Object.create(People.prototype) 我觉得也是赞赞的。


JavaScript的继承是基于原型链而非“类”,其所谓继承不过是在原型链中查找其本身不存在的属性而已。
目前最有效的设计,是在子类型中调用父类型构造函数,同时指定子类型的prototype属性为一个以父类型的prototype为原型的对象,这样可以构建合理的原型链。
还是拿传统的动物来举例:

function Animal(name) {
    if (!(this instanceof Animal)) {
        //防止没有使用new调用此函数时污染全局环境
        return new Animal(name);
    }
    this.name = name;
}
Animal.prototype.type = 'Animal';
Animal.prototype.run = function () {
    console.log(
        this.type + ' ' + this.name +  ' is running!'
    );
};

如上我们建立了一个Animal类型,其name属性由参数name确定,其实例的原型中的type属性由Animal.prototype.type来确定。

var animalJoe = new Animal('Joe');
animalJoe.run(); // Animal Joe is running!

下面将建立一个Dog类型,继承自Animal:

function Dog(name, color) {
    if (!(this instanceof Dog)) {
        return new Dog(name, color);
    }
    Animal.call(this, name); //调用父类型构造函数初始化子类型的实例
    this.color = color;
}
Dog.prototype = Object.create(Animal.prototype); //Dog.prototype的原型即Animal.prototype
Dog.prototype.constructor = Dog; //指定构造函数,方便以后使用
console.log(Dog.prototype.__proto__ === Animal.prototype); //true
Dog.prototype.type = 'Dog'; //覆盖了父类型原型中的type属性

var dogJack = new Dog('Jack');
dogJack.run(); //Dog Jack is running!
console.log(dogJack instanceof Animal); //true
console.log(dogJack instanceof Dog); //true
console.log(dogJack.__proto__ === Dog.prototype); //true
console.log(dogJack.__proto__.__proto__ === Animal.prototype); //true

为方便继承可以采用如下函数:

function extend(Pt, St, np) {
    //指定原型
    St.prototype = Object.create(Pt.prototype);
    St.prototype.constructor = St;
    //扩展新的原型
    for (var i in np) {
        St.prototype[i] = np[i];
    }
}

可以参考下ruanyf的http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inhe...


有区别!

原形链不一样。第一个的原型链是:m->People.prototype,第二个的原型链是:m->{// People实例}->People.prototype

提问者提问:这两种写法除了原型链条上的区别外,如果在运用到实际的工作中,会不会遇到坑?

会!!第一种比较可能遇到坑!看代码:

javascript// 假设全局存有 EventProxy 构造函数
// 我想让继承 People 的所有实例具有 EventProxy 的事件方法,如:
...
var m = new Male();

m.on('beat', function () {
  console.log('好疼');
});

m.emit('beat'); // =>'好疼'

第一种继承实现:

javascriptfunction People () {
  EventProxy.call(this);
}

function Male () {}

Male.prototype = People.prototype;

var m = new Male()

m.on('beat', function () {
  console.log('好疼');
}); // 报错!!!,不存有on方法

第二种继承实现:

javascriptfunction People () {
  EventProxy.call(this);
}

function Male () {}

Male.prototype = new People();

var m = new Male()

m.on('beat', function () {
  console.log('好疼');
}); // 不报错,正常

当然对于第一种继承实现,也是有方法避免报错的:

javascriptfunction People () {
  EventProxy.call(this);
}

function Male () {
  People.apply(this, arguments);
}

Male.prototype = People.prototype;

var m = new Male()

m.on('beat', function () {
  console.log('好疼');
}); // 不报错,正常

总结:在javascript的原型继承的世界里,这是十分灵活自由的,所以当然也会存有很多很多的坑。对于很多初学者或小白(比如我)是很难明白那种方法比较正统,也很难明白那种方法比较正确,但这不重要,毕竟这些‘哲理’上的东西需要经验的积累,但至少需要知道当发生报错时,究竟是什么错并如何解决!

PS:对于第二种继承实现和对第一种继承实现的完善同样是有区别的。

【热门文章】
【热门文章】