零、序言

  参考资料: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. OpenMP笔记(三)

    个人博客地址:http://www.bearoom.xyz/2019/02/21/openmp3/ 这一部分主要记录一些指令的使用. 一.parallel的使用 parallel是用于构造并行块的,也 ...

  2. 拉格朗日乘子(Lagrange multify)和KKT条件

    拉格朗日乘子(Lagrange multify)和KKT条件 无约束问题 无约束问题定义如下: f(x)称为目标函数, 其中x是一个向量,它的维度是任意的. 通过求导, 令导数等于零即可: 如下图所示 ...

  3. 用eclipse运行算法第四版的BinarySearch

    import java.util.Arrays; import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdIn; impo ...

  4. Python3中bytes和HexStr之间的转换

    1 Python3中bytes和HexStr之间的转换 ByteToHex的转换 def ByteToHex( bins ): """ Convert a byte st ...

  5. 瑞芯微RK3399六核-迅为3399开发板介绍

    迅为3399开发板基于瑞芯微的RK3399处理器设计,Rockchip RK3399是瑞芯微推出的一款低功耗.高性能的应用处理器芯片,该芯片基于Big.Little架构,即具有独立的NEON协同处理器 ...

  6. leetcode中二分查找的具体应用

    给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置. 你的算法时间复杂度必须是 O(log n) 级别. 如果数组中不存在目标值,返回 [ ...

  7. 干货 | IP高防使用配置

    一.知识简介 DoS(Denial of Service),即拒绝服务攻击.该攻击是利用目标系统网络服务功能缺陷或者直接消耗其系统资源,目的是使该目标客户的系统不可用,无法提供正常的服务. DDoS( ...

  8. 黑马程序员IDEA版JAVA基础班\JavaWeb部分视频\2-10Request和Response\第5节 request登录案例

    用户登录案例需求: 1.编写login.html登录页面 username & password 两个输入框 2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表 ...

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

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

  10. Downton Abbey

    1. 当女儿以为泰坦尼克号不会沉的时候,父亲用了一个有意思的比喻: - I thought it was supposed to be unsinkable. - Every mountain is ...