重学JS 系列:聊聊继承(推荐)


Posted in Javascript onApril 11, 2019

原型

继承得靠原型来实现,当然原型不是这篇文章的重点,我们来复习一下即可。
其实原型的概念很简单:

  1. 所有对象都有一个属性 __proto__ 指向一个对象,也就是原型
  2. 每个对象的原型都可以通过 constructor 找到构造函数,构造函数也可以通过 prototype 找到原型
  3. 所有函数都可以通过 __proto__ 找到 Function 对象
  4. 所有对象都可以通过 __proto__ 找到 Object 对象
  5. 对象之间通过 __proto__ 连接起来,这样称之为原型链。当前对象上不存在的属性可以通过原型链一层层往上查找,直到顶层 Object 对象

其实原型中最重要的内容就是这些了,完全没有必要去看那些长篇大论什么是原型的文章,初学者会越看越迷糊。 

当然如果你想了解更多原型的深入内容,可以阅读我 之前写的文章。

ES5 实现继承

ES5 实现继承总的来说就两种办法,之前写过这方面的内容,就直接复制来用了。

总的来说这部分的内容我觉得在当下更多的是为了应付面试吧。

组合继承

组合继承是最常用的继承方式,

function Parent(value) {
 this.val = value
}
Parent.prototype.getValue = function() {
 console.log(this.val)
}
function Child(value) {
 Parent.call(this, value)
}
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

以上继承的方式核心是在子类的构造函数中通过 Parent.call(this) 继承父类的属性,然后改变子类的原型为 new Parent() 来继承父类的函数。

这种继承方式优点在于构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数,但是也存在一个缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。

重学JS 系列:聊聊继承(推荐)

寄生组合继承

这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。

function Parent(value) {
 this.val = value
}
Parent.prototype.getValue = function() {
 console.log(this.val)
}

function Child(value) {
 Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
 constructor: {
  value: Child,
  enumerable: false,
  writable: true,
  configurable: true
 }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

重学JS 系列:聊聊继承(推荐)

Babel 如何编译 ES6 Class 的

为什么在前文说 ES5 实现继承更多的是应付面试呢,因为我们现在可以直接使用 class 来实现继承。

但是 class 毕竟是 ES6 的东西,为了能更好地兼容浏览器,我们通常都会通过 Babel 去编译 ES6 的代码。接下来我们就来了解下通过 Babel 编译后的代码是怎么样的。

function _possibleConstructorReturn (self, call) { 
  // ...
  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 Parent = function Parent () {
  // 验证是否是 Parent 构造出来的 this
  _classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
  _inherits(Child, _Parent);

  function Child () {
    _classCallCheck(this, Child);
  
    return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
  }

  return Child;
}(Parent));

以上代码就是编译出来的部分代码,隐去了一些非核心代码,我们先来阅读 _inherits 函数。

设置子类原型部分的代码其实和寄生组合继承是一模一样的,侧面也说明了这种实现方式是最好的。但是这部分的代码多了一句 Object.setPrototypeOf(subClass, superClass),其实这句代码的作用是为了继承到父类的静态方法,之前我们实现的两种继承方法都是没有这个功能的。

然后 Child 构造函数这块的代码也基本和之前的实现方式类似。所以总的来说 Babel 实现继承的方式还是寄生组合继承,无非多实现了一步继承父类的静态方法。

继承存在的问题

讲了这么些如何实现继承,现在我们来考虑下继承是否是一个好的选择?

总的来说,我个人不怎么喜欢继承,原因呢就一个个来说。

我们先看代码。假如说我们现在要描述几辆不同品牌的车,车必然是一个父类,然后各个品牌的车都分别是一个子类。

class Car {
  constructor (brand) {
    this.brand = brand
  }
  wheel () {
    return '4 个轮子'
  }
  drvie () {
    return '车可以开驾驶'
  }
  addOil () {
    return '车可以加油'
  }
}
Class OtherCar extends Car {}

这部分代码在当下看着没啥毛病,实现了车的几个基本功能,我们也可以通过子类去扩展出各种车。

但是现在出现了新能源车,新能源车是不需要加油的。当然除了加油这个功能不需要,其他几个车的基本功能还是需要的。

如果新能源车直接继承车这个父类的话,就出现了第一个问题 ,大猩猩与香蕉问题。这个问题的意思是我们现在只需要一根香蕉,但是却得到了握着香蕉的大猩猩,大猩猩其实我们是不需要的,但是父类还是强塞给了子类。继承虽然可以重写父类的方法,但是并不能选择需要继承什么东西。

另外单个父类很难描述清楚所有场景,这就导致我们可能又需要新增几个不同的父类去描述更多的场景。随着不断的扩展,代码势必会存在重复,这也是继承存在的问题之一。

除了以上两个问题,继承还存在强耦合的情况,不管怎么样子类都会和它的父类耦合在一起。

既然出现了强耦合,那么这个架构必定是脆弱的。一旦我们的父类设计的有问题,就会对维护造成很大的影响。因为所有的子类都和父类耦合在一起了,假如更改父类中的任何东西,都可能会导致需要更改所有的子类。

如何解决继承的问题

继承更多的是去描述一个东西是什么,描述的不好就会出现各种各样的问题,那么我们是否有办法去解决这些问题呢?答案是组合。

什么是组合呢?你可以把这个概念想成是,你拥有各种各样的零件,可以通过这些零件去造出各种各样的产品,组合更多的是去描述一个东西能干什么。

现在我们把之前那个车的案例通过组合的方式来实现。

function wheel() {
 return "4 个轮子";
}
function drvie() {
 return "车可以开驾驶";
}
function addOil() {
 return "车可以加油";
}
// 油车
const car = compose(wheel, drvie, addOil)
// 新能源车
const energyCar = compose(wheel, drive)

从上述伪代码中想必你也发现了组合比继承好的地方。无论你想描述任何东西,都可以通过几个函数组合起来的方式去实现。代码很干净,也很利于复用。

最后

其实这篇文章的主旨还是后面两小节的内容,如果你还有什么疑问欢迎在评论区与我互动。

我所有的系列文章都会在我的 Github 中最先更新,有兴趣的可以关注下。今年主要会着重写以下三个专栏

  1. 重学 JS
  2. React 进阶
  3. 重写组件

以上所述是小编给大家介绍的JS继承详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Extjs4中Form的使用之本地hiddenfield
Nov 26 Javascript
javascript数组快速打乱重排的方法
Jan 02 Javascript
jquery判断小数点两位和自动删除小数两位后的数字
Mar 19 Javascript
jquery easyui 结合jsp简单展现table数据示例
Apr 18 Javascript
JS小游戏之极速快跑源码详解
Sep 25 Javascript
JavaScript中SetInterval与setTimeout的用法详解
Nov 10 Javascript
浅谈window.onbeforeunload() 事件调用ajax
Jun 29 Javascript
Bootstrap中定制LESS-颜色及导航条(推荐)
Nov 21 Javascript
Vue.js结合Ueditor富文本编辑器的实例代码
Jul 11 Javascript
vue父组件中获取子组件中的数据(实例讲解)
Sep 27 Javascript
js限制输入框只能输入数字(onkeyup触发)
Sep 28 Javascript
createObjectURL方法实现本地图片预览
Sep 30 Javascript
优雅的将ElementUI表格变身成树形表格的方法步骤
Apr 11 #Javascript
详解用场景去理解函数柯里化(入门篇)
Apr 11 #Javascript
Vue开发Html5微信公众号的步骤
Apr 11 #Javascript
跟混乱的页面弹窗说再见
Apr 11 #Javascript
vue实现todolist功能、todolist组件拆分及todolist的删除功能
Apr 11 #Javascript
vue实现todolist基本功能以及数据存储功能实例详解
Apr 11 #Javascript
JavaScript高阶教程之“==”隐藏下的类型转换
Apr 11 #Javascript
You might like
基于php导出到Excel或CSV的详解(附utf8、gbk 编码转换)
2013/06/25 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
PHP使用mongoclient简单操作mongodb数据库示例
2019/02/08 PHP
js 静态动态成员 and 信息的封装和隐藏
2011/05/29 Javascript
jQuery学习笔记之控制页面实现代码
2012/02/27 Javascript
关于锚点跳转及jQuery下相关操作与插件
2012/10/01 Javascript
YUI模块开发原理详解
2013/11/18 Javascript
javascript获取隐藏元素(display:none)的高度和宽度的方法
2014/06/06 Javascript
jquery拼接ajax 的json和字符串拼接的方法
2017/03/11 Javascript
微信小程序 页面跳转及数据传递详解
2017/03/14 Javascript
使用Require.js封装原生js轮播图的实现代码
2017/06/15 Javascript
分享5个小技巧让你写出更好的 JavaScript 条件语句
2018/10/20 Javascript
如何使用Node.js爬取任意网页资源并输出PDF文件到本地
2019/06/17 Javascript
JavaScript中callee和caller的区别与用法实例分析
2019/06/28 Javascript
toString.call()通用的判断数据类型方法示例
2020/08/28 Javascript
梳理一下vue中的生命周期
2020/12/30 Vue.js
[01:53]2016完美“圣”典风云人物:Maybe专访
2016/12/05 DOTA
查看Python安装路径以及安装包路径小技巧
2015/04/28 Python
关于Python如何避免循环导入问题详解
2017/09/14 Python
利用Django内置的认证视图实现用户密码重置功能详解
2017/11/24 Python
python图书管理系统
2020/04/05 Python
Python实现socket非阻塞通讯功能示例
2019/11/06 Python
python 列表、字典和集合的添加和删除操作
2019/12/16 Python
使用Python串口实时显示数据并绘图的例子
2019/12/26 Python
Python中的面向接口编程示例详解
2021/01/17 Python
css3的focus-within选择器的使用
2020/05/11 HTML / CSS
HTML5实现锚点时请使用id取代name
2013/09/06 HTML / CSS
微信小程序canvas实现水平、垂直居中效果
2020/02/05 HTML / CSS
出纳员的岗位职责
2014/02/22 职场文书
论文指导教师评语
2014/04/28 职场文书
2014各大专业毕业生自我评价
2014/09/17 职场文书
2014最新自愿离婚协议书范本
2014/11/19 职场文书
2016年小学生寒假家长评语
2015/10/10 职场文书
导游词之蜀山胜景瓦屋山
2019/11/29 职场文书
redis 限制内存使用大小的实现
2021/05/08 Redis
python 实现两个变量值进行交换的n种操作
2021/06/02 Python