Javascript的类与继承

154 阅读4分钟

类 / 继承描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域的建模
方法 --《你不知道的JavaScript》

1.面向对象程序设计思想

        要了解JavaScript的类与继承,首先我们需要简单了解下面向对象的程序设计思想。面向对象程序设计的思想强调的是将数据和操作数据的方式进行打包,也就是将类的属性和方法进行封装形成的一种数据结构。面向对象的三大基本特征是:封装、继承和多态。

        封装就是隐藏对象属性和方法的实现细节,仅暴露对外接口内容,控制属性的读写级别。继承就是子类继承父类的属性和行为。多态是指允许子类重写了父类的方法。

       总而言之,面向对象是软件开发设计中的一种模式,与之相对的其他模式还有面向过程化编程和函数式编程等。像Java中面向对象是一种必须的模式,万物皆是对象,而C++中你可以选择使用面向对象或面向过程编程,或者混用。而JavaScript中本身并没有类(ES6中新增了class语法糖,但并不是实现了类),但我们前面提到,类本身是一种设计模式,所以我们可以通过其它方法来模拟类的特性,提供近似的语法。

2.Javascript中的原型链

        与Java的继承是对父类的进行拷贝生成副本不同,Javascript通过一个叫做原型(prototype)链的方式来模拟类的继承。相信读到这篇文章的人都会对原型链有一定的了解,这里不详细介绍。简单说下,每个JavaScript对象都有一个[[prototype]]属性,这个属性将两个对象关联起来,当访问一个对象的属性没有找到时就会访问对象的prototype链,直到尽头为止。如何用原型链模拟继承可以用下图做简单描述:

3.Javascript中的类与实例

        类和实例的关系本文不再赘述,可以看到在Javascript中定义一个类,其实是利用每个函数都有一个prototype对象这一特性,定义了一个模拟类。由于在Javascript中没有类,所以模拟类的方式其实多种多样,举一个一般的类的定义方式:

function Foo(name) {
  this.name = name
}

Foo.prototype.sayName = function() {
  console.log('I am ' + this.name);
}

var a = new Foo('a');
var b = new Foo('b');
a.sayName(); // I am a
b.sayName(); // I am b

这样,当我们实例化一个类时,可以给实例添加属性值,同时两个实例a,b能够通过原型链的机制访问到sayName方法。这里我们可以简单模拟下new关键字的作用,模拟一个实例化方法:

function objectFactory(constructor, args) {
  var obj = new Object();
  obj.__proto__ = constructor.prototype;
  var ret = constructor.apply(obj, args);
  return typeof ret == 'object' ? ret : obj;
}

4.Javascript中的继承

关于继承,在这里我们参考《Javascript高级程序设计》举几个例子,并简单介绍下优缺点:

function Parent () {
    this.name = 'jack';
}

Parent.prototype.getName = function () {
    console.log(this.name);
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName()) //jack

这种继承方式成为原型链继承,该方式缺点就是所有子类的实例会共享父类的属性。

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {
    Parent.call(this);
}

这种继承方式称为构造函数继承,这种继承的缺点就是所有方法实例化的时候都会创建一个实例。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {

    Parent.call(this, name);
    
    this.age = age;

}

Child.prototype = new Parent();

这种继承方式叫做组合继承,该方式结合了上述两种继承方式的优点,缺点是由于改变了原型链,没法通过instanceof和isPrototypeOf来判断继承关系。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child() {
}

function factory(parent, child) {
  //首先我们要创建child的原型对象,并将该对象的__proto__指向parent.prototype,
  function Prototype() {}
  Prototype.prototype = parent.prototype;
  var _prototype = new Prototype;

  //将这个原型对象和子类关联
  child.prototype = _prototype;
  _prototype.constructor = _prototype;
}

这就是比较经典的组合寄生继承,思路参考之前的继承图,我们先创建Child类的原型对象。创建这个对象时,我们是希望将该原型对象的__prototype__指向Father的prototype,等同于使用

var _protptype = Object.create(parent.protoType);

最后我们将Child类和我们创建的原型对象通过prototype和constructor链接起来,完美的复现了我们的继承图,并且可以正常使用instanceof和isPrototypeOf,是比较公认的较为完善的继承方式