拥抱ES6,再谈ES5原型继承

初衷

最近,ES6被多次曝光在JS界内,ES6有很多新特性,这让笔者产生了学习的欲望,因为ES6是下一代的JavaScript,感觉会在2年之内迅速普及起来,因此笔者打算开始进行ES6的学习,那么先学哪个特性比较重要呢?

其实在任何系统或者业务的设计中,代码层面的设计都离不开面向对象和设计模式,因此类和继承就是首当其冲需要研究的范畴,ES6提供了简单的class关键字,可以让我们很轻松的定义一个类:

//定义类
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '('+this.x+', '+this.y+')';
  }

}

那么问题来了,笔者似乎从来没仔细研究过ES5是如何实现类和继承的,好像仅仅是知道原型继承这么一个概念,知道prototype这么一个关键字,其他深入的都没有针对性的进行理解,那么OK,今天笔者就沉下心来进行学习,再次回顾一下JavaScript的原型继承,为拥抱ES6做好充分的准备。

ES5中的JS类

在ES5中,是不存在类这个概念的,如果你想要在ES5中定义一个类,往往是这样的:

function People(){}

以上,就定义了一个People类,当你想要实例化一个People对象时,你需要进行new操作:

var p = new People();

此时,你就得到了一个People的实例p,到这里并没有什么问题。假如每一个People实例都必须具备一个属性role,然后role的值为user,其实是有两种方案的。

第一种:

function People() {
   this.role = 'user';
}
var p = new People();
p.role; // user

第二种,也就是在People的原型上添加role属性,笔者发现以下两种在原型上定义属性的效果是一样的,当然这是在最新版的chrome下测试的,其他浏览器暂不关注:

People.prototype = {
  role : 'user'
};

People.prototype.role = 'user';

var p = new People();
p.role; // user

实际上以上两种做法是有大区别的,如果采用

People.prototype = {
  role : 'user'
};

那么

var p = new People();
p.constructor == People // false, 此时 p 的constructor 为 Object

关于 constructor 有一篇资料:http://javascript.info/tutorial/constructor

ES5中的继承

那么问题来了,我想要实现一个Male类,它继承自People类,因此People类的所有属性,它必须都要有,而且它还可以拥有自身的特有属性。 想到这里,就有一个关键的要点了,如何实现类的继承,JS的继承是基于原型(prototype)的,那换句话说,只有原型上的属性才能够被继承,因此实现类继承就采用了以下方式,但笔者发现,有两种定义方式都可以,目前还没有得到答案,此外我已经在网络上提出了问题:

http://segmentfault.com/q/1010000002878590

http://stackoverflow.com/questions/30657494/whats-the-difference-between-the-two-js-prototype-inherintence

function Male() {}
Male.prototype = new People();  // 这是方式一
Male.prototype = People.prototype; // 这是方式二

var m = new Male();
m.role; // 输出 user

至此,我们就实现了一个基本的原型继承,一个Male类继承自People类,假如我们用下面这种方式来实现,role属性是无法被继承的。

function People(){
   this.role = 'user';
}

function Male() {}
Male.prototype = People.prototype;

var m = new Male();

m.role // 输出 undefined

但是如果采用此种写法,role又会被成功定义。

Male.prototype = new People();
var m = new Male();
m.role // 输出 user

在网上问了Male.prototype = People.prototypeMale.prototype=new People() 的区别,有一个答主说,原型链条不一样,但是在业务模块实现的使用上,没有任何区别。

拨乱反正

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

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

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

关于原型和原型继承,这篇文章特别好。