[[Prototype]] 与 prototype
[[Prototype]] 与 prototype 是不同的:
凡是被两层方括号括起来的属性都是内部属性,内部属性不能通过“对象.属性”的方式获取。
[[Prototype]] 就是内部属性,它不能通过“对象.[[Prototype]]”的方式获取,但可以通过“对象.__proto__”的方式获取。
prototype 是一般属性,可以直接通过“对象.prototype”的方式获取。
每个对象都有 [[Prototype]] 属性。
我们知道,数组可以通过 new Array() 创建、函数可以通过 new Function() 创建,数组和函数本质上就是对象,因此它们也有 [[Prototype]] 属性。
例如,以下代码的执行结果:
const obj = { };
console.dir(obj);
const arr = [ ];
console.dir(arr);
只有函数和类有 prototype 属性。
使用 typeof 检测一个类的标识符,返回值为 ‘function’,从这儿可以看出,类本质上就是“构造函数”。因为类是函数,函数又是对象,所以,类也是对象。
综上,函数和类既有 [[Prototype]] 属性,又有 prototype 属性。(也有例外,如 Proxy 没有 prototype 属性)
例如,以下代码的执行结果:
function fn() { };
console.dir(fn);
class Clazz { };
console.dir(Clazz);
console.log(typeof Clazz); // 输出结果为:function
原型链 prototype chain
最简单的原型链:
const o = { };
console.log(o.__proto__ === Object.prototype); // 输出结果为:true
console.log(o.__proto__.__proto__); // 输出结果为:null。这说明 Object.prototype 是原型链的最末端。
toString、valueOf 就定义在 Object.prototype 上。
难一点的原型链:
class Person { };
const p = new Person();
console.log(p.__proto__ === Person.prototype); // 输出结果为:true
console.log(p.__proto__.__proto__ === Object.prototype); // 输出结果为:true
Person 是一个类,也即是(构造)函数,因此 Person 是 Function 的一个实例对象(相当于 Function 是类,Person 是 Function 的实例对象),因此,另一条原型链:
console.log(Person.__proto__ === Function.prototype); // 输出结果为:true
console.log(Person.__proto__.__proto__ === Object.prototype); // 输出结果为:true
更难一点,带继承的原型链:
class Person { };
class Child extends Person { }
const c = new Child();
// 原型链一:
console.log(c.__proto__ === Child.prototype); // 输出结果为:true
console.log(c.__proto__.__proto__ === Person.prototype); // 输出结果为:true
console.log(c.__proto__.__proto__.__proto__ === Object.prototype);// 输出结果为:true
// 原型链二:
console.log(Child.__proto__ === Person); // 输出结果为:true
console.log(Child.__proto__.__proto__ === Function.prototype); // 输出结果为:true
console.log(Child.__proto__.__proto__.__proto__ === Object.prototype);// 输出结果为:true
Object、Array、Function、String、Date 等都是类,有 [[Prototype]] 和 prototype 两个属性。类即是构造函数,typeof Object、typeof Array 等的返回值都是 ‘function’。从构造函数这个角度来看,它们和 Person 类没有区别,都是 Function 的实例对象(相当于 Function 是类,Object、Array 等是 Function 的实例对象):
console.log(Object.__proto__ === Function.prototype); // 输出结果为:true
console.log(Array.__proto__ === Function.prototype); // 输出结果为:true
console.log(Function.__proto__ === Function.prototype); // 输出结果为:true。这条特别有意思。
console.log(String.__proto__ === Function.prototype); // 输出结果为:true
console.log(Date.__proto__ === Function.prototype); // 输出结果为:true
call、apply、bind 等方法就定义在 Function.prototype 上。
Object、Array 等也是一个对象,对象上可以定义方法。Object.defineProperty()、Object.assign()、Array.from() 这样的静态方法,就是定义在 Object、Array 上的。
以数组为例,它的两条原型链:
// 原型链一:
const arr = []
console.log(arr.__proto__ === Array.prototype)
console.log(arr.__proto__.__proto__ === Object.prototype)
// 原型链二(同 Person 完全相同):
console.log(Array.__proto__ === Function.prototype); // 输出结果为:true
console.log(Array.__proto__.__proto__ === Object.prototype);// 输出结果为:true
数组的 push、shift、indexOf 等方法就定义在 Array.prototype 上。
数组对象,如上例中的 arr,可以使用 Array.prototype 上的方法,也可以使用 Object.prototype 上的方法,除此之外,没有方法可以使用;
Array 可以使用自己身上的方法,如 Array.from(),也可以使用 Function.prototype 上的方法,如 call()(可以用,但一般用不到),还可以使用 Object.prototype 上的方法(数组对象与 Array 都可以使用 Object.prototype 上的方法),除此之外,没有方法可以使用。