深入理解JavaScript继承的多种方式和优缺点


Posted in Javascript onMay 12, 2017

写在前面

本文讲解JavaScript各种继承方式和优缺点。

注意:

跟《JavaScript深入之创建对象》一样,更像是笔记。

哎,再让我感叹一句:《JavaScript高级程序设计》写得真是太好了!

1.原型链继承

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

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

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

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

问题:

1.引用类型的属性被所有实例共享,举个例子:

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

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]

2.在创建 Child 的实例时,不能向Parent传参

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

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

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

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

优点:

1.避免了引用类型的属性被所有实例共享

2.可以在 Child 中向 Parent 传参

举个例子:

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

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

var child1 = new Child('kevin');

console.log(child1.name); // kevin

var child2 = new Child('daisy');

console.log(child2.name); // daisy

缺点:

方法都在构造函数中定义,每次创建实例都会创建一遍方法。

3.组合继承

原型链继承和经典继承双剑合璧。

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();

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

4.原型式继承

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:

包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

var person = {
  name: 'kevin',
  friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1person2有独立的 name 值,而是因为person1.name = 'person1',给person1添加了 name 值,并非修改了原型上的 name 值。

5. 寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
  var clone = object.create(o);
  clone.sayName = function () {
    console.log('hi');
  }
  return clone;
}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

为了方便大家阅读,在这里重复一下组合继承的代码:

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();

var child1 = new Child('kevin', '18');

console.log(child1)

组合继承最大的缺点是会调用两次父构造函数。

一次是设置子类型实例的原型的时候:

Child.prototype = new Parent();

一次在创建子类型实例的时候:

var child1 = new Child('kevin', '18');

回想下 new 的模拟实现,其实在这句中,我们会执行:

Parent.call(this, name);

在这里,我们又会调用了一次 Parent 构造函数。

所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为['red', 'blue', 'green']。

那么我们该如何精益求精,避免这一次重复调用呢?

如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?

看看如何实现:

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;
}

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('kevin', '18');

console.log(child1);

最后我们封装一下这个继承方法:

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

function prototype(child, parent) {
  var prototype = object(parent.prototype);
  prototype.constructor = child;
  child.prototype = prototype;
}

// 当我们使用的时候:
prototype(Child, Parent);

引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:

这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用正则获取指定路径文件的名称
Feb 27 Javascript
让GoogleCode的SVN下的HTML文件在FireFox下正常显示.
May 25 Javascript
jQuery 自动增长的文本输入框实现代码
Apr 02 Javascript
Jquery中显示隐藏的实现代码分析
Jul 26 Javascript
javascript两种function的定义介绍及区别说明
May 02 Javascript
javascript中2个感叹号的用法实例详解
Sep 04 Javascript
JavaScript实现的圆形浮动标签云效果实例
Aug 06 Javascript
JS禁用页面上所有控件的实现方法(附demo源码下载)
Dec 17 Javascript
Javascript实现汉字和拼音互转的终极方案
Oct 19 Javascript
JS 使用 window对象的print方法实现分页打印功能
May 16 Javascript
浅谈JavaScript 代码简洁之道
Jan 09 Javascript
详解ES6中class的实现原理
Oct 03 Javascript
JS实现图片预加载之无序预加载功能代码
May 12 #Javascript
详解React开发中使用require.ensure()按需加载ES6组件
May 12 #Javascript
vue学习笔记之指令v-text && v-html && v-bind详解
May 12 #Javascript
JS常用正则表达式总结【经典】
May 12 #Javascript
vue.js的安装方法
May 12 #Javascript
JS匹配日期和时间的正则表达式示例
May 12 #Javascript
js如何获取网页所有图片
May 12 #Javascript
You might like
php实现12306余票查询、价格查询示例
2014/04/17 PHP
PHP 抽象方法与抽象类abstract关键字介绍及应用
2014/10/16 PHP
Mac环境下php操作mysql数据库的方法分享
2015/05/11 PHP
php上传图片类及用法示例
2016/05/11 PHP
Gambit vs CL BO3 第一场 2.13
2021/03/10 DOTA
可以用来调试JavaScript错误的解决方案
2010/08/07 Javascript
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
js实例属性和原型属性示例详解
2014/11/23 Javascript
javascript新闻跑马灯实例代码
2020/07/29 Javascript
jquery弹出遮掩层效果【附实例代码】
2016/04/28 Javascript
利用JavaScript阻止表单提交的两种方法
2016/08/11 Javascript
详解jQuery的表单验证插件--Validation
2016/12/21 Javascript
js数组去重的hash方法
2016/12/22 Javascript
mac中利用NVM管理不同node版本的方法详解
2017/11/08 Javascript
原生JavaScript实现todolist功能
2018/03/02 Javascript
koa大型web项目中使用路由装饰器的方法示例
2019/04/02 Javascript
[00:56]2014DOTA2国际邀请赛 DK、iG 赛前探访
2014/07/10 DOTA
[01:04:30]Fnatic vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
python通过索引遍历列表的方法
2015/05/04 Python
Python日志模块logging基本用法分析
2018/08/23 Python
python实现根据文件格式分类
2019/10/31 Python
基于python中__add__函数的用法
2019/11/25 Python
Django 解决阿里云部署同步数据库报错的问题
2020/05/14 Python
django项目中使用云片网发送短信验证码的实现
2021/01/19 Python
html5定位并在百度地图上显示的示例
2014/04/27 HTML / CSS
研发工程师的岗位职责
2013/11/18 职场文书
父亲生日宴会答谢词
2014/01/10 职场文书
《望洞庭》教学反思
2014/02/16 职场文书
辩论赛主持词
2014/03/18 职场文书
国庆节演讲稿范文2014
2014/09/19 职场文书
转学证明范本
2015/06/19 职场文书
2015年科学教研组工作总结
2015/07/22 职场文书
python 爬取豆瓣网页的示例
2021/04/13 Python
解决Goland 同一个package中函数互相调用的问题
2021/05/06 Golang
Python实现生成bmp图像的方法
2021/06/13 Python
德劲DE1102数字调谐收音机机评
2022/04/07 无线电