(1) 创建一个空的简单对象(即{})
(2)为步骤1新创建的对象添加属性_proto_,该属性连接至构造函数的原型对象
(3)将步骤1新创建的对象作为this的上下文
(4)执行new出来的对象指向的函数,如果该函数没有返回对象,则返回this
示例代码如下:
function Bar(name){
this.name = name;
}
let foo = new Bar('hello');
foo就是我们new出来的对象,它的_proto_指向的就是Bar这个函数,因为Bar函数没有返回值,因此this指向的是new出来的这个对象本身。
2.1 原型链的作用?
个人理解的是,原型链可以帮我们理清楚哪些属性是new出来的对象本身所拥有的,哪些属性是new出来的这个对象通过继承的方式得来的,从而对对象有更清晰的认识,更灵活的使用对象。
对于javaScript的绝大多数对象来说,它们最终的原型都是Object。
2.2 _proto_与prototype
原型链的链接过程也可以称为继承。当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
我们通过一个例子来说明_proto_与prototype之间的关系:
_proto_是对象A的属性,该属性可以将对象追溯到其构造函数对应的上级原型对象B.prototype上。上级原型B的构造函数自带prototype属性,该属性是一个指针,用于指向该构造函数对应的父级原型对象C。因此prototype.xxx就代表对象C上的一个属性。一般没声明的话prototype指向的就是空。
因此,_proto_与prototype之间的关系为:
A._proto_ = B.prototype
A._proto_._proto_ = B.prototype._proto_ = C.prototype
prototype起着一个连接上下文的作用,帮助对象把整个原型链接起来。
2.3 原型链示例
// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 这么写也一样
function f() {
this.a = 1;
this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
// (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。
// 综上,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
2.4 原型链特性:属性遮蔽
原型链中的属性遮蔽指的是:以上述代码为例,如果对象O拥有一个名为a的属性,而函数f的原型上也拥有一个名为a的属性,那我们在访问O.a的属性值时,是以哪一个值为准?因为原型链的回溯是自底向上,从O开始的。因此当O拥有要访问的a属性时,就不需要继续向上遍历了。因此函数f的原型上的那个名为a的属性就不会被遍历到。这就叫做属性遮蔽
2.5 经典面试题
var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){};
var f = new F();
问f拥有a和b哪个属性?
通过在浏览器控制台输出,可以得出结论,只拥有a这一个属性。
因为f._proto_指向的是一个对象而不是Function,而Object的上一级原型也是Object,该Object的prototype有a属性,所以f只拥有a属性
基于类的继承其实也是基于原型链的继承的语法糖,是在ES6阶段提出的新概念。个人认为,基于类的继承把原型链的过程描述的更清晰:
class Animals{
constructor(name) {
this.name = name;
}
greet(){
console.log(`大家好,我叫${this.name}`);
}
}
class Cat extends Animals{
// super用于改变父类的某些属性
// constructor(props) {
// super(props);
// }
}
class Dog extends Animals{// Dog._proto_为Animals
}
const hua = new Cat('花花');//_proto_为Cat,chua._proto_._proto_为Animals
const cao = new Dog('草草');.//_proto_为Dog,cao._proto_._proto_为Animals
hua.greet();//因为子类Cat没有name属性,因此就会向上回溯,调用父类Animals的name属性
cao.greet();//因为子类Dog没有name属性,因此就会向上回溯,调用父类Animals的name属性