JavaScript中比较难理解的点之【prototype、_ _ proto _ _、constructor】,通常不明白这三者关系的同学都有个毛病:继承也不懂!
深刻理解这个知识点不仅可以对学习继承有帮助,而且对new关键字、this、性能方面都会有更好的认识。最关键是,几乎作为面试必考题目之前,没啥理由不好好看完~
友情提示:文章相对枯燥且绕,一定要耐心。这篇文章会尽量按照我这段时间所产生过的疑惑,以懵逼当事人之一的角度去一一解开谜底。
在这个复杂三角恋的关系中,我认为最让人容易混淆的有如下几个点:
- Function及Object在原型链中所扮演的角色
- 这三者存在相互引用,且呈链式这样骚里骚气的关系
- Function及Object这两个内置对象的特殊性
- 函数即对象
- construtor、prototype、__proto __具体存在哪个对象当中
接下来我们对劈腿三件套【prototype、_ _ proto _ _、constructor】 依次从不同的角度去分析?
- 为何有?
- 是什么?
- 有何用?
- 怎么用?
目录
真正剖析三角恋【prototype、_ proto _、contrutor】是本文最核心的地方,如果你对该知识点有了一定的了解之后,也建议先从这里读起
为何有?
这你应该听过:JS一开始设计的初衷是一个给浏览器做简单交互用的,作者估计也万万没想到时至今日能发展到这么强大!后来的JS慢慢的更像一个Java这样的面向对象程序语言。而在Java中有类Class,对象Object,举个例:
// 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模拟类、继承有很多相似的地方
construtor-构造函数
如果你学过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关键字。
那么对象可以是可以创建了,但是如何实现继承?往下看
prototype-原型对象
假如让你当js语言的设计者,你会如何去实现继承的功能呢?
带着这个问题思考会非常有用,而我首先会这么思考:
要实现继承,第一个要解决的问题:
son如何去访问father的方法?son中拥有father!
模拟实现继承效果的伪代码:
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 _是什么?
proto是指向prototype对象的变量。(他指向的是他的构造函数的prototype)
看代码:
function Person () {
this.name = 'tony';
}
let person = new Person();
调试模式下观察person对象:
我们只定义了name属性,但却被自动添加了proto属性,并且指向的prototype是Object类型,这其实就是Js中对象默认自动继承Object的意思了。当执行person.age会先从person对象查找,没有则从proto对象中查找。
真正剖析三角恋【prototype、_ proto _、contrutor】
文章最值钱的内容,前面做了那么多铺垫就是为了引出这里而铺垫
function Person () {
this.name = 'tony';
}
let person = new Person();
还是刚才的案例,重点看一下person下的_ _ proto_ _内的属性
插入:先梳理一下函数即对象的概念?
Function作为内置对象之一,不要过多的纠结他,只要知道Function是函数也是一个对象,并且继承自Object。构造函数Person是一个函数,继承自Function,但new出来的person是对象,继承自Object
继续------------------------》》
-
第一点:construtor始终指向创建自己的函数(记住了)
如:person 下的 _ _ proto _ _ 的constructor指向Person函数 -
第二点:_ _proto__始终指向prototype【prototype存在于构造函数Person下,这一点也非常非常重要】
-
第三点:Person产生的对象可以有很多个,但所产生的protorype只有一个
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
- 第四点:prototype存在于函数对象中【Person()】
- 第五点:_proto _存在于对象中【person】
- 第六点:函数即对象,所以函数也有__proto_ _
记住上面的点:然后带着来理解==let person = new Person()==所发生的事情:
_纠正上面紫色字体,应该改为:Object构造函数的protype没有__proto __
这个过程中有两点需要注意的:
- Function函数的__proto __指向自己的prototype
- Object函数的prototype没有__proto __,所以Object就是原型链的终点
总结
- Function函数的__proto __指向自己的prototype
- Object对象没有__proto __,所以Object就是原型链的终点
- __proto __指向prototype
- prototype才有construtor,construtor指向构造函数,并且构造函数才有prototype,这相互嵌套的关系理解起来可能会容易混淆
- prototype只有一个(Person的只有一个)
- 除了Object构造函数没有prototype外,对象都有__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对象都是同一份。所以一旦修改就会影响到所有的对象。
一般的场景就是,既有需要共享的属性,也有独属的。如Person,每个人都有年龄,姓名,但值各不相同,所以适合构造函数继承,而获取名称,获取年龄这样的方法每个人都一模一样,所以应该定义成公共方法,就应该使用适合链继承。
综上述:
组合继承才是上佳选择,欸,感觉有点跑题了,我们讲的主题可不是这个。后面再考虑做个关于一般企业是如何使用js继承的。
其实到这里我感觉应该可以结题了,但又想了想,会不会有人看了上面Person与Object的【prototype、_ _ proto _ _、constructor】关系,搞多了个Son继承Person就不知道咋回事的了吧。
我想想应该还真的有,所以我觉得再以上面原型链继承之后【prototype、_ _ proto _ _、constructor】各对象之间的关系:
这里主要体现了son是如何通过原型链集成之后的关系。
那么到这里就该结题了。
题外话:这篇包括从查阅资料,写demo测试,到这篇文章的编写前前后后花了将近5天时间。但其中是写文章的时候, 用自己的语言重新组织出来,测试案例,才感觉真正有点心领神会的感觉,所以除了看文章之外一定要动手,思考一下,如果要给别人讲解这个知识点你会怎么讲
另外,很感谢非常不错的文章,让我对这个知识点有了不同的认知: