零、序言

  参考资料:JavaScript常用八种继承方案

  注:1.此篇笔记是站在上述资料的肩膀上的一篇小结;

    2.阅读之前建议温习一下 js 中的 prototype 和 constructor;(js - __proto__ 、 prototype和constructor)

一、原型链上的继承(new)

function Father() {
this.fatherValue = true;
this.colors = ['red', 'yellow', 'green'];
} Father.prototype.getFatherValue = function() {
return this.fatherValue;
} function Son() {
this.sonValue = false;
} // 以下两句需要按照顺序写,避免意外的覆盖
Son.prototype = new Father(); // 核心,这里建立继承关系 Son.prototype.getSonValue = function() { // 定义需要的属性/方法
return this.sonValue;
} var instance1 = new Son();
console.log(instance1)

  

  这个方法的原理是利用原型链  instance1.__proto__ === Son.prototype ,然后手动修改 Son.prototype 的指向,使其指向一个  Father 的实例对象,从而实现继承。

  从上例中我们可以发现, instance1 自身并没有 fatherValue、colors 这些属性,但是我们可以通过原型链访问得到这些属性,说明继承成功。

  但是这里就存在一个问题,我们接着看下面的代码:

var instance1 = new Son();
console.log(instance1.colors); // ["red", "yellow", "green"]
instance1.colors.push('black');
console.log(instance1.colors); // ["red", "yellow", "green", "black"] var instance2 = new Son();
console.log(instance2.colors); // ["red", "yellow", "green", "black"]

  对象 instance1 对引用类型的 colors 修改,那么其他所有的实例中的 colors 都被修改了,这也是这种继承方法的缺点之一:父类使用 this 声明的属性被所有实例共享。

  另外,我们也没有办法在实例子类的同时,根据需要向父类传参数,不够灵活

二、借用构造函数(call)

function Father() {
this.color = ['red', 'green', 'blue'];
}
function Son() {
Father.call(this) // 核心,利用 this 在初始化的时候指向实例对象实现继承
}
var instance1 = new Son();
instance1.color.push('black');
console.log(instance1.color); // 'red, green, blue, black' var instance2 = new Son();
console.log(instance2.color); // 'red, green, blue'

附一张 instance1 内部结构图:

  我们可以看到这里与第一种方法的不同的地方在属性的挂载上,第一种方法 colors 是挂在原型链上的,而这种方法 colors 直接是在子类的实例对象上的,所以我们就能修正第一种方法的实例共享的问题。

  我们仔细分析一下这里面的核心关系:我们在 new Son() 的时候,一定会执行函数调用语句  Father.call(this) 这句,而这一句实质是改变  Father 内部的 this 的指向,使其指向子实例对象,并在子实例对象上挂载  color 属性(相当于 instance1.color = ['red', 'green', 'blue']; ),这样,在 Father.call(this) 执行完之后,子实例对象上就会多一个属性,并且,因为该过程中执行的是函数调用,所以每次新实例化子对象的时候均会创建地址不同的 ['red', 'green', 'blue'] 并赋值,从而解决第一种方法中的共享问题。

  这种方法的优点除解决共享问题外,还可以在实例化子类型对象时向父类型传递参数。当然,也有缺点,因为这种方法与原型链没有任何关系,故,子类只能继承父类中通过  this 声明(注册)的属性,不能访问,也不能继承父类  prototype 上的属性/方法

父类方法无法复用:因为无法继承父类的prototype,所以每次子类实例化都要执行父类函数,重新声明父类this里所定义的方法,因此方法无法复用。 -- 这一句不知道怎么理解?

  

三、组合式继承(call + new)

  这种方式是将上面两种方法综合一下:

function Father(name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
} Father.prototype.sayFather = function() {
console.log(this.name);
} function Son(name, age) {
// 继承属性
// 第二次调用 Father();
Father.call(this, name);
this.age = age;
} // 继承方法
// 第一次调用 Father();
Son.prototype = new Father(); // console.log(Son.prototype.constructor) // => Father() {} // 重写(扶正) Son.prototype 的 constructor, 让其指向自身的构造函数 Son
// 因为上一句 (Son.prototype = new Father()) 的关系,如不修改,Son.prototype.constructor 会指向 Father
Son.prototype.constructor = Son; Son.prototype.saySon = function() {
console.log(this.age)
} // console.log(Son.prototype.constructor) // () => Son() {} var instance1 = new Son('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // 'red,blue,green,black'
instance1.sayFather(); // 'Nicholas';
instance1.saySon(); // 29 var instance2 = new Son('Greg', 27);
console.log(instance2.colors); // 'red,blue,green'
instance2.sayFather(); // 'Greg';
instance2.saySon(); // 27

  这样的优点自然是解决上面两种方法的主要痛点。

  然后我们来观察下子实例的结构:

  原因是在源码中执行过两次 Father(),这两次分别在子实例的原型(Son.prototype)和子实例(new Father())上挂载了同样的属性,当然这里不存在同一个子实例原型和子实例上的引用类型数据共享的问题(instance.colors !== instance.__proto__.colors)。

  而因为每个子实例均会优先访问自身的 1 中的属性,所以这就绕过了父类使用 this 声明的属性被所有实例共享的问题

  当然,这也是这种方法的缺点:在使用子类创建实例对象时,其原型中会存在一份同样的属性/方法

  另外,还有几点需要注意:

    1.凡是使用原型链(new Father())的方式,都会存在  Son.prototype.constructor 需要扶正的问题,所以第一种方式中需要补全。( ̄_, ̄ )

四、原型式继承(Object.create())

function cloneObject(obj) {
function F() {}
F.prototype = obj; return new F();
} var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}; var anotherPerson = cloneObject(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob'); var yetAnotherPerson = cloneObject(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie'); console.log(person.friends); // 'Shelby, Court, Van, Rob, Barbie'
console.log(anotherPerson.friends); // 'Shelby, Court, Van, Rob, Barbie'
console.log(yetAnotherPerson.friends); // 'Shelby, Court, Van, Rob, Barbie'

  其中, cloneObject 函数是  Object.create() 原生方法的模拟实现,这里只是演示下,实际情况中可以使用 Object.create() 来代替。当然,这里发生的复制都是浅复制。

  接着我们看一下子实例的内部结构:

、  和第一种方法打印出来的子实例结构类似,其实这两种方式本质上是相同的,所以他们的名字就差一个字,缺点也一样:共享引用类型;不能灵活传参

五、寄生式继承

  这种继承方式仅在第四种方法的基础上做了些许增强(可以理解成定制化),修改不大:

function createAnother(original) {
var clone = Object.create(original);
clone.sayHi = function() { // 增强的部分
console.log('hi');
} return clone;
} var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'

  优缺点同原型式继承。

六、寄生组合式继承(call + 寄生式)

  结合借用构造函数和寄生模式实现继承,是目前最成熟的方式,也是现在很多库实现的方法。

function Father(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Father.prototype.sayFather = function() {
console.log(this.name);
} function Son(name, age) {
Father.call(this, name);
this.age = age;
} function inheritPrototype(son, father) {
var prototype = Object.create(father.prototype); // 创建一个父类原型的副本对象
prototype.constructor = son; // 扶正 constructor, 否则会指向 father
son.prototype = prototype;
}
inheritPrototype(Son, Father); Son.prototype.saySon = function() {
console.log(this.age);
} var instance1 = new Son('xyc', 23);
var instance2 = new Son('lxy', 29); instance1.colors.push('2'); // ['red', 'blue', 'green', '2']
instance2.colors.push('3'); // ['red', 'blue', 'green', '3']

  这种方式与组合式的区别在于使用  inheritPrototype 函数来代替  Son.prototype = new Father(); ,在整个过程中只调用一次  Father() ,从而避免在  Son.prototype 上挂载多余的属性。我们来看一下子实例的结构:

  我们可以看到,在所有子实例的原型对象(instance.__proto__)上并没有找到组合式存在的不必要的、重复属性。所以总结来说,借用组合式实现参数传递,借用寄生式完善原型链建立,因此,还能正常使用  instanceof 和  isPrototypeOf() 。

七、ES 6 extends 继承

  这个和 java 的用法一致,具体写法就不贴了,就贴一下核心代码实现,当然,是通过 babel 编译成 es5 的:

function _inherits(subType, superType) {

    // 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
}); if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}

  其本质就是寄生组合的方式。

js - 常用的继承的更多相关文章

  1. js如何实现继承

    js继承有5种实现方式:1.继承第一种方式:对象冒充  function Parent(username){    this.username = username;    this.hello = ...

  2. js的各种继承

    请先看这个链接:https://segmentfault.com/a/1190000002440502 还有一个里边有js的采用临时方法的继承 http://javapolo.iteye.com/bl ...

  3. Node.js 常用工具

    Node.js 常用工具 util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心JavaScript 的功能 过于精简的不足. util.inherits util.inherit ...

  4. [js]js原型链继承小结

    这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...

  5. Node.js 常用工具util包

    Node.js 常用工具 util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心JavaScript 的功能 过于精简的不足. util.isError(obj); util.is ...

  6. js常用框架

    JS常用框架:jQuery.Prototype.MooTools 参考:w3cshool jQuery jQuery 是目前最受欢迎的 JavaScript 框架. 它使用 CSS 选择器来访问和操作 ...

  7. 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承

    ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...

  8. JS中的继承(上)

    JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...

  9. JS中的继承(下)

    JS中的继承(下) 在上一篇 JS中的继承(上) 我们介绍了3种比较常用的js继承方法,如果你没看过,那么建议你先看一下,因为接下来要写的内容, 是建立在此基础上的.另外本文作为我个人的读书笔记,才疏 ...

随机推荐

  1. Java中常用的API(三)——缓冲区字符串

    前两节中分别介绍了Object和String,这一节主要介绍StringBuffer和StringBuilder. StringBuffer 由于String是不可变的,所以导致String对象泛滥, ...

  2. 拷贝构造函数[c++]

    拷贝构造函数何时会被调用? 1. 对象以值传递的方式传入函数参数 2.对象以值传递的方式从函数返回 3.对象需要通过另外一个对象进行初始化 下面我们来看代码: //#include <iostr ...

  3. List中常用的linq操作

    [Serializable] public class Product { public Product() { } public Product(string id,string pname,int ...

  4. goweb-动作

    go-模板引擎 动作 Go 模板的动作就是一些嵌入到模板里面的命令,这些命令在模板中需要放到两个 大括号里{{ 动作 }},之前我们已经用过一个很重要的动作:点(.),它代表了传递给模 板的数据.下面 ...

  5. 素小暖讲JVM:Eclipse运行速度调优

    本系列是用来记录<深入理解Java虚拟机>这本书的读书笔记.方便自己查看,也方便大家查阅. 欲速则不达,欲达则欲速! 这两天看了JVM的内存优化,决定尝试一下,对Eclipse进行内存调优 ...

  6. Java线程——线程习题(二)生成者消费者

    生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据. 这里实现如下情况的生产--消费模型: 生产者不断交替地生产两组数据“姓 ...

  7. java生成6位数所有组合

    for(int i=0;i<=9;i++){ String str=""; str=str+i; String strj=""; for(int j=0; ...

  8. Ubuntu python多个版本管理

    1.查看python有哪些版本使用命令 whereis python 如图: 2.这么多版本如何切换呢 使用 sudo update-alternatives --install <link&g ...

  9. Java搭建WebSocket的两种方式

    下面分别介绍搭建方法:一.直接使用Java EE的api进行搭建.一共3个步骤:1.添加依赖<dependency>    <groupId>javax</groupId ...

  10. spring mvc + ajax上传文件,页面局部刷新

    1.点击上传按钮进行如下操作,通过表单名称以及input名称获取相应的值,对于上传的文件,使用.files来获取, 因为包含文件的上传,所以采用FormData的形式来进行数据交互,通过append将 ...