五分钟理清JavaScript的原型prototype和__proto__

124 阅读2分钟

1.prototype是什么

prototype是函数的特殊属性,这个属性是函数所独有的(即其他类型不会有这个属性)。该特殊属性可与 JavaScript 的 new 操作符一起使用。

function A(){
    
}

let b = "我是个字符串";
let c = new A();

class D {
}

// 尝试输出该属性
console.log(A.prototype); // {constructor: ƒ}
console.log(b.prototype); // undefined
console.log(c.prototype); // undefined
console.log(D.prototype); // {constructor: ƒ}

可见只有函数才有该属性。

张三:睁眼说瞎话?上面D不是也有输出吗?

:class其实是模拟出的 “类”,只是个语法糖,仍然基于原型,它也是一个函数对象

张三:哦,行吧!

2.__proto__是什么

每个实例对象(object)都有一个私有属性__proto__或者称为[[Prototype]],指向它的构造函数的原型对象(prototype)。注意这个属性是每个object都有的。

function A(){
    
}

let b = "我是个字符串";
let c = new A();

// 输出该属性
console.log(b.__proto__); // String {'', constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
console.log(c.__proto__); // {constructor: ƒ}
console.log(c.__proto__ === A.prototype); // true

// 将prototype展开的构造函数就是A本身也是c的构造函数
A.prototype.constructor === A === c.constructor

根据__proto__定义可知,c.__proto __ === c.constructor.prototype === A.prototype

通常我们所说的原型链就是通过这个属性来访问的。

下面引用MDN的例子解释说明(o.[[Prototype]]就是o.__proto __)

// 让我们从一个函数里创建一个对象 o,它自身拥有属性 a 和 b 的:
let f = function () {
   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

从上述例子可知原型链就是通过__proto__一层一层往上搜索形成的链路,直到顶层Object的__proto__为null止。

总结

  1. prototype属性是函数独有的,指代其原型对象,可以与new操作符搭配创建实例。当遇到xxx.prototype时,其实就是获取xxx的原型对象。
  2. __proto__属性是每个实例对象都拥有的,指向其构造函数的原型对象(即构造函数的prototype)。 xxx.__proto __ === xxx.constructor.prototype

参考文档

MDN继承与原型链