Appearance
手写 new
来源:
js/实现/new.html,手写实现new操作符的完整逻辑。
共享执行环境:代码块间可互相访问变量和函数定义
此页面启用共享 iframe 模式,所有代码块共享执行环境。
速查导航
new 做了什么
- 创建一个全新对象
- 执行
[[Prototype]]链接(obj.__proto__ = Ctor.prototype) - 将新对象绑定到函数调用的
this - 如果函数没有返回对象类型,则自动返回新对象
手写实现
js
两种创建方式的区别:
obj.__proto__ = Ctor.prototype:直接赋值,原型链顶端为Object.prototypeObject.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 方式。