JavaScript中比较难理解的点之【prototype、_ proto _、constructor】,一般不明白这三者关系的同窗都有个毛病:继承也不懂!
深入理解这个知识点不只能够对学习继承有帮助,并且对new关键字、this、性能方面都会有更好的认识。最关键是,几乎做为面试必考题目以前,没啥理由很差好看完~
友情提示:文章相对枯燥且绕,必定要耐心。这篇文章会尽可能按照我这段时间所产生过的疑惑,以懵逼当事人之一的角度去一一解开谜底。javascript
在这个复杂三角恋的关系中,我认为最让人容易混淆的有以下几个点:java
接下来咱们对劈腿三件套【prototype、_ proto _、constructor】 依次从不一样的角度去分析?面试
@TOC浏览器
真正剖析三角恋【prototype、_ proto _、contrutor】是本文最核心的地方,若是你对该知识点有了必定的了解以后,也建议先从这里读起函数
这你应该听过:JS一开始设计的初衷是一个给浏览器作简单交互用的,做者估计也万万没想到时至今日能发展到这么强大!后来的JS慢慢的更像一个Java这样的==面向对象==程序语言。而在Java中有类Class,对象Object,举个例:post
// Java // 一种类: 人 public class Person { String name = '狗子'; } // 男人 public class Man extend Person { int jj = 18; } // 一个具体的男人 Man tony = new Man(); // 托尼老师 - 一个拥有18cm男人的实例
而在Java中,有new、extend关键字来完成了继承的关系,性能
那么问题来了,JS如何实现?
这个问题就是【为什么有?】的答案,为了让js更好的写出面向对象的代码,实现继承。学习
在 ==【prototype、_ proto _、constructor】== 中我认为先解释清楚【construtor、prototype】之间的关系尤其重要,因此,我决定先从【consrtutor】做为切入点展开。测试
若是你学过Java那么就能够找到js模拟类、继承有不少类似的地方
若是你学过Java之类的语言,那么理解这点会很是轻松。由于construtor跟Java当中的同样,就是一个生成具体对象的函数。
代码:字体
function Person (name) { this.name = name; } let person = new Person('js bigname'); // 对比 Java public class Person { String name = ''; // java构造函数 Person (name) { this.name = name; } } Person person = new Person('java bigname');
对吧几乎同样的意思跟写法。
construtor是什么?
这里就能够下定义了,用来建立对象的函数!Js构造函数自己并没有特殊,仅仅是约定首字母大写而已。真正给函数带来不一样的是 ==new==关键字。
那么对象能够是能够建立了,可是如何实现继承?往下看
<font color=#green size=4>假如让你当js语言的设计者,你会如何去实现继承的功能呢?带着这个问题思考会很是有用,而我首先会这么思考:
要实现继承,第一个要解决的问题:
==son如何去访问father的方法?son中拥有father!==
<font color=#0066CC size=4>模拟实现继承效果的伪代码:
function Father (name) { this.name = name; } function Son (name) { this.showName = function () { console.log(this.name || 'empty') }; }; let son = new Son('son1'); son.showName(); // empty
此时Son与Father一点关系都没有,怎么办?
function Father (name) { this.name = name; } function Son (name) { this.father = new Father(name); // 改变的地方 this.showName = function () { console.log(this.name || this.father.name || 'empty') // 改变的地方 }; }; let son = new Son('son1'); son.showName(); // son1
在son中增长父类对象father,若是在son中找不到该属性,则从father中去读取,而father的父类实际上是Object,万物皆Object嘛,逃不掉的。
function Object (firstName) { this.firstName = firstName; } function Father (name , firstName) { this.father = new Object(firstName); this.name = name; } function Son (name, firstName) { this.father = new Father(name , firstName); this.showName = function () { // son自己没有firstName则从father上找,fathter没有则从father的father上找(也就是object) // 这一条的关系链其实也就是日常所讲的原型链 console.log(this.firstName || this.father.firstName || this.father.father.firstName || 'empty') console.log(this.name || this.father.name || 'empty') }; }; let son = new Son('son1', 'liu'); son.showName();
将这一个设想结合到真实的prototype中,son实例中以及father实例中的father对象其实就是一个简易版的prototype!!!它指明了各个对象之间的关系。
这里先作一个基于上面代码总结出来的关系图:
JS中这种继承关系的实现,其实也基本跟上面一致。
prototype是什么?
prototype是在【建立对象过程当中】==自动==为对象添加的【内置属性(对象类型)】;你能够先这么理解,后面还会讲到prototype的真正位置。
_proto _是什么?
proto是指向prototype对象的变量。(他指向的是他的构造函数的prototype)
看代码:
function Person () { this.name = 'tony'; } let person = new Person();
调试模式下观察person对象:
咱们只定义了name属性,但却被自动添加了proto属性,而且指向的prototype是Object类型,这其实就是Js中对象默认自动继承Object的意思了。当执行==person.age==会先从person对象查找,没有则从proto对象中查找。
文章最值钱的内容,前面作了那么多铺垫就是为了引出这里而铺垫
function Person () { this.name = 'tony'; } let person = new Person();
仍是刚才的案例,重点看一下person下的_ proto _内的属性
插入:先梳理一下函数即对象的概念?
Function做为内置对象之一,不要过多的纠结他,只要知道<font color=red size=4>Function是函数也是一个对象,而且继承自Object。构造函数Person是一个函数,继承自Function,但new出来的person是对象,继承自Object
继续------------------------》》
如:person 下的 proto 的constructor指向Person函数
function Person () { this.name = 'tony'; } Person.prototype.age = 12; let person1 = new Person(); let person2 = new Person(); console.log(person1 === person2); // false console.log(person1.__proto__ === person2.__proto__); // true
记住上面的点:而后带着来理解==let person = new Person()==所发生的事情:
<font color=#aaa size=3>_纠正上面紫色字体,应该改成:Object构造函数的protype没有__proto __
这个过程当中有两点须要注意的:
// Object构造函数是指函数对象Object(){},而不是construtor
那么什么是原型链?
上图中荧光绿【1,2,3,4】就是一天原型链!他规定了对象之间的关系,以及变量访问的查找规则。
先作个小结:
JS千辛万苦作了这么多花里花哨的关系处理,无非就是为了达到继承的效果!也就是处理变量访问的规则,例如person.name,假如person自己没有这个属性咋办?就去person的__proto __里面找呗,同理往下推理。这一条查找的路线就是原型链!
到这里应该能理解到,【prototype、_ proto _、constructor】是为了更好地面向对象,实现继承的解决方案
怎么利用这些属性实现继承?
先来第一种
construtor继承:
function Father () { this.name = 'father'; } function Son () { Father.call(this); // 构造继承,将son传递给Father()函数做为上下文,因此this.name实际在son上建立name属性 this.age = 12; } let son = new Son();
看下执行以后son的结构:
看图,在son对象中建立了name属性,而且prototype仍然仍然是Object类型,与Father没有关系。
这种方式的缺点:全部属性都建立在对象当中,试想一下,假如建立了100个son,就会在建立了100次name。若是你想要建立的属性是给全部对象不是独一份,而是共享的怎么办?
回想一下上面提到的,prototype是独一份的,因此只须要将共享的属性建立prototype中,而后将Son的prototype改成指向Father,因此看下一个继承方式。
原型链继承:
function Father () { this.name = 'father'; } // Father在原型中定义公共方法 Father.prototype.getName = function () { console.log(this.name); }; function Son () { this.age = 12; } //name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找 Son.prototype = new Father(); // 原型链继承 let son = new Son();
看下执行以后son的结构:
固然,原型链存在的问题就是,全部son实例对象的prototype指向的father对象都是同一份。因此一旦修改就会影响到全部的对象。
<font color=#0066CC size=3>通常的场景就是,既有须要共享的属性,也有独属的。如Person,每一个人都有年龄,姓名,但值各不相同,因此适合构造函数继承,而获取名称,获取年龄这样的方法每一个人都如出一辙,因此应该定义成公共方法,就应该使用适合链继承。
综上述:
组合继承才是上佳选择,欸,感受有点跑题了,咱们讲的主题可不是这个。后面再考虑作个关于==通常企业是如何使用js继承的==。
其实到这里我感受应该能够结题了,但又想了想,会不会有人看了上面Person与Object的【prototype、_ proto _、constructor】关系,搞多了个Son继承Person就不知道咋回事的了吧。
我想一想应该还真的有,因此我以为再以上面原型链继承以后【prototype、_ proto _、constructor】各对象之间的关系:
这里主要体现了son是如何经过原型链集成以后的关系。
那么到这里就该结题了。
<font color=#0066CC size=3>题外话:这篇包括从查阅资料,写demo测试,到这篇文章的编写前先后后花了将近5天时间。但其中是写文章的时候, 用本身的语言从新组织出来,测试案例,才感受真正有点心照不宣的感受,因此除了看文章以外必定要动手,思考一下,若是要给别人讲解这个知识点你会怎么讲
另外,很感谢很是不错的文章,让我对这个知识点有了不一样的认知: