JavaScript 系列 - 原型链

76 阅读2分钟

原型是 JavaScript 对象相互继承特性的机制。

JavaScript 中所有的对象都有一个内置属性,称为它的 [[prototype]](原型)。它本身是一个对象,所以叫做原型对象,原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上 (Object.prototype)。

当你试图访问一个对象的属性时:如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined

Object.prototype 是最基础的原型,所有对象默认都拥有它。Object.prototype 的原型是 null

一个对象的原型并不总是 Object.prototype

原型链.jpg

获取原型

  • Object.getPrototypeOf
  • __proto__
  • p.constructor.prototype

对象内置属性 [[prototype]]

  • Get 操作
  • for in

属性设置屏蔽

[[prootype]] 上是否存在属性

如果你在一个对象中定义了一个属性,而在该对象的原型中定义了一个同名的属性就会出现属性屏蔽

  • 存在
    • 不是只读,创建屏蔽属性
    • 只读 writable:false, 无法创建修改已有属性
    • setter 无法创建修改setter
  • 不存在
    • 创建屏蔽属性

检查一个对象的继承祖先(内省或者反射)

  • object instanceof constructor

    只能处理对象和函数关系

  • isPrototypeOf(object)

设置原型

使用 Object.create

Object.create(obj) 来创建一个以 obj 为原型的新对象

const personPrototype = {
  greet() {
    console.log("hello!");
  },
};

const carl = Object.create(personPrototype);
carl.greet(); // hello!

使用构造函数

所有的函数(除了内置函数)都有一个名为 prototype 的属性不可枚举。当你调用一个函数作为构造函数时,这个属性被设置为新构造对象的原型

因此,如果我们设置一个构造函数的 prototype,我们可以确保所有用该构造函数创建的对象都被赋予该原型

const personPrototype = {
  greet() {
    console.log(`你好,我的名字是 ${this.name}!`);
  },
};

function Person(name) {
  this.name = name;
}

Object.assign(Person.prototype, personPrototype);
// 或
// Person.prototype.greet = personPrototype.greet;
const reuben = new Person("Reuben");
reuben.greet(); // 你好,我的名字是 Reuben!

函数原型

使用 new 调用构造函数

  • 新对象 [[prototype]] 指向函数的 prototype
  • 新对象 constructor 委托给函数的 prototype

函数的 prototype

普通函数默认有一个公有并且不可枚举的属性 constructor

函数之间委托

  • Bar.prototype = Object.create(Foo.prototype)

    创建一个新对象并把新对象内部的 [[Prototype]] 关联到你指定的对象

  • Bar.prototype = Foo.prototype

    • Bar.prototype 直接引用 Foo.prototype对象
    • 相互影响
  • Bar.prototype = new Foo()

    • Foo 的额外功能会影响 Bar
  • Object.setPrototypeOf(..)

创建关联

  • Object.create(null)
    • instanceof 总会返回 false
  • 内部委托

内置函数没有 prototype