原型与原型链及 new 的模拟实现

189 阅读3分钟

1. 原型与原型链

1. 原型和原型链是什么

const arr = [1, 2, 3, 4, 5]
arr.forEach(item => console.log(item)) //=> 1 2 3 4 5

const reg = /abc/
console.log(reg.test("abc")) //=> true

在上面的例子中, 明明 forEachArray.prototype 上的方法, 为什么 arr 可以调用呢? 而 test 也是 RegExp.prototype 上的方法, 可是 reg 也可以调用. 这些我们平时写代码的时候都会遇到的现象, 其实就是由原型和原型链引起的

1. 一个对象的原型就是其 [[Prototype]]

当我们定义一个 arr 对象的时候, 这个 arr 对象会自动生成一个 [[Prototype]] 属性, 并且指向其构造函数的 prototype 对象. 这个 [[Prototype]] 就是 arr 对象的原型

  • 每个对象都会有其自己的原型: [[Prototype]]
  • 每个构造函数都会有其对应的构造原型: prototype 对象, 该 prototype 还有一个 constructor 属性指向其构造函数: 比如 Object.prototype.constructor === Object

2. 原型链就是由原型 [[Prototype]] 连成的链

image.png 当尝试从 obj 中获取某属性时, js 首先会在 obj 中查找该属性, 如果没有找到的话, 就会从 obj 的原型中继续找, 再没有找到的话, 就继续从 obj 的原型的原型 中查找... 直到找到对应的属性返回其值, 或者是找到了 Object.prototype(原型链的根)对象, 在该对象中也没有发现对应的属性时, 会返回 undefined

代码描述

function find(obj, property) {
  while (obj !== null) {
    if (obj.hasOwnProperty(property)) {
      // 如果属性就在对象本身上, 那么直接返回该属性的值
      return obj[property]
    } else {
      // 否则就沿着原型链向上查找
      obj = Object.getPrototypeOf(obj) 
    }
  }
  return undefined // 最终没有找到属性 property, 则返回 undefined
}

2. 原型规则

  1. 哪个构造函数创建的对象, 该对象的 [[Prototype]] 就指向那个构造函数的 prototype:
    • 函数是 Function 构造的, 所以函数的 [[Prototype]] 指向 Function.prototype
    • 数组是 Array 构造的, 所以数组的 [[Prototype]] 指向 Array.prototype
    • ......
  2. 由于 [[Prototype]] 是对象, 那么 [[Prototype]] 之间指来指去, 最终会指向 Object.prototype

image.png

2. 实例对象的创建过程

通过对一个构造函数使用 new 操作符, 我们可以获得由该构造函数所创建的实例对象
如: const arr = new Array(100) , 我们可以获得一个 Array 类的实例对象

2.1 当我们在 new 构造函数的时候, 我们在做什么

以我们自己写的一个构造函数为例

function Servant(name, gender) {
  this.name = name
  this.gender = gender
}

const altria = new Servant("altria", "female")
graph TD
A1(构造一个实例对象 obj, <br>并使 obj 的原型指向 Servant.prototype) --> A2(将 Servant 的 this 指向 obj) --> A3(执行 Servant 函数) --> A4(Servant 的返回值是否是引用类型值)
A4 --YES--> A5(返回该引用类型值)
A4 --NO--> A6(返回 obj)
console.log(altria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(altria) === Servant.prototype) //=> true

当我们将 Servant 构造函数中的返回值设置为一个引用类型值

function Servant(name, gender) {
  this.name = name
  this.gender = gender
  return [name, gender]
}

const altria = new Servant("altria", "female")
console.log(altria) //=> ['altria', 'female']

2.2 对 new 操作符的模拟实现

const myNew = (Func, ...args) => {
  const obj = Object.setPrototypeOf({}, Func.prototype)
  const ret = Func.call(obj, ...args)
  return (typeof ret === "object" && ret !== null || typeof ret === "function") ? ret : obj
}

比较一下自己的 myNew 函数与 JS 提供的 new 操作符使用后的结果

const altria = new Servant("altria", "female")
const myAltria = myNew(Servant, "altria", "female")

console.log(altria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(altria) === Servant.prototype) //=> true

console.log(myAltria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(myAltria) === Servant.prototype) //=> true