前言

对于灵活的js而言,继承相比于java等语言,继承实现方式可谓百花齐放。方式的多样就意味着知识点繁多,当然也是面试时绕不开的点。撇开ES6 class不谈,传统的继承方式你知道几种?每种实现原理是什么,优劣点能谈谈吗。这里就结合具体例子,按照渐进式的思路来看看继承的发展。

准备

谈到js继承之前先回顾下js 实例化对象的实现方式。

构造函数是指可以通过new 来实例化对象的函数,目的就是为了复用,避免每次都手动声明对象实例。

new 简单实现如下:

  1. function my_new(func){
  2. var obj = {}
  3. obj._proto_ = func.prototype // 修改原型链指向,拼接至func原型链
  4. func.call(obj) // 实例属性赋值
  5. return obj
  6. }

由上可以看出,通过构造函数调用,可以将实例属性赋值到目标对象上。
如此可以推想,子类中调用父类构造函数同样可以达到继承的目的。
这就提供了js继承的一种思路,即通过构造函数调用。

至于原型属性,就是通过修改原型指向,来实现原型属性的共享。
那么继承时同样也可以通过该方式进行。

总结

基于构造函数和原型链两种特性,结合js语言的灵活性。
继承的实现方式虽然繁多万变也不离其宗

继承的n种方式

原型式继承

定义:这种继承借助原型并基于已有的对象创建新对象,同时还不用创建自定义类型的方式称为原型式继承。

直接看代码更清晰:

  1. function createObj(o) {
  2. function F() { }
  3. F.prototype = o;
  4. return new F();
  5. }
  6. var parent = {
  7. name: 'trigkit4',
  8. arr: ['brother', 'sister', 'baba']
  9. };
  10. var child1 = createObj(parent);

该方式表面上看基于对象创建,不需要构造函数(当然实际构造函数被封装起来罢了)。只借助了原型对象,所以名称为原型式继承。

缺点
比较明显优良者

  • 无法复用该继承,每个子类的实例,都要走完整的createObj流程。
  • 对于子类对象
    因为构造函数封装createObj中,对其而言,没有构造函数。由此造成无法初始化时传参。
    补充:其中 createObj 就是我们ES6中常用的Object.create(),不过Object.create进行了完善,允许额外参数来完善了。

解决思路
既然提到没有构造函数导致了问题,那么大胆猜测,更进一步就是涉及了构造函数的原型链继承了。

原型链式继承

定义:为了让子类继承父类的属性(也包括方法),首先需要定义一个构造函数。然后,将父类的新实例赋值给构造函数的原型。

  1. function Parent() {
  2. this.name = 'mike';
  3. }
  4. function Child() {
  5. this.age = 12;
  6. }
  7. Child.prototype = new Parent();
  8. child.prototype.contructor = child // 原型属性被覆盖,所以要修正回来。
  9. var child1 = new Child();

也就是直接修改子类的原型对象指父构造函数的实例,这样把父类的实例属性和原型属性都挂到自己原型链上。

缺点

  • Child.prototype = new Parent() ,那么子函数自身的原型属性就被覆盖了,如果需要就要在后面补充。
  • 子对象实例化时,无法向父类构造函数传递参数。
    例如在new Child()执行的时候,想要去覆盖name,只能在Child.prototype = new Parent()时。 是我们在new Child()的时候统一传参初始化是更常规需求。

解决思路
如何在子类初始化时,调用父类构造函数。结合前面的基础,答案也呼之欲出。

借用构造函数(类式继承)

类式继承:是在子类型构造函数的内部调用超类型的构造函数。

思路比较清晰,由问题驱动。
既然原型链式子类不能向父类传参的问题,那么在子类初始化是调用父类不就满足目的了。

示例如下:

  1. function Parent(age) {
  2. this.name = ['mike', 'jack', 'smith'];
  3. this.age = age;
  4. }
  5. Parent.prototype.run = function () {
  6. return this.name + ' are both' + this.age;
  7. };
  8. function Child(age) {
  9. // 调用父类
  10. Parent.call(this, age);
  11. }
  12. var child1 = new Child(21);

这样满足了初始化时传参的需求,但是问题也比较明显。

  1. child1.run //undefined

问题

  • 父类原型属性丢失
    父类初始化只继承了示例属性,原型属性在子类的原型链上丢失

解决思路
丢失的原因在于原型链没有修改指向,那么修改下指向不就完了。

组合继承

定义:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

示例:

  1. function Parent(age) {
  2. this.name = ['mike', 'jack', 'smith'];
  3. this.age = age;
  4. }
  5. Parent.prototype.run = function () {
  6. return this.name + ' are both' + this.age;
  7. };
  8. function Child(age) {
  9. // 调用父类构造函数
  10. Parent.call(this, age);
  11. }
  12. Child.prototype = new Parent();//原型属性继承
  13. Child.prototype.contructor = Child
  14. var child1 = new Child(21);

这样问题就避免了:

  1. child1.run() // "mike,jack,smith are both21"

问题
功能满足之后,就该关注性能了。这种继承方式问题在于父类构造函数执行了两次。
分别是:

  1. function Child(age) {
  2. // 调用父类构造函数,第二次
  3. Parent.call(this, age);
  4. }
  5. Child.prototype = new Parent();//修改原型链指向,第一次

解决思路
解决自然是取消一次构造函数调用,要取消自然要分析这两次执行,功能上是否有重复。
第一次同样继承了实例和原型属性,第二次执行同样继承了父类的实例属性。
因此第二次满足对父类传参的不可获取性,因此只能思考能否第一次不调用父类构造函数,只继承原型属性。
答案自然是能,前面原型式继承就是这个思路。

寄生组合式继承

顾名思义,寄生指的是将继承原型属性的方法封装在特定方法中,组合的是将构造函数继承组合起来,补充原型式继承的不足。

饶了点,直接看:

  1. function createObj(o) {
  2. function F() { }
  3. F.prototype = o;
  4. return new F();
  5. }
  6. //继承原型属性 即原型式继承
  7. function create(parent, child) {
  8. var f = createObj(parent.prototype);//获取原型对象
  9. child.prototype = f
  10. child.prototype.constructor = child;//增强对象原型,即保持原有constructor指向
  11. }
  12. function Parent(name) {
  13. this.name = name;
  14. this.arr = ['brother', 'sister', 'parents'];
  15. }
  16. Parent.prototype.run = function () {
  17. return this.name;
  18. };
  19. function Child(name, age) {
  20. // 示例属性
  21. Parent.call(this, name);
  22. this.age = age;
  23. }
  24. // 原型属性继承寄生于该方法中
  25. create(Parent.prototype,Child);
  26. var child1 = new Child('trigkit4', 21);

这样沿着发现问题解决问题的思路直到相对完善的继承方式。至于ES的方式本篇就不涉及了。

结束语

唯有厚积,才能薄发,想要心仪的offer,就得准备充裕,夯实基础,切忌似是而非,道理我都懂就是答得不完全,这样跟不懂差别也不太大。不算新的日子里立个flag,每周三个知识点回顾。要去相信,你若盛开蝴蝶自来。

前端面试基础回顾之深入JS继承的更多相关文章

  1. 前端读者 | 前端面试基础手册(HTML+CSS)

    本文来自@羯瑞:希望前端面试基础手册能帮助要找工作的前端小伙伴~~ HTML 前端需要注意哪些SEO? 合理的title.description.keywords:搜索对着三项的权重逐个减小,titl ...

  2. 前端面试基础题:Ajax原理

    Ajax 的原理简单来说是在⽤户和服务器之间加了—个中间层( AJAX 引擎),通过XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后⽤ javascrip t 来操作 D ...

  3. 前端面试题目汇总摘录(JS 基础篇)

    JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...

  4. web前端开发必懂之一:JS继承和继承基础总结

    首先,推荐一篇博客豪情的博客JS提高: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他; 我们会先从JS的基本的设计模 ...

  5. 前端面试题目汇总摘录(JS 基础篇 —— 2018.11.02更新)

    温故而知新,保持空杯心态 JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string type ...

  6. 2019.7月-前端面试总结(H5+C3+JS+ES6+Vue+浏览器)

    第二次面试 HTML HTML5中的新标签,举例一下 canvas绘画,本地离线存储localStorage,sessionStorage,video和audio元素,语义化元素,表单类型(date, ...

  7. 前端面试基础-html篇之H5新特性

    h5的新特性(目前个人所了解)如下 语义化标签 表单新特性 视频(video)和音频(audio) canvas画布 svg绘图 地理定位 为鼠标提供的拖放API webworker (重点)Stor ...

  8. 前端面试基础-html篇之CSS3新特性

    CSS3的新特性(个人总结)如下 过度(transiton) 动画(animation) 形状转换 transform:适用于2D或3D转换的元素 transform-origin:转换元素的位置(围 ...

  9. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

随机推荐

  1. Java容易搞错的知识点

    一.关于Switch 代码: Java代码 1         public class TestSwitch { 2             public static void main(Stri ...

  2. sublime text 3创建新文件插件-AdvanceNewFile

    这里要记录sublime text 3 在创建新文件时安装的插件–AdvanceNewFile ST本来自带的创建新文件的快捷键是ctrl+n.但是用户需要保存时才可修改名称以及文件路径.但是安装完A ...

  3. oracle 用IN来替换OR

    下面的查询可以被更有效率的语句替换: 低效: SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR     LOC_ID = 20 OR     LOC_ID = 3 ...

  4. Android本地数据存储: ASimpleCache

    一:前言 在上一篇博客Android本地数据存储: Reservoir 博客中,我提到,除了Reservoir库,还可以采用ASimpleCache开源库,来实现本地数据存储.昨天并没有仔细的对比Re ...

  5. .net core 文件下载

    public IActionResult Dowanload(string id,int numTypes) { try { var memory = new MemoryStream(): //mo ...

  6. title与h1的区别、b与strong的区别、i与em的区别?

    title与h1的区别 定义: title是网站标题, h1是文章主题 作用: title概括网站信息,可以直接告诉搜索引擎和用户这 个网站是关于什么主题和内容的,是显示在网页Tab栏里的: h1突出 ...

  7. Python--day27--复习

    例1:

  8. vue样式加scoped后不能覆盖组件的原有样式解决方法

    <style scoped> </style> 为了vue页面样式模块化,不对全局造成污染,建议每个页面的style标签加上scoped,表示他的样式只属于当前的页面,父组件的 ...

  9. HashMap和HashSet的使用,区别。集合,Array、Collection(List/Set/Queue)、Map

    HashMap和HashSet的区别 HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collectio ...

  10. Vue 双向数据绑定v-model

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...