深入理解JavaScript原型链与继承
栏目: JavaScript · 发布时间: 5年前
内容简介:原型链一直都是一个在JS中比较让人费解的知识点,但是在面试中经常会被问到,这里我来做一个总结吧,首先引入一个关系图:1.构造函数: JS中所有函数都可以作为构造函数,前提是被new操作符操作;2.实例: parent1 接收了new Parent(),parent1可以称之为实例;
原型链一直都是一个在JS中比较让人费解的知识点,但是在面试中经常会被问到,这里我来做一个总结吧,首先引入一个关系图:
一.要理解原型链,首先可以从上图开始入手,图中有三个概念:
1.构造函数: JS中所有函数都可以作为构造函数,前提是被new操作符操作;
function Parent(){ this.name = 'parent'; } //这是一个JS函数 var parent1 = new Parent() //这里函数被new操作符操作了,所以我们称Parent为一个构造函数; 复制代码
2.实例: parent1 接收了new Parent(),parent1可以称之为实例;
3.原型对象: 构造函数有一个prototype属性,这个属性会初始化一个原型对象;
二.弄清楚了这三个概念,下面我们来说说这三个概念的关系(参考上图):
1.通过new操作符作用于JS函数,那么就得到了一个实例;
2.构造函数会初始化一个prototype,这个prototype会初始化一个原型对象,那么原型对象是怎么知道自己是被哪个函数初始化的呢?原来原型对象会有一个constructor属性,这个属性指向了构造函数;
3.那么关键来了实例对象是怎么和原型对象关联起来的呢?原来实例对象会有一个__proto__属性,这个属性指向了该实例对象的构造函数对应的原型对象;
4.假如我们从一个对象中去找一个属性name,如果在当前对象中没有找到,那么会通过__proto__属性一直往上找,直到找到Object对象还没有找到name属性,才证明这个属性name是不存在,否则只要找到了,那么这个属性就是存在的,从这里可以看出JS对象和上级的关系就像一条链条一样,这个称之为原型链;
5.如果看到这里还没理解原型链,可以从下面我要说到继承来理解,因为原型继承就是基于原型链;
三.new操作符的工作原理
废话不多说,直接上代码 var newObj = function(func){ var t = {} t.prototype = func.prototype var o = t var k =func.call(o); if(typeof k === 'object'){ return k; }else{ return o; } } var parent1 = newObj(Parent)等价于new操作 1.一个新对象被创建,它继承自func.prototype。 2.构造函数func 被执行,执行的时候,相应的参数会被传入,同时上下文(this) 会被指定为这个新实例。 3.如果构造函数返回了一个新对象,那么这个对象会取代整个new出来的结果,如果构造函数没有返回对象, 那么new出来的结果为步骤1创建的对象。 复制代码
继承
一.构造函数实现继承(构造继承)
function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child{ Parent.call(this);//apply this.type = 'parent2'; } Parent.prototype.id = '1' var child1 = new Child() console.log(child1.name)//parent1 console.log(child1.id)//undefined //以下代码看完继承方式2,再回过头来看 var child2 = new Child(); child1.play[0] = 2; console.log(child2.play)//[1,2,3] 从上面构造继承的代码可以看出,构造继承实现了继承, 打印出来父级的name属性,但是实例对象并没有访问到父级原型上面到属性; 复制代码
二.原型链实现继承
function Parent(){ this.name = 'parent' this.play = [1,2,3] } function Child(){ this.type = 'child'; } Child.prototype = new Parent(); Parent.prototype.id = '1'; var child1 = new Child(); console.log(child1.name)//parent1 console.log(child1.id)//1 从这里可以看出,原型继承弥补了构造继承到缺点,继承了原型上到属性; 但是下面再做一个操作: var child2 = new Child(); child1.play[0] = 2; console.log(child2.play)//[2,2,3] 这里我只是改变了实例对象child1到play数组,但是实例打印实例对象child2到paly数组,发现也跟着变化 了,所以可以得出结论,原型链继承引用类型到属性,在所有实例对象上面改变该属性,所有实例对象该属性都会 变化,这样肯定就存在问题,现在我们回到继承方式1(构造继承),会发现构造继承不会存在这个问题,所以 其实构造继承和原型链继承完全可以互补,由此我们引入第三种继承方式; 额外解释:这里通过一个原型链继承,我们再来回顾一下对原型链的理解,上面代码,我们进行了一个操作: Child.prototype = new Parent(); 这个操作把父类的实例赋值给子类的原型,然后结合上面原型链的关系图,我们再来理一下(为了阅读方便,复 制上图到此处): 复制代码
现在我们可以把图中到实例看成child1,首先如果要找child1实例对象中的name属性,那么我首先到Child本身去找,发现没有找到name属性,因为Child函数里面只有一个type属性,那么通过__proto__找到Child的原型对象,而刚才我们做了一个操作:
Child.prototype = new Parent(); 这个操作把父类的实例给了Child的原型,所以通过这个我们就可以找到父级的name,这就是原型链,一层一层的,像一个链条;
三.组合继承
function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child{ Parent.call(this);//apply this.type = 'parent2'; } Child.prototype = new Parent(); Parent.prototype.id = '1' var child1 = new Child() console.log(child1.name)//parent1 console.log(child1.id)//1 var child2 = new Child(); child1.play[0] = 2; console.log(child2.play)//[1,2,3] 从上面代码可以看出,组合继承就是把构造继承和原型链继承组合在一起,把他们的优势互补,从而弥补了各自的 缺点;那么组合继承就完美了吗?我们继续思考,从代码中可以发现,我们调用了两次Parent函数,一次是 new Parent(),一次是Parent.call(this),是否可以优化呢?我们引入第四种继承方式; 复制代码
四.组合继承(优化1)
function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child{ Parent.call(this);//apply this.type = 'parent2'; } Child.prototype = Parent.prototype;//这里改变了 Parent.prototype.id = '1' var child1 = new Child() console.log(child1.name)//parent1 console.log(child1.id)//1 var child2 = new Child(); child1.play[0] = 2; console.log(child2.play)//[1,2,3] 我们改成Child.prototype = Parent.prototype,这样就只调用一次Parent了,解决了继承方式3的问题, 好吧,我们继续思考,这样就没有问题了吗,我们做如下操作: console.log(Child.prototype.constructor)//Parent 这里我们打印发现Child的原型的构造器成了Parent,按照我们的理解应该是Child,这就造成了构造器紊乱, 所以我们引入第五种继承优化 复制代码
五.组合继承(优化2)
function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child{ Parent.call(this);//apply this.type = 'parent2'; } Child.prototype = Parent.prototype; Child.prototype.constructor = Child//这里改变了 Parent.prototype.id = '1' var child1 = new Child() console.log(child1.name)//parent1 console.log(child1.id)//1 var child2 = new Child(); child1.play[0] = 2; console.log(child2.play)//[1,2,3] 现在我们打印 console.log(Child.prototype.constructor)//Child 这里就解决了问题,但是我们继续打印 console.log(Parent.prototype.constructor)//Child 发现父类的构造器也出现了紊乱,所有我们通过一个中间值来解决这个问题,最终版本为: function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child{ Parent.call(this);//apply this.type = 'parent2'; } var obj = {}; obj.prototype = Parent.prototype; Child.prototype = obj; //上面三行代码也可以简化成Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child console.log(Child.prototype.constructor)//Child console.log(Parent.prototype.constructor)//Parent 用一个中间obj,完美解决了这个问题复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 构造函数、原型、原型链、继承
- 前端基本功(七):javascript中的继承(原型、原型链、继承的实现方式)
- JavaScript原型链继承
- JavaScript 继承和原型链
- 彻底弄懂JS原型与继承
- 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程语言实现模式
Terence Parr / 李袁奎、尧飘海 / 华中科技大学出版社 / 2012-3-20 / 72.00元
《编程语言实现模式》旨在传授开发语言应用(工具)的经验和理念,帮助读者构建自己的语言应用。这里的语言应用并非特指用编译器或解释器实现编程语言,而是泛指任何处理、分析、翻译输入文件的程序,比如配置文件读取器、数据读取器、模型驱动的代码生成器、源码到源码的翻译器、源码分析工具、解释器,以及诸如此类的工具。为此,作者举例讲解已有语言应用的工作机制,拆解、归纳出31种易于理解且常用的设计模式(每种都包括通......一起来看看 《编程语言实现模式》 这本书的介绍吧!
html转js在线工具
html转js在线工具
RGB HSV 转换
RGB HSV 互转工具