JavaScript 之原型与原型链

102 阅读3分钟

原型

prototype 属性

在JavaScript中,每个函数创建时都有一个prototype属性

// 构造函数
function Person (){}
var person = new Person()
console.log(Person.prototype);
// 普通函数
function fn(){}
console.log(fn.prototype);

image.png

如上代码,构造函数Person和普通函数fn都具有prototype属性

[[Prototype]]__proto__

JavaScript的对象中都包含了一个[[Prototype]]内部属性(除了null),这个属性所指向的就是该对象的原型。

[[Prototype]] 作为对象的内部属性,是不能被直接访问的,从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__

function Person (){}
var person = new Person()
console.log(person.__proto__ === Person.prototype);  // true

如上代码,实例对象person__proto__属性等于构造函数Person的原型

实例与构造函数原型的关系:

image.png

constructor

在原型对象中,还包含一个constructor属性,这个属性对应创建所有指向该原型的实例的构造函数

function Person (){}
var person = new Person()
console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__.constructor === Person); // true

实例对象、构造函数和构造函数原型之间的关系: image.png

原型的原型

Person.prototype 是原型对象,既然是对象它也有[[Prototype]]这个属性,也就是有它的原型对象。

function Person (){}
var person = new Person()
console.log(Person.prototype.__proto__);

image.png

这里我们打印了 Person.prototype.__proto__,发现 constructor指向Object()构造函数。说明Person.prototype的原型等于Object.prototype
同时这个原型对象里有个属性__proto__等于null,表示Object.prototype 它就是原型的顶端了。

所以:

Person.prototype.__proto__.constructor === Object  // true
Person.prototype.__proto__ === Object.prototype    // true
Object.prototype.__proto__ === null                // null

现在我们的关系图再次更新一下:

image.png

函数原型

从上面的关系图发现,构造函数为什么有[[Prototype]]属性呢?
因为每个 JavaScript 函数实际上都是一个 Function 对象。就是函数是Function()构造函数的实例对象

function Person() {}
function fn() {}
console.log(fn.constructor === Function) // true
console.log(Person.constructor === Function) // true
console.log(Person.__proto__ === Function.prototype) // true
console.log(Function.prototype.constructor === Function) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true

image.png

如上代码,Person()Object()Function()构造函数都是Function的实例对象,所以它们__proto__指向Function.prototype
Function.prototype.constructor又指向了构造函数本身Function()
Function.prototype__proto__指向了原型对象Object.prototype

现在我们可以得出一些结论:

  • 每个函数创建时都有一个prototype属性
  • 每个对象中都包含了一个[[Prototype]]内部属性(除了null),这个属性所指向的就是该对象的原型。
  • 因为函数是Function()构造函数的实例对象,所以有[[Prototype]]属性
  • 在原型对象中,还包含一个constructor属性,这个属性对应创建所有指向该原型的实例的构造函数
  • 在原型链中,原型链顶端是 Object.prototype
  • Function.prototype继承Object.prototype而产生
  • 一切对象继承自Object.prototype,一切函数对象继承自Function.prototype

原型链

当我们访问一个对象属性时,如果在这个对象上找不到,就会从它的__proto__属性找到它的原型对象,如果原型对象上也没找到,就继续如此查找,直到找到终点 Object.prototype,这个就是原型链

function Person(){}

Person.prototype.name = 'jack'
Person.prototype.sayName = function(){
  console.log(this.name);
}

var p1 = new Person()
p1.sayName() // jack

p1.name = 'alice'
p1.sayName() // alice

delete p1.name
p1.sayName() // jack

console.log(p1.toString()); // [object Object]

以上代码执行过程:第一次指向sayName()方法,

  1. 执行sayName()方法,实例对象p1不存在name属性,在构造函数Person.prototype上找到name属性,输出 jack
  2. 给实例对象p1添加属性name,此时输出 alice
  3. 删除了实例对象的name属性,在实例上找不到name又找到Person.prototype上了,输出 jack
  4. 调用toString()方法,实例对象上和Person.prototype上都不存在,继续找,在Object.prototype上面找到后执行