一开始看MDN的JavaScript指南,没看明确。次要起因是对于构造函数(constructor)、原型(prototype)、对象实例(object, instance)之间关系的形容太少;间接就给我整个原型链让我挺懵逼的。
于是靠百度来搞懂。我感觉先从这三者关系动手,而后回头了解原型链更容易。
相干材料:
(偏重关系)构造函数、对象实例、原型对象三者之间的关系
(偏重原型链)JS重点整顿之JS原型链彻底搞清楚
一、关系:一个层级中构造函数、原型、对象实例的关系
我认为在原型链、层级体系中,最不重要的反而是对象实例。然而,它能起到入口的作用,帮忙咱们拜访、探寻原型、构造函数。同时,要探讨三者关系,就只探讨一个层级内的三者,先忘掉继承、其余层级的事件。
1、准备:构建原型链、层级的“柴犬代码”
上面先用代码构建Shiba->Dog->Animal这条原型链,共三个层级:(咱们称这段代码为“柴犬代码”,能够先跳过柴犬代码,看上面构造函数、原型、对象实例三者互相拜访的代码)
function Animal() { this.type = 'animal'; this.name = 'default' this.age = 0;}Animal.prototype.sayHi = function() { console.log('Hi, I am a(n) ' + this.type + '. My name is ' + this.name + '.');}function Dog() { this.type = 'dog';}Dog.prototype = new Animal();function Shiba(name='default', age=0) { this.type = 'shiba'; this.name = name; this.age = age;}Shiba.prototype = new Dog();
2、从对象实例登程拜访原型、构造函数
咱们申明一个Shiba的对象实例gougou,而后通过gougou拜访Shiba这一层级的原型、构造函数:
var gougou = new Shiba('gougou', 2);console.log(gougou.__proto__); // 原型console.log(gougou.__proto__.constructor); // 构造函数console.log(gougou.__proto__.constructor === Shiba); // true
这里要阐明的问题是:
(1) 对象实例能够通过__proto__拜访原型。(不要问为什么)
(2) 原型能够通过constructor拜访构造函数。(因为最初一行输入为true)
2、从构造函数登程拜访原型
实际上,构造函数和原型之间能够双向拜访:
console.log(Shiba.prototype); // 原型console.log(Shiba.prototype === gougou.__proto__); // true
这里阐明的问题是:
构造函数能够通过prototype拜访原型。(因为第二行输入为true)
3、小结三者的拜访形式
小结:
(1) 对象实例:本人申明的变量名。
(2) 原型:应用对象实例的__proto__属性拜访;如果晓得构造函数名称(类名),能够用构造函数的prototype属性拜访。
(3) 构造函数:如果是自定义的构造函数,本人就晓得构造函数名称;但很多时候是JavaScript内置的构造函数,能够通过原型的constructor属性拜访。
个别状况下,要想拜访以后档次的构造函数、原型,是以对象实例作为入口,先拜访原型,再从原型拜访构造函数。
4、构造函数、原型、对象实例的关系、分工
我认为,构造函数、原型、对象实例三者的拜访形式曾经能阐明他们的关系了。那么,为什么要有这样的构造分工呢?益处是什么?
从作用上来说:
(1) 构造函数居外围、摆布位置,持有原型,并领导对象实例的生成(new)。
(2) 原型一方面帮忙构造函数存储办法,供构造函数生成的对象调用,防止对象实例各自存储一份办法(避免浪费空间);另一方面间接造成原型链,构建了层级构造、继承关系。
(3) 对象实例应该作为原型链、层级体系的附属品。对象实例的属性是构造函数领导生成的;对象实例的办法,是构造函数持有的原型提供的。构造函数能够生产无数个对象实例。
二、原型链:将原型串成链、搭建层级
1、原型链继承:“显式串联”层级
咱们回头看那段柴犬代码。代码中曾经构建好Shiba->Dog->Animal这条原型链,留神到“串起来”的两行代码:
Dog.prototype = new Animal();Shiba.prototype = new Dog();
这里要阐明的问题是:
将Dog层级中的原型,指定为Animal层级的一个对象实例,就造成了Dog->Animal这段原型链,Dog继承了Animal的属性、办法。(Dog对Animal说:拿来吧你)
Shiba和Dog的关系也一样。因而,Shiba->Dog->Animal这条原型链构建好了。
2、持续上溯:堆__proto__就完事
既然以后层级的原型是上一层级的对象实例,那么咱们能够顺藤摸瓜,沿着原型链上溯各个档次直到null。null没有原型,它是原型链的最顶端或者起点。
在柴犬代码中,以对象实例gougou为入口,拜访它的原型gougou.__proto__,这是在Shiba层级;同时,原型又是Dog层级的对象实例,持续通过__proto__属性拜访……如此逐层往上即可。代码如下:
console.log('Shiba层级的原型:\n', gougou.__proto__);console.log('Shiba层级的构造函数:\n', gougou.__proto__.constructor);console.log('Dog层级的原型:\n', gougou.__proto__.__proto__);console.log('Dog层级的构造函数:\n', gougou.__proto__.__proto__.constructor);console.log('Animal层级的原型:\n', gougou.__proto__.__proto__.__proto__);console.log('Animal层级的构造函数:\n', gougou.__proto__.__proto__.__proto__.constructor);
这里曾经有点数不清有多少个__proto__了。反正这里阐明的问题是:
(1) 的确能够沿着原型链往上拜访各个层级的原型,以及构造函数。
(2) 以后层级的原型,就是更高层级的对象实例(柴犬代码中用type属性做了标记,能够分辨是Animal对象实例还是Dog对象实例)。
3、残缺的原型链:上溯至null
留神到Animal层级打印的原型,间接就是一个对象字面量了。实际上,这就是Objct层级的一个对象实例。残缺的原型链是:Shiba->Dog->Animal->Object->null。
不想再从gougou登程写那么多__proto__了,还有另一种形式拜访Animal层级的原型:构造函数Animal的prototype属性。上溯代码如下,这次咱们验证为主:
console.log(Animal.prototype.__proto__ === Object.prototype); // trueconsole.log(Object.prototype.__proto__ === null); // true
这里阐明的问题是:
通过Animal.prototype.__proto__获取到了上一层级的原型,刚好等于Object层级的原型,也就是说Animal的上一层级就是Object。然而Object层级再往上就没有所谓层级了,只剩一个null了。“null层级”只有原型null,没有构造函数。
实现。
最初,分享下对于JavaScript原型链学习的集体感触:
(1) 解脱传统继承中子类继承父类的惯性思维。JavaScript中的构造函数看着像是“类名”,实际上和继承没啥关系,原型链继承靠的是原型。
(2) 解脱“原型”字眼隐含的继承含意。这可能是我本人想当然了,看到到“对象的原型”这种字眼就想当然认为原型是对象的“父类”。实际上,原型和对象应该放到同一层级内探讨;不同层级咱们用原型谈话。