手写题之 `new` 与理解 `arguments` 类数组对象

56 阅读3分钟

手写题之 new 与理解 arguments 类数组对象


一、new 操作符的本质作用

在 JavaScript 中,new 是一个用于创建构造函数实例的关键字。它的出现标志着从“函数调用”到“对象构造”的语义转变。当我们执行:

js
编辑
const instance = new Constructor(...args);

JavaScript 引擎会按以下 四个关键步骤 执行:

  1. 创建一个全新的空对象:即 {}

  2. 设置原型链:将新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)指向 Constructor.prototype

  3. 绑定 this 并执行构造函数体:将构造函数中的 this 指向这个新对象,并传入所有参数执行函数逻辑;

  4. 决定返回值

    • 如果构造函数显式返回一个非 null 的对象(如 {}[]new Date() 等),则直接返回该对象;
    • 否则,默认返回新创建的对象

📌 注意:如果构造函数返回的是原始值(如 return 42return '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); // 

屏幕截图 2025-12-09 221802.png

p1p2 行为一致,说明我们成功模拟了 new


三、关于 arguments —— 函数中的类数组对象

1. 什么是 arguments

  • arguments 是一个自动存在于每个非箭头函数作用域中的局部变量

  • 它是一个类数组对象(Array-like Object) ,具有:

    • 数字索引(如 arguments[0]
    • .length 属性
    • 没有 Array.prototype 上的方法(如 pushslicemap 等)

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 的底层运行机制又更近了一步!