一开始看 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); // true
console.log(Object.prototype.__proto__ === null); // true
这里阐明的问题是:
通过 Animal.prototype.__proto__获取到了上一层级的原型,刚好等于 Object 层级的原型,也就是说 Animal 的上一层级就是 Object。然而 Object 层级再往上就没有所谓层级了,只剩一个 null 了。“null 层级”只有原型 null,没有构造函数。
实现。
最初,分享下对于 JavaScript 原型链学习的集体感触:
(1) 解脱传统继承中子类继承父类的惯性思维。JavaScript 中的构造函数看着像是“类名”,实际上和继承没啥关系,原型链继承靠的是原型。
(2) 解脱“原型”字眼隐含的继承含意。这可能是我本人想当然了,看到到“对象的原型”这种字眼就想当然认为原型是对象的“父类”。实际上,原型和对象应该放到同一层级内探讨;不同层级咱们用原型谈话。