前言

面向对象编程是每次面试必问的知识点,而前端js如何实现继承每次命中率高达80%

这不,近两天我们面试时候,同事就问道面试者此问题,但是,不论之前自己做的回答,还是面试者的回答,基本都不太令人满意

很大的原因是多数时候前端并不需要实现继承,就jquery来说也基本上是一码到底,没有实现继承,据我所知,也就prototype与ext实现过继承

所以继承对前端来说似乎不太适用

近两年来情况有所变化,SPA的兴起以及前端逻辑的复杂化,让前端代码愈发的多,愈发的重,所以继承慢慢的进入了一些初级一点的前端视野

所以,好好的了解如何实现继承,继承的几个用法,是非常有意义的,就算只是为面试都是很有用的

文章只是个人见解,有误请提出,demo未做检测,有误请提出

实现继承

当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象

而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)

每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法

每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype

① 我们通过isPrototypeOf来确定某个对象是不是我的原型
② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true

var Person = function (name, age) {this.name = name;this.age = age;
};
Person.prototype.getName = function () {return this.name;
};
var y = new Person('叶小钗', 30);

通俗一点来说,prototype是一模板,新创建对象就是对他一个拷贝,里面的属性或者方法都会赋值给实例

这里说是模板赋值其实不太合理,反正由类产生的所有实例的__proto__都会共享一个prototype,这里我做一个例子

我们在断点情况下是没有name2属性的,但是我们如果在断点下加上这个代码的话,a.name2,就有值了

Klass.prototype.name2 = '222';

所以,这里说模板,不如说是指针指向,都是共享一个对象;继承的情况的话就是这样

(function () {var Person = function (name) {this.name = name;};//Person.prototype = {};//这句将影响十分具有constructor属性Person.prototype.getName = function () {return this.name;};var Student = function (name, sex, id) {this.name = name || '无名氏';this.sex = sex || '不明';this.id = id || '未填'; //学号
    };//相当于将其prototype复制了一次,若是包含constructor的话将指向PersonStudent.prototype = new Person();Student.prototype.getId = function () {return this.id;}var y = new Person();var s = new Student;var s1 = y instanceof Person;var s2 = s instanceof Student;var s3 = s instanceof Person;var s4 = Student.prototype.constructor === Person;var s5 = Student.constructor === Person;var s6 = Student.constructor === Function;var s = '';
})();

View Code

prototype实现继承

我们在具体项目中,真正复杂一点的项目可能就会对继承进行封装,让自己更好的使用,我们下面就来看看prototype怎么干的

PS:我这里做了一点小的修改:

  1 var Class = (function () {
  2   function subclass() { };
  3
  4   //我们构建一个类可以传两个参数,第一个为需要继承的类,
  5   //如果没有的话就一定会有第二个对象,就是其原型属性以及方法,其中initialize为构造函数的入口
  6   function create() {
  7
  8     //此处两个属性一个是被继承的类,一个为原型方法
  9     var parent = null;
 10     var properties = $A(arguments);
 11
 12     if (Object.isFunction(properties[0]))
 13       parent = properties.shift();
 14
 15     //新建类,这个类最好会被返回,构造函数入口为initialize原型方法
 16     function klass() {
 17       this.initialize.apply(this, arguments);
 18     }
 19
 20     //扩展klass类的“实例”对象(非原型),为其增加addMethods方法
 21     Object.extend(klass, Class.Methods);
 22
 23     //为其指定父类,没有就为空
 24     klass.superclass = parent;
 25
 26     //其子类集合(require情况下不一定准确)
 27     klass.subclasses = [];
 28
 29     //如果存在父类就需要继承
 30     if (parent) {
 31       //新建一个空类用以继承,其存在的意义是不希望构造函数被执行
 32       //比如 klass.prototype = new parent;就会执行其initialize方法
 33       subclass.prototype = parent.prototype;
 34       klass.prototype = new subclass;
 35       parent.subclasses.push(klass);
 36     }
 37
 38     //遍历对象(其实此处这样做意义不大,我们可以强制最多给两个参数)
 39     //注意,此处为一个难点,需要谨慎,进入addMethods
 40     for (var i = 0, length = properties.length; i < length; i++)
 41       klass.addMethods(properties[i]);
 42
 43     if (!klass.prototype.initialize)
 44       klass.prototype.initialize = Prototype.emptyFunction;
 45
 46     klass.prototype.constructor = klass;
 47     return klass;
 48   }
 49
 50   /**
 51   由于作者考虑情况比较全面会想到这种情况
 52   var Klass = Class.create(parent,{},{});
 53   后面每一个对象的遍历都会执行这里的方法,我们平时需要将这里直接限定最多两个参数
 54   */
 55   function addMethods(source) {
 56
 57     //当前类的父类原型链,前面被记录下来了
 58     var ancestor = this.superclass && this.superclass.prototype;
 59
 60     //将当前对象的键值取出转换为数组
 61     var properties = Object.keys(source);
 62
 63     //依次遍历各个属性,填充当前类(klass)原型链
 64     for (var i = 0, length = properties.length; i < length; i++) {
 65
 66       //property为键,value为值,比如getName: function(){}的关系
 67       var property = properties[i], value = source[property];
 68
 69       /****************
 70       这里有个难点,用于处理子类中具有和父类原型链同名的情况,仍然可以调用父类函数的方案(我这里只能说牛B)
 71       如果一个子类有一个参数叫做$super的话,这里就可以处理了,这里判断一个函数的参数使用了正则表达式,正如
 72       var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
 73       ****************/
 74       if (ancestor && Object.isFunction(value) && value.argumentNames()[0] == "$super") {
 75
 76         //将当前函数存下来
 77         var method = value;
 78         /****************
 79         第一步:
 80
 81         这里是这段代码最难的地方,需要好好阅读,我们首先将里面一块单独提出
 82         value = (function (m) {
 83         return function () { return ancestor[m].apply(this, arguments); };
 84         })(property)
 85         这里很牛B的构建了一个闭包(将方法名传了进去),任何直接由其父类原型中取出了相关方法
 86         然后内部返回了该函数,此时其实重写了value,value
 87         ***这里***有一个特别需要注意的地方是,此处的apply方法不是固定写在class上的,是根据调用环境变化的,具体各位自己去理解了
 88
 89         第二步:
 90         首先value被重新成其父类的调用了,此处可以简单理解为(仅仅为理解)$super=value
 91         然后下面会调用wrap操作vaule将,我们本次方法进行操作
 92         wrap: function (wrapper) {
 93           var __method = this;
 94           return function () {
 95             return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
 96           }
 97         }
 98         可以看出,他其实就是将第一个方法(value)作为了自己方法名的第一个参数了,后面的参数不必理会
 99         ****************/
100         value = (function (m) {
101           return function () { return ancestor[m].apply(this, arguments); };
102         })(property).wrap(method);
103       }
104       //为其原型赋值
105       this.prototype[property] = value;
106     }
107     return this;
108   }
109
110   return {
111     create: create,
112     Methods: {
113       addMethods: addMethods
114     }
115   };
116 })();

下面来一个简单的例子:

 1 var Person = Class.create({
 2   initialize: function (name) {
 3     this.name = name;
 4   },
 5   getName: function () {
 6     console.log('我是父类');
 7     return  this.name;
 8   },
 9   getAge: function () {
10     return this.age;
11   }
12 });
13
14 var Employee = Class.create(Person, {
15   initialize: function ($super, name, age) {
16     $super(name);
17     this.age = age;
18   },
19   getName: function ($super) {
20     return '我是子类:' + $super();
21   }
22 });
23
24 var C = Class.create(Employee, {
25   getAge: function ($super) {
26     return $super();
27   }
28 });
29
30 var y = new C("叶小钗", 25);
31 console.log(y.getName() + ': ' + y.getAge());

View Code

这里,我们根据自己的需求重写写了下继承相关代码,表现基本与上述一致,各位可以自己试试

PS:当然如果有问题请指出

简化prototype继承

 1 var arr = [];
 2 var slice = arr.slice;
 3
 4 function create() {
 5   if (arguments.length == 0 || arguments.length > 2) throw '参数错误';
 6
 7   var parent = null;
 8   //将参数转换为数组
 9   var properties = slice.call(arguments);
10
11   //如果第一个参数为类(function),那么就将之取出
12   if (typeof properties[0] === 'function')
13     parent = properties.shift();
14   properties = properties[0];
15
16   function klass() {
17     this.initialize.apply(this, arguments);
18   }
19
20   klass.superclass = parent;
21   klass.subclasses = [];
22
23   if (parent) {
24     var subclass = function () { };
25     subclass.prototype = parent.prototype;
26     klass.prototype = new subclass;
27     parent.subclasses.push(klass);
28   }
29
30   var ancestor = klass.superclass && klass.superclass.prototype;
31   for (var k in properties) {
32     var value = properties[k];
33
34     //满足条件就重写
35     if (ancestor && typeof value == 'function') {
36       var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
37       //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
38       if (argslist[0] === '$super' && ancestor[k]) {
39         value = (function (methodName, fn) {
40           return function () {
41             var scope = this;
42             var args = [function () {
43               return ancestor[methodName].apply(scope, arguments);
44             } ];
45             return fn.apply(this, args.concat(slice.call(arguments)));
46           };
47         })(k, value);
48       }
49     }
50
51     klass.prototype[k] = value;
52   }
53
54   if (!klass.prototype.initialize)
55     klass.prototype.initialize = function () { };
56
57   klass.prototype.constructor = klass;
58
59   return klass;
60 }

如此,我们就完成了自己的继承了

实战继承

知道原型可以实现继承是皮毛,知道各个库是怎样干的也只是入门
因为,要在项目中用到才能算真正掌握继承,这里我们便来点实战的小例子
这里我写一个简单的view用于下面各种继承

 1 var AbstractView = create({
 2   initialize: function (opts) {
 3     opts = opts || {};
 4     this.wrapper = opts.wrapper || $('body');
 5
 6     //事件集合
 7     this.events = {};
 8
 9     this.isCreate = false;
10
11   },
12   on: function (type, fn) {
13     if (!this.events[type]) this.events[type] = [];
14     this.events[type].push(fn);
15   },
16   trigger: function (type) {
17     if (!this.events[type]) return;
18     for (var i = 0, len = this.events[type].length; i < len; i++) {
19       this.events[type][i].call(this)
20     }
21   },
22   createHtml: function () {
23     throw '必须重写';
24   },
25   create: function () {
26     this.root = $(this.createHtml());
27     this.wrapper.append(this.root);
28     this.trigger('onCreate');
29     this.isCreate = true;
30   },
31   show: function () {
32     if (!this.isCreate) this.create();
33     this.root.show();
34     this.trigger('onShow');
35   },
36   hide: function () {
37     this.root.hide();
38   }
39 });
40
41 var Alert = create(AbstractView, {
42
43   createHtml: function () {
44     return '<div class="alert">这里是alert框</div>';
45   }
46 });
47
48 var AlertTitle = create(Alert, {
49   initialize: function ($super) {
50     this.title = '';
51     $super();
52
53   },
54   createHtml: function () {
55     return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>';
56   },
57
58   setTitle: function (title) {
59     this.title = title;
60     this.root.find('h2').html(title)
61   }
62
63 });
64
65 var AlertTitleButton = create(AlertTitle, {
66   initialize: function ($super) {
67     this.title = '';
68     $super();
69
70     this.on('onShow', function () {
71       var bt = $('<input type="button" value="点击我" />');
72       bt.click($.proxy(function () {
73         alert(this.title);
74       }, this));
75       this.root.append(bt)
76     });
77   }
78 });
79
80 var v1 = new Alert();
81 v1.show();
82
83 var v2 = new AlertTitle();
84 v2.show();
85 v2.setTitle('我是标题');
86
87 var v3 = new AlertTitleButton();
88 v3.show();
89 v3.setTitle('我是标题和按钮的alert');

View Code

http://sandbox.runjs.cn/show/ddlp7nlt

结语

希望这次继承的文章对各位有帮助,此外文中错误请指出

亲爱的道友们,我其实在我们团队只是中等水平,我们上海携程无线是一个优秀的团队,
如果你现在正在找工作,请加入我们吧!!!
在我们团队,你可以肆无忌惮的黑自己的老大,你会体会到和谐的氛围,当然妹子多多的!要求:靠谱前端,吃苦耐劳,待遇刚刚的!!!需要的朋友可以私信我顺便推广道友的jquery技术交流群:239147101

【一次面试】再谈javascript中的继承相关推荐

  1. 移花接木—— 再谈javascript中的 call 与 apply

    2019独角兽企业重金招聘Python工程师标准>>> 在JavaScript中,call 和 apply 是Function对象自带的两个方法,这两个方法的主要作用是改变函数中的t ...

  2. 浅谈javascript中原型(prototype)、构造函数、对象实例及三者之间的关系

    转自:http://www.cnblogs.com/zhangwei412827/archive/2012/12/14/2816263.html 浅谈javascript中原型(prototype). ...

  3. Unity教程之再谈Unity中的优化技术

    这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体 这一步主要是为了针对性能瓶颈中的" ...

  4. html 滚动条 scrolltop scrollheight,浅谈JavaScript中scrollTop、scrollHeight、offsetTop、offsetHeight...

    浅谈JavaScript中scrollTop.scrollHeight.offsetTop.offsetHeight 发布时间:2020-07-17 09:27:20 来源:亿速云 阅读:223 作者 ...

  5. 浅谈JavaScript中的NaN

    浅谈JavaScript中的NaN NaN概念以及简单案例 追寻的纯粹该拥有自己的本质.-JC.F 什么是NaN? NaN:NaN(Not a Number),它表示不是数字,但是仍是数值类型. Na ...

  6. 02.Javascript中的继承----Inherits

    02.Javascript中的继承----Inherits 本文不再过多的阐述OOP中继承的概念,只是用原生的Javascript代码来模拟类继承(不是对象扩展) 类继承:inherits 假设有已定 ...

  7. JavaScript中的继承入门

    正统的面相对象的语言都会提供extend之类的方法用于出来类的继承,但Javascript并不提供extend方法,在Javascript中使用继承需要用点技巧. Javascript中的实例的属性和 ...

  8. JavaScript学习13 JavaScript中的继承

    JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript">//继承第一种方式: ...

  9. javascript中的继承方式

    javascript中的继承方式有好几种. 下面分别举例供大家参考学习: 1.function parent() { this.x=1; } function child() { var instan ...

最新文章

  1. 安全36计 你需要了解的那些安全术语
  2. python ansible_Ansible Python API | linux系统运维
  3. 025_JDK的hashCode方法
  4. 从后台传给前台的url字符串值的注意事项
  5. IT Monitor
  6. ffmpeg解封装及解码实战
  7. fork join框架使用_Java:使用Fork / Join框架的Mergesort
  8. 求数的绝对值一定是正数_人教版初中数学七年级上册绝对值公开课优质课课件教案视频...
  9. copying mysql status_mysql慢查询copying to tmp table
  10. android.mk官网介绍,转载:Android.mk语法介绍
  11. 如何从javascript检索GET参数? [重复]
  12. 基于nginx搭建直播,web播放视频方案
  13. MongoDB安装配置(Windows)
  14. 洛谷1012 拼数
  15. 1分钟了解微信收款商业版
  16. iPhone连接Mac电脑总是断开
  17. Simulink仿真入门到精通(十七) Simulink代码生成技术详解
  18. 蚂蚁金服区块链已开出近60万张医疗电子票据,市民报销看病更方便!
  19. radio选中触发事件以及获取选中的值
  20. android 背景 投影,智能投影新篇章,android与投影完美结合-神画Y1

热门文章

  1. 在吗?认识一下JWT(JSON Web Token) ?
  2. 125万奖金!“中国GPT-3”赛事来了
  3. 开源助力!武汉新型冠状病毒防疫开源信息收集平台
  4. 3分钟看完一篇论文,这个AI文本生成模型把今年NeurIPS 2300+篇总结了个遍
  5. ICLR 2022初审你得了多少分?平均4.93浮动,预测6分才被接收
  6. Sci-Hub十岁生日解封,超233万新论文被放出!总数达到近8800万
  7. 微信头像可以加皇冠翅膀了,好看!
  8. 顶会paper越来越多,我该怎么看?
  9. 阿里P9大佬总结必备的算法和工具,被10万算法工程师点赞
  10. 扒出了3867篇论文中的3万个基准测试结果,他们发现追求SOTA其实没什么意义