原型机制是理解对象继承和属性查找的关键

核心概念

  • 每个对象都有 __proto__ 属性,函数也是对象
  • 每个函数(除箭头函数)都有一个 prototype 属性
  • 实例对象的 __proto__ 指向构造函数的 prototype
    • 即:实例对象.__proto__ === 构造函数.prototype
  • prototype 是一个对象,有一个默认的 constructor 属性,指向其构造函数
    • 构造函数.prototype = { constructor: 构造函数, __proto__: XXX }
    • 即:构造函数.prototype.constructor === 构造函数

注意点

实例无法修改原型的字段

实例无法直接修改(新增、删除、修改)原型上的字段(属性和方法),而是修改自身字段。除非显示调用 实例.__proto__ 进行修改

function F() {}
F.prototype.tag = 'F'
F.prototype.printTag = function() { console.log('[TAG]', this.tag) }
 
const f = new F()
console.log(f.tag) // 'F'
f.printTag() // '[TAG] F'
 
delete f.tag // true
// 无法删除原型上的字段
// f:
// {
//   __proto__: { tag: 'F', printTag: function(), constructor: F, __proto: XXX }
// }
 
f.tag = 'i am f'
f.printTag() // '[TAG] i am f'
// 无法修改原型上的字段,只是在自身上修改字段
// f:
// {
//   tag: 'i am f',
//   __proto__: { tag: 'F', printTag: function(), constructor: F, __proto: XXX }
// }
// 调用 printTag() 时,找 this.tag,在自身上找到了,就不向上找原型链了
 
delete f.tag // true
f.printTag() // '[TAG] F'
// 可以删除自身的字段
// f:
// {
//   __proto__: { tag: 'F', printTag: function(), constructor: F, __proto: XXX }
// }

new 时指向

实例对象.__proto__ 指向 构造函数.prototype,是在 new 时发生的。之后如果修改了 构造函数.prototype 指向,之前 new 的实例对象的 __proto__ 还指向原来的 构造函数.prototype

function F() {}
F.prototype.tag = 'F'
F.prototype.printTag = function() { console.log('[TAG]', this.tag) }
 
const f1 = new F()
 
F.prototype = {
  constructor: F,
  tag: 'other F',
  printTag() { console.log('[--TAG--]', this.tag) }
}
 
const f2 = new F()
 
console.log(f1.tag) // 'F'
f1.printTag() // '[TAG] F'
console.log(f2.tag) // 'other F'
f2.printTag() // '[--TAG--] other F'

new 逻辑手写:

Function.prototype.new = function(...args) {
  // 1. 创建实例对象
  const instance = {}
  // 2. 绑定原型
  instance.__proto__ = this.prototype
  // 或:const instance = Object.create(this.prototype)
 
  // 3. 模拟 ES6 的 new.target
  instance.newTarget = this
  // 构造函数通过 this.newTarget 访问
 
  // 4. 执行构造函数,并改变 this 指向为实例对象
  const res = this.apply(instance, args)
 
  // 5. 构造函数返回非原始类型,则返回该对象
  if (res !== null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  // 否则返回实例对象
  return instance
}

访问 [[Prototype]]

  • 实例对象.__proto__ 是非标准字段,所以不建议使用
  • 应该使用 Object.getPrototypeOf(实例对象)Reflect.getPrototypeOf(实例对象) 获取
  • 使用 Object.setPrototypeOf(实例对象)Reflect.setPrototypeOf(实例对象) 修改

一览图

两个注意点:

  1. Object.prototype.__proto__ === null
    • 理解:Object.prototype 是对象,所以其 __proto__ 应该指向 Object.prototype,但这样就死循环了,所以显式规定 Object.prototype.__proto__ === null
  2. Function.__proto__ === Function.prototype
    • 理解:Function 是函数,所以 Function.__proto__ === Function.prototype