原型
继承得靠原型来实现,当然原型不是这篇文章的重点,我们来复习一下即可。
其实原型的概念很简单:
其实原型中最重要的内容就是这些了,完全没有必要去看那些长篇大论什么是原型的文章,初学者会越看越迷糊。
当然如果你想了解更多原型的深入内容,可以阅读我 之前写的文章。
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() 来继承父类的函数。
这种继承方式优点在于构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数,但是也存在一个缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。
寄生组合继承
这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。
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
以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。
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 中最先更新,有兴趣的可以关注下。今年主要会着重写以下三个专栏
以上所述是小编给大家介绍的JS继承详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小牛知识库网站的支持!
本文向大家介绍聊聊JavaScript如何实现继承及特点,包括了聊聊JavaScript如何实现继承及特点的使用技巧和注意事项,需要的朋友参考一下 “继承”是面向对象编程里面经常提及到的概念,它的目的是实现代码复用。JavaScript并没有“类”的概念,那么,它如何实现继承呢? (ES6有关键字class和extend,继承的语法与Java等面向对象语言类似,但是,ES6 class,只是Jav
本文向大家介绍聊一聊Java反射,包括了聊一聊Java反射的使用技巧和注意事项,需要的朋友参考一下 这次提到的Java反射涉及的代码比较多。因为工作中经常用到反射,对代码做了很多抽象以及过滤器。虽然代码量很多,但是简单易用,过滤插件也易修改。 下面介绍下工作中哪些地方比较容易用到反射。比如插件或者过滤器,如果抽象的子类比较少,配置成XML等结构也是可以达到同样的效果。如果希望灵活一些,添加了插件或
本文向大家介绍聊一聊前端存储。相关面试题,主要包含被问及聊一聊前端存储。时的应答技巧和注意事项,需要的朋友参考一下 老朋友cookie 短暂的 sessionStorage 简易强大的localStorage websql与indexeddb 详细参见:https://segmentfault.com/aZ1190000005927232
许多项目使用互联网多线交谈(IRC)提供实时聊天室,作为用户和开发者互相提问并得到及时答复的讨论场所。即使你可以在你的服务器运行IRC服务器时,也不必为此事麻烦。而应该象其他人一样:在Freenode(http://freenode.net/)运行你的IRC频道。Freenode给了你足够的权利来管理你项目的IRC频道,[17]可以让你摆脱维护IRC服务器这类无意义的麻烦。 首先要选择一个频道名称
主要内容:多继承下的构造函数,命名冲突在前面的例子中,派生类都只有一个基类,称为 单继承(Single Inheritance)。除此之外, C++也支持 多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。 多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、 C#、 PHP 等干脆取消了多继承。 多继承的语法也很简单,将多个基类用逗号隔开即可。例如已声明了类A
即时聊天是工作沟通中必不可少的工具。钉钉的即时聊天具有消息一触即达、身份和信息双重安全保障、群聊可精细化管理等特性,帮助你的企业实现工作沟通与生活聊天分离,让工作重回专注。 全员群管理 全员群开启 ● 团队创建完成后,默认会开启全员群。 全员群设置 ● 管理员可以登录管理员后台,对全员群进行设置。 登录管理员后台,点击通讯录中企业名称右侧的设置 查看全员群信息 设置全员群群主(默认是管理员) 修改