重学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 相关文章推荐
JS效率个人经验谈(8-15更新),加入range技巧
Jan 09 Javascript
jQuery select控制插件
Aug 17 Javascript
jQuery中绑定事件的命名空间详解
Apr 05 Javascript
js中call与apply的用法小结
Dec 28 Javascript
IE6-IE9使用JSON、table.innerHTML所引发的问题
Dec 22 Javascript
关于JavaScript数组你所不知道的3件事
Aug 24 Javascript
JavaScript字符集编码与解码详谈
Feb 02 Javascript
jQuery Ajax前后端使用JSON进行交互示例
Mar 17 Javascript
js 获取json数组里面数组的长度实例
Oct 31 Javascript
Nuxt配合Node在实际生产中的应用详解
Aug 07 Javascript
微信小程序登录态和检验注册过没的app.js写法
May 22 Javascript
浅谈javascript如何获取文件后缀名
Aug 07 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
星际争霸 Starcraft 编年史
2020/03/14 星际争霸
用PHP中的 == 运算符进行字符串比较
2006/11/26 PHP
fleaphp rolesNameField bug解决方法
2011/04/23 PHP
PHP中常用的转义函数
2014/02/28 PHP
destoon二次开发模板及调用语法汇总
2014/06/21 PHP
什么是PEAR?什么是PECL?PHP中两个容易混淆的概念解释
2015/07/01 PHP
php使用pclzip类实现文件压缩的方法(附pclzip类下载地址)
2016/04/30 PHP
Laravel网站打开速度优化的方法汇总
2017/07/16 PHP
laravel 框架结合关联查询 when()用法分析
2019/11/22 PHP
滚动条变色 隐藏滚动条与双击网页自动滚屏显示代码
2009/12/28 Javascript
textarea 控制输入字符字节数(示例代码)
2013/12/27 Javascript
使用delegate方法为一个tr标签加一个链接
2014/06/27 Javascript
javascript正则表达式之search()用法实例
2015/01/19 Javascript
jQuery实现的进度条效果
2015/07/15 Javascript
原生js实现网易轮播图效果
2020/04/10 Javascript
VUE搭建手机商城心得和遇到的坑
2019/02/21 Javascript
vue动态注册组件实例代码详解
2019/05/30 Javascript
NodeJs crypto加密制作token的实现代码
2019/11/15 NodeJs
Windows下安装 node 的版本控制工具 nvm
2020/02/06 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
详细解读Python中的__init__()方法
2015/05/02 Python
Python实现字符串格式化输出的方法详解
2017/09/20 Python
Django中针对基于类的视图添加csrf_exempt实例代码
2018/02/11 Python
python 根据网易云歌曲的ID 直接下载歌曲的实例
2019/08/24 Python
Linux下通过python获取本机ip方法示例
2019/09/06 Python
Django多数据库配置及逆向生成model教程
2020/03/28 Python
Python中如何引入第三方模块
2020/05/27 Python
Html5上传图片 移动端、PC端通用代码
2016/06/08 HTML / CSS
C++是不是类型安全的
2014/02/18 面试题
护理专业推荐信
2013/11/07 职场文书
给客户的道歉信
2014/01/13 职场文书
运动会领导邀请函
2014/02/05 职场文书
安全负责人任命书
2014/06/06 职场文书
争先创优演讲稿
2014/09/15 职场文书
财务人员岗位职责
2015/02/03 职场文书
合理缓解职场压力,让你随时保持最佳状态!
2019/06/21 职场文书