自己实现JavaScript的new

new操作符是JavaScript中实例化对象时使用的操作符。自己动手实现一个new,能帮我们理解它背后的机理。

前情提要

本问题讨论基于如下类定义(人有名字,“说名字”在原型上):

function Person(name) {
  this.name = name
}
Person.prototype.sayMyName = function () {
  console.log(this.name)
}

原理分析

我们知道,对象是形如{k: v}的键值对的集合,其中v可为各种类型。

如果用空对象x = {}调用构造函数,则构造函数做的this.name = name,就相当于x.name = name,即给x新增了name属性并赋值。这便是在”构造“(或者说构建、填充)这一对象的过程。

x调用构造函数后,x确实有了name属性,但却不一定是Person——小猫小狗也可以有名字。因此,我们需要指定x原型为Person,相当于说明x是按Person的坯子制造出来的。

在指定了原型后,自然而然的,x也可以调用原型链上的方法如sayMyName了。

代码实现

根据上面的分析,不难写出如下代码:

function New(cons, ...args) { // cons为构造函数,...args为构造函数的参数
  let x = {}
  cons.apply(x, args) // 应用构造函数,填充属性
  x.__proto__ = cons.prototype // 修改原型。注意对象上的是__proto__,构造函数(类)上的是prototype
  // 注:形如__xxx__的属性显然是不希望被直接修改的属性,因此平时没有必要,就不要操作__proto__
  return x
}

进行测试:

let p = New(Person, 'Bob')
console.log(p) // { name: 'Bob', ... }
console.log(p instanceof Person) // true

一点小问题

虽然很少这么做,但还是要提到:构造函数中可以中途返回值!

如果这个值是基本类型,则单纯退出构造函数,不再执行下面的构造操作,也就是这个对象只构造到当前状态;如果这个值是引用数据类型,例如一个对象,则构造的结果就是这个对象,之前的所有构造操作都失去作用。

因此,代码实现中可能出现应用x = cons.apply({}, args)的情况,可以考虑进行特别处理。

posted @ 2020-04-21 00:03  z0gSh1u  阅读(204)  评论(0编辑  收藏  举报