WdBly Blog

懂事、有趣、保持理智

周维的个人Blog

懂事、有趣、保持理智

站点概览

周维 | Jim

603927378@qq.com

推荐阅读

Javascript中的继承

在Javascript中虽然没有类的概念(ES6才新增,但也只是语法糖),但是却能使用OOP编程模式,这是因为JS中有面向对象编程的几点基本特征,即抽象、封装、继承、多态,这篇文章我们学习JS中的七种实现继承的方法。

上面提到,面向对象编程的几点特征在JS中都存在,所谓的抽象则是将一组有共同特征的事物抽象为一个JS对象,如将人这个事物抽象为{name: 'jim', age: 24}这样一个对象, 而封装则是指将一组这样的对象以某种数据结构捆绑起来形成类(构造函数)的过程。

关于对象的抽象和封装请参考我的上一篇《Javascript中七种创建对象的方法》

继承

继承是面向对象编程的一个重要概念,继承是以现有类为基础派生出其他新类的过程。

Js中的继承

Js将原型链作为实现继承的主要方法,其基本思想是让一个对象继承另一个对象的属性和方法,关于如何**解释原型链?**我在《解释什么是原型链?》中有详细解释。

1、原型链继承

// 超类型 function SuperType() { this.superType = true; } SuperType.prototype.setType = function(type) { this.superType = type; } // 子类型 function SubType() { this.subType = true; } // 将子类型的原型对象指向一个超类型的实例 SubType.prototype = new SuperType(); // 为子类型的原型添加方法 SubType.prototype.setType = function(type) { this.subType = type; } var instance = new SubType();

优点

  • 使用原型链的强大功能,能够契合Js中实现继承。

缺点

  • 使用原型链继承,超类型中的属性会被所有实例共享,实践中很少单独使用原型链。

2、借用构造函数继承(经典继承)

借用构造函数的思想是,通过对象冒充(call, apply)继承构造函数内部的属性和方法。

// 超类型 function SuperType() { this.superType = true; this.setSuperType = function(type) { this.superType = type; } } // 子类型 function SubType() { SuperType.apply(this, arguments); } var instance = new SubType();

优点

  • 实现了属性和方法的不共享。
  • 相对于原型链来说,借用构造函数能够由子类向父类传递参数。

缺点

  • 方法都定义在了构造函数内部,无法复用,因此也是很少直接使用。

3、组合继承

组合继承是将原型链借用构造函数组合在一起,并取长补短的一种继承方法, 其思想是通过原型链继承实例的属性方法, 通过借用构造函数继承实例属性。

// 超类型 function SuperType(type) { this.superType = type; } SuperType.prototype.setType = function(type) { this.superType = type; } // 子类型 function SubType(type) { // 继承构造函数内部的属性 SuperType.call(this, type); this.subType = type; } // 将子类型的原型对象指向一个超类型的实例 SubType.prototype = new SuperType(); // 将constructor指回来 SubType.protptype.constructor = SubType; SubType.prototype.setType = function(type) { this.subType = type; } var instance = new SubType();

通过原型链模式,将实例的所有属性和方法继承到instance的原型对象中,这时我们可以想到,原型对象中任然包含了超类构造函数内部的属性。

关键在于,通过借用构造函数,将超类的构造函数内部的属性直接继承到实例上,那么实例中的属性就覆盖了原型对象上的同名属性,从而实现了属性的私有。

组合继承也是JS中使用最多的一种继承方式。

优点

  • 实现了私有属性和公共方法的基础。包含了原型链和借用构造函数的优点

缺点

  • 构造函数内的属性会被继承到原型链和实例上,有性能浪费。
  • 在创建子类时,会调用一次超类构造函数,同时在new一个实例时,又会通过call方法调用一次超类构造函数,也是浪费。

4、原型式继承

原型式继承的思想在于并没有严格的使用构造函数,而是基于一个已有对象创建一个新对象的方法。

function object(obj){ function F(){}; F.prototype = obj; return new F(); }

通过观察上方代码可以了解到,object函数接收一个对象,在函数内部,将该对象作为了返回对象的原型对象,其实相当于创建了一个obj的副本,通过object函数生成的对象上的属性和方法都是共享的。

ES5中对原型式继承进行了规范,新增了Object.create()方法实现原型式继承(也可以叫寄生式继承)。

优点

  • 在只是想创建多个相似对象的情况下,原型式继承完全是可用的,类似于对象的一个浅复制。

缺点

  • 使用范围小,且和原型模式相同,包含应用类型的值的属性方法都将被共享。

5、寄生式继承

寄生式继承对原型式继承进一步增强,在函数内部通过增强对象的方式达到寄生的效果。

function object(obj){ function F(){}; F.prototype = obj; return new F(); } function createAnother(obj){ // 首先使用原型式继承返回一个对象 var clone = object(obj); // 对这个对象进行增强 clone.sayHi = function(){ console.log("Hi"); } // 返回增强后的对象 return clone; }

优点

  • 相对于原型式继承,寄生式继承能对对象进行增强。

缺点

  • 用于使用寄生式继承增强对象的方法不可公用,是直接定义到新实例上的。

6、寄生组合式继承

我们前面说过,组合式继承是Js中使用最广的继承方式,但是它的缺点在于会调用两次超类构造函数。

那么所谓的寄生组合式继承,其实是对组合式继承的优化,其实现是利用借用构造函数继承属性,通过原型链的混入继承方法。

// 超类型 function SuperType(type) { this.superType = type; } SuperType.prototype.setType = function(type) { this.superType = type; } // 子类型 function SubType(type) { // 继承构造函数内部的属性 SuperType.call(this, type); this.subType = type; } // 通过原型链的混入继承方法 inheritPrototype(SubType, SuperType); SubType.prototype.setType = function(type) { this.subType = type; } var instance = new SubType(); // 原型链混入函数 function inheritPrototype(subType, superType) { // 首先得到superType.prototype的拷贝 var prototype = object(superType.prototype); // 将prototype的constructor属性指向了subType prototype.constructor = subType; // 将subType的原型对象指向prototype subType.prototype = prototype; } // 原型式继承 function object(obj){ function F(){}; F.prototype = obj; return new F(); }

寄生组合式继承的实质其实是浅复制,不通过new SuperType()来实现浅复制,而是通过原型式继承实现浅复制,然后将浅复制的对象赋给SubType作为原型对象。

优点

  • 超类的构造函数只会调用一次。

缺点

  • 和组合式继承一样,实例的原型中任然有相同名称的属性,只不过被实例的属性给屏蔽了。

7、ES6 Class继承

ES6提供了 class的继承方式,但class不过是ES5中某种继承方式的语法糖,被babel翻译后的Class如下,下面我们来探究究竟是哪种继承方式吧。

//ES6写法 class Super { } class Sub extends Super { constructor() { super(); } }
// 被babel翻译成ES5写法, 简化版 "use strict"; function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) { Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } } var Super = function Super() { }; var Sub = function (_Super) { // 原型链混入函数 _inherits(Sub, _Super); function Sub() { return _possibleConstructorReturn(this, (Sub.__proto__ || Object.getPrototypeOf(Sub)).call(this)); } return Sub; }(Super);

仔细一看是不是很熟悉, 这不是就是我们上面讲过的寄生组合式继承

  • 首先编译时期,执行_inherits原型链混入函数,并在其中通过原型式继承(Object.create()) 基于SuperClass的原型对象创建一个新的对象并作为SubClass的原型。
  • 当调用new Sub()时, 执行_possibleConstructorReturn函数,通过Object.getPrototypeOf(Sub)).call(this))调用父类的构造函数,继承父类constructor内定义的属性或方法。
提交

全部评论0

暂时没有评论...