Skip to content

手写 new

来源:js/实现/new.html,手写实现 new 操作符的完整逻辑。

共享执行环境:代码块间可互相访问变量和函数定义

此页面启用共享 iframe 模式,所有代码块共享执行环境。

速查导航


new 做了什么

  1. 创建一个全新对象
  2. 执行 [[Prototype]] 链接(obj.__proto__ = Ctor.prototype
  3. 将新对象绑定到函数调用的 this
  4. 如果函数没有返回对象类型,则自动返回新对象

手写实现

js

两种创建方式的区别:

  • obj.__proto__ = Ctor.prototype:直接赋值,原型链顶端为 Object.prototype
  • Object.create(Ctor.prototype):创建干净的空对象,__proto__ 正确指向

返回值处理

js

返回值规则:

  • 返回 Object/Function/Array/Date/RegExp/Error → 使用该返回值
  • 返回基本类型或 undefined → 忽略,返回新对象
  • typeof null === 'object',所以需要额外排除 null

补充知识

ES5 中 arguments 转数组的方式

js
// 方式1:Array.prototype.slice.call(最常用)
var argsArr = [].slice.call(arguments, 1);

// 方式2:Array.from(ES6)
var argsArr = Array.from(arguments);

// 方式3:展开运算符(ES6,但不能在 arguments 上直接用 [...arguments] 获取部分参数)
var argsArr = [...arguments].slice(1);

new.target

ES6 引入的元属性,在构造函数中指向当前构造函数本身:

js

objectFactory 中用 objectFactory.target = ctor 模拟了 new.target 的行为。

早期 call/apply 的 eval 实现

在 spread 语法出现之前,需要用 eval 拼接参数调用:

js
// 历史方案:用 eval 拼接参数列表
Function.prototype.call1 = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  // args = ["arguments[1]", "arguments[2]"]
  // eval 执行: context.fn(arguments[1], arguments[2])
  var result = eval('context.fn(' + args + ')');
  delete context.fn;
  return result;
};

function bar(a, b) { console.log(this, a, b); }
bar.call1({ name: 'test' }, 'hello', 'world');

现在有了 ...args 展开语法,不再需要 eval 这种 hack 方式。