零、序言

  参考资料: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. Ubuntu16.04编译tensorflow的C++接口

    原文:https://www.bearoom.xyz/2018/09/27/ubuntu1604buildtf4cpp/ 之前有一篇介绍到在windows下利用VS2015编译tensorflow的C ...

  2. 36. docker swarm docker secret 的使用和管理

    1.secret management 的作用 用来存储 其他人不想看到 的数据 2.secret management 存在 swarm manager 节点 raft database 里. se ...

  3. 配置window下python3环境

    功能介绍 整理生信小知识库,一些技巧一些知识. 昨天 以下配置环境基于window操作系统,安装python3版本为例,推荐基础版配置. !   METHOD 1 (基础版) 官网下载对应电脑版本的p ...

  4. 树状数组--模版1和2 P3368、P3374

    题目描述 如题,已知一个数列,你需要进行下面两种操作: 将某一个数加上 x 求出某区间每一个数的和 输入格式 第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数. 第二行包含 n 个 ...

  5. Channels(纪念一下卡我心态的一道题)

    链接:https://ac.nowcoder.com/acm/contest/3947/C来源:牛客网 题目描述 Nancy喜欢学习,也喜欢看电视. 为了想了解她能看多长时间的节目,不妨假设节目从时刻 ...

  6. UVA 10158 并查集的经典应用

    这个题目一看就是用并查集,有N个国家代表,在M行给出两两之间的关系,敌人或者朋友,(当然如果该关系跟已知关系冲突,则输出-1) 关系的几个约束条件时这样的 在朋友方面,朋友的朋友就是自己的朋友,这个就 ...

  7. 65)STL中string的知识

    1)代码展示: string是一个类,只不过封装了 char*  而且还封装了  很多的字符串操作函数 2)string类的初始化: string的构造函数 ²  默认构造函数: string();  ...

  8. 调用新浪短地址转换api的一个测试

    import base64 import requests url="http://www.~~~~.com" headers={ "User-Agent":& ...

  9. linux安装java步骤

    本文转发自博客园-Q鱼丸粗面Q.博客园-郁冬的文章,内容略有改动 本文已收录至博客专栏linux安装各种软件及配置环境教程中 方式一:yum方式下载安装 1.查找java相关的列表 yum -y li ...

  10. Django整体架构

    Django整体架构 用户能够访问到的所有的资源 都是程序员提前暴露好的, 如果没有暴露 用户就永远访问不了 用户能够访问到的所有的资源 都是程序员提前暴露好的, 如果没有暴露 用户就永远访问不了 一 ...