手写题之 new 与理解 arguments 类数组对象
一、new 操作符的本质作用
在 JavaScript 中,new 是一个用于创建构造函数实例的关键字。它的出现标志着从“函数调用”到“对象构造”的语义转变。当我们执行:
js
编辑
const instance = new Constructor(...args);
JavaScript 引擎会按以下 四个关键步骤 执行:
-
创建一个全新的空对象:即
{}; -
设置原型链:将新对象的内部属性
[[Prototype]](可通过__proto__访问)指向Constructor.prototype; -
绑定
this并执行构造函数体:将构造函数中的this指向这个新对象,并传入所有参数执行函数逻辑; -
决定返回值:
- 如果构造函数显式返回一个非
null的对象(如{}、[]、new Date()等),则直接返回该对象; - 否则,默认返回新创建的对象。
- 如果构造函数显式返回一个非
📌 注意:如果构造函数返回的是原始值(如
return 42或return 'hello'),则会被忽略,依然返回新对象。
二、手写模拟 new 的实现
我们可以用一个函数 objectFactory 来手动模拟 new 的行为:
function objectFactory() {
const obj = new Object(); // 1. 创建空对象
const Constructor = [].shift.call(arguments); // 2. 取出第一个参数作为构造函数
// Constructor.apply(obj,[...arguments]); //3. 绑定 this 并执行构造函数
Constructor.apply(obj, arguments); // 3. 绑定 this 并执行构造函数 这两种都可以
obj.__proto__ = Constructor.prototype; // 4. 设置原型链
return obj; // 5. 返回新对象
}
使用示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.species = '人类';
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}, a ${this.age} years old ${this.species}`);
};
const p1 = new Person('荔枝', 18);
const p2 = objectFactory(Person, '荔枝', 18);
console.log(p1.name, p1.age, p1.species); //
console.log(p2.name, p2.age, p2.species); //
✅
p1和p2行为一致,说明我们成功模拟了new。
三、关于 arguments —— 函数中的类数组对象
1. 什么是 arguments?
-
arguments是一个自动存在于每个非箭头函数作用域中的局部变量; -
它是一个类数组对象(Array-like Object) ,具有:
- 数字索引(如
arguments[0]) .length属性- 但没有
Array.prototype上的方法(如push,slice,map等)
- 数字索引(如
2. arguments 的特殊性质
| 特性 | 说明 |
|---|---|
| 动态绑定 | 在非严格模式下,arguments[i] 与对应的形参双向绑定(修改一个会影响另一个) |
| 严格模式差异 | 在严格模式下,arguments 与形参解耦,互不影响 |
| 不可枚举 | arguments 对象的属性是不可枚举的(for...in 不会遍历索引) |
| 无 Symbol.iterator | 因此不能直接用 for...of 遍历(除非先转为数组) |
示例:非严格模式下的“魔法绑定”
js
编辑
function demo(a) {
console.log(a, arguments[0]); // 1, 1
a = 2;
console.log(a, arguments[0]); // 2, 2 ← arguments[0] 也被改了!
}
demo(1);
⚠️ 这种行为容易引发 bug,ES5 严格模式已废除此特性。
3. 将 arguments 转为真数组的几种方式
| 方法 | 兼容性 | 说明 |
|---|---|---|
[].slice.call(arguments) | ES3+ | 经典方案,兼容老浏览器 |
Array.prototype.slice.call(arguments) | 同上 | 更明确 |
Array.from(arguments) | ES6+ | 语义清晰,支持类数组/可迭代对象 |
[...arguments] | ES6+ | 最简洁,但要求 arguments 可迭代(实际可行) |
✅ 推荐现代开发使用
[...arguments]或Array.from。
使用场景示例
js
编辑
function sum() {
// 转为数组后使用 reduce
return [...arguments].reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
❌ 错误示范:
js 编辑 arguments.reduce(...) // TypeError: arguments.reduce is not a function
四、对比总结表(增强版)
| 概念 | 核心机制 | 常见误区 | 最佳实践 |
|---|---|---|---|
new | 创建对象 → 链接原型 → 绑定 this → 返回结果 | 忽略构造函数返回对象的情况 | 手写时务必判断返回值类型 |
myNew | 模拟上述四步 | 用 obj.__proto__ = ...(不推荐) | 优先使用 Object.create 设置原型 |
arguments | 类数组,含实参列表 | 误以为是数组 | 转为数组后再操作 |
| 类数组转数组 | 利用数组方法或扩展运算符 | 直接调用 map/filter | 使用 [...args] 或 Array.from |
五、关键要点速记
- ✅
new的四步曲:创对象 → 设原型 → 绑this→ 看返回; - ✅ 构造函数若返回对象,
new会优先返回它; - ✅
arguments是类数组,不是数组,不能直接用数组方法; - ✅ 箭头函数没有自己的
arguments,需通过外层函数访问; - ✅ 现代 JS 推荐使用 剩余参数(
...args)替代arguments,更清晰安全:
💡 掌握这些,你对 JavaScript 的底层运行机制又更近了一步!