JavaScript 学习笔记:手写 new
一、new 是什么
new 的核心作用,是执行一个构造函数,并返回一个实例对象。这个对象能够访问构造函数内部的属性,也能顺着原型链访问构造函数 prototype 上的方法。
比如:
new 的核心作用,是执行一个构造函数,并返回一个实例对象。这个对象能够访问构造函数内部的属性,也能顺着原型链访问构造函数 prototype 上的方法。
比如:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log("Hi, I am " + this.name);
};
var p = new Person("muyu", 18);
p.sayHi(); // Hi, I am muyu
在这个过程中,new 默默在背后做了四件事:
创建一个全新的空对象。
链接原型链:将这个新对象的内部属性 __proto__ 链接到构造函数的 prototype 对象上。
绑定 this 并执行:将构造函数内部的 this 绑定到这个新对象上,并执行构造函数内部的代码(为新对象添加属性)。
处理返回值:如果构造函数没有显式返回一个对象,则默认返回这个新创建的对象。
所以,一个完整的 myNew 模拟函数,至少要完美复现这四步。
function myNew(constructor) {
// 1. 创建一个空对象
var obj = {};
// 2. 获取传入的参数
var args = Array.prototype.slice.call(arguments, 1);
// 3. 将构造函数的 this 指向这个新对象,并执行
constructor.apply(obj, args);
// 4. 返回这个新对象
return obj;
}
// ================= 测试用例 =================
function Person
这个版本干了什么:
我们手动创建了一个 {}。
利用 Array.prototype.slice.call(arguments, 1) 拿到了除构造函数之外的其余参数。
利用 apply 强行把 Person 内部的 this 掰到了空对象 obj 上。
存在的问题: 完全没有处理原型链。返回的 obj 就是一个普通的对象(它的原型指向 Object.prototype),导致实例无法访问 Person.prototype 上的方法(比如 sayHi)。
为了解决原型链丢失的问题,我们需要在对象创建阶段,就把它和构造函数的原型绑定起来。
function myNew(constructor) {
// 使用 Object.create 直接创建一个带有正确原型链的空对象
var args = Array.prototype.slice.call(arguments, 1);
var obj = Object.create(constructor.prototype);
constructor.apply(obj, args);
return obj;
}
// ================= 测试用例 =================
function
这个版本干了什么:
var obj = {},改用 Object.create(constructor.prototype)。这行代码极其优雅,它直接创建了一个空对象,并且把这个空对象的隐式原型 __proto__ 指向了传入的 prototype。存在的问题: 忽略了 JavaScript 中 new 的一个边界特性——返回值拦截。如果原生的构造函数显式 return 了一个引用类型(对象、数组、函数等),那么 new 表达式的最终结果应该是这个被 return 的对象,而不是引擎偷偷创建的那个实例;如果 return 的是基本数据类型(如字符串、数字),则忽略该返回值,依然返回实例。我们现在的版本无脑返回了 obj。
function myNew(constructor) {
// 1. 边界拦截:确保传入的第一个参数是一个函数
if (typeof constructor !== "function") {
throw new TypeError("myNew function the first argument must be a function");
}
// 2. 准备参数和实例对象
var args = Array.prototype.slice.call(arguments, 1);
var obj = Object.create(constructor.prototype)
这个版本干了什么:
增加了 typeof constructor !== "function" 的安全校验。
接收了 constructor.apply 的执行结果 result。
严谨地判断了 result 是否为 object(注意排除 null)或者是 function。如果满足,说明构造函数想要“狸猫换太子”,我们就把 result 返回出去;如果不满足,说明返回的是基本类型或没写 return,我们乖乖返回创建的实例 obj。
存在的问题: 无。这已经涵盖了原生 new 绝大多数的核心逻辑。
在实现 myNew 的过程中,我们串联起了以下几个重要的 JavaScript 知识:
原型链继承 (Prototype Chain):通过 Object.create() 深刻理解了实例的 __proto__ 是如何与构造函数的 prototype 产生羁绊的。
动态上下文 (Dynamic Context):再次利用 apply 方法,证明了 this 的指向是可以在函数执行时被动态篡改的。
参数类数组 (Array-like Object):复习了通过 Array.prototype.slice.call(arguments) 将类数组转化为真数组的经典技巧。
引用类型与基本类型的区别:在处理构造函数返回值时,深刻体会到了 JavaScript 引擎对值类型和引用类型的区别对待。