JS new 操作符完整执行过程

JS new 操作符完整执行过程(内存+原型+原型链深度解析)

new 是 JavaScript 中创建实例对象 的核心操作符,它不是简单的语法糖,而是一套内存分配、原型绑定、构造函数执行、返回值处理的完整流程。

我会用最通俗的语言 + 分步拆解 + 内存/原型/原型链变化,把整个过程讲透,你看完就能彻底理解。

一、先明确基础概念(必须懂)

  1. 构造函数 :普通函数,首字母大写(约定),用来生成实例(如 function Person(){} )
  2. 原型 prototype :每个函数都有的属性,是一个对象,存放实例共享的方法/属性
  3. __proto__ :每个对象都有 的隐式原型,指向构造函数的 prototype,是原型链的核心
  4. 原型链 :实例通过 __proto__ 逐级向上查找属性/方法的链条,终点是 Object.prototype,再向上是 null

二、new 操作符 4 步核心执行流程

当执行 const obj = new 构造函数() 时,JS 引擎会严格按 4 步执行 ,每一步都会改变内存 和原型关系:

第 1 步:创建一个全新的空对象

js 复制代码
// 内部执行:创建空对象,分配独立堆内存
const newObj = {}; 
  • 内存变化 :在堆内存中开辟一块新空间,存储这个空对象,栈内存的变量指向这块堆地址
  • 原型/原型链 :此时空对象的 __proto__ 默认指向 Object.prototype(原生默认规则)

第 2 步:绑定原型链(最核心步骤)

将新对象的隐式原型 __proto__ 指向构造函数的显式原型 prototype

js 复制代码
// 内部执行:改写原型指向
newObj.__proto__ = 构造函数.prototype; 
关键变化:
  1. 原型关系 :实例.__proto__ === 构造函数.prototype
  2. 原型链 :新对象不再直接继承 Object,而是继承构造函数的原型,原型链被重新串联
  3. 内存 :只是修改引用地址,不新增内存(__proto__ 是指针,指向堆中的原型对象)

第 3 步:执行构造函数,绑定 this

把构造函数的 this 强制绑定到第 1 步创建的新对象上,并执行构造函数内部代码

js 复制代码
// 内部执行:call 改变 this 指向
构造函数.call(newObj); 
关键变化:
  1. this 指向 :构造函数内的 this 不再指向 window/undefined,指向新对象
  2. 内存 :构造函数内的 this.xxx 赋值,会直接写入新对象的堆内存中
  3. 属性区分 :
    • 实例自身属性:this.name → 存在实例对象内存中
    • 共享属性:构造函数.prototype.say → 存在原型对象中

第 4 步:返回实例对象

  • 如果构造函数没有手动返回对象,JS 自动返回第 1 步创建的新对象
  • 如果构造函数手动返回了对象,则以手动返回的对象为准(基本类型无效,仍返回新对象)
js 复制代码
// 最终赋值
const obj = newObj; 

三、完整代码演示 + 内存/原型/原型链实时变化

我们用一个实际例子,一步步看所有变化:

js 复制代码
// 1. 定义构造函数(函数也是对象,有 prototype 属性)
function Person(name) {
  // 构造函数内部:this 指向 new 创建的新对象
  this.name = name; // 实例自身属性
}

// 2. 给构造函数的原型添加方法(所有实例共享)
Person.prototype.say = function () {
  console.log("我是" + this.name);
};

// 3. 执行 new 操作符
const p1 = new Person("张三");

执行 new Person("张三") 全过程拆解

1. 创建空对象

堆内存生成空对象:{},栈变量 p1 暂存引用

2. 绑定原型(核心)
js 复制代码
p1.__proto__ = Person.prototype;

✅ 原型关系成立 :p1.__proto__ === Person.prototype

3. 执行构造函数,this 绑定
js 复制代码
Person.call(p1, "张三");
// 等价于执行:p1.name = "张三"
  • 内存:空对象变成 { name: "张三" }
  • this:完全指向 p1
4. 返回对象

p1 最终指向这个带属性的对象


四、内存分布可视化(最关键!)

栈内存(存引用地址)

复制代码
p1        → 堆内存地址:0x100
Person    → 堆内存地址:0x200

堆内存(存实际对象)

复制代码
# 0x100:p1 实例对象(自身属性)
{
  name: "张三",
  __proto__: 0x300  // 指向 Person.prototype
}

# 0x200:Person 构造函数对象
{
  prototype: 0x300, // 显式原型
  __proto__: Function.prototype
}

# 0x300:Person.prototype 原型对象
{
  say: function(){},
  __proto__: Object.prototype, // 原型链向上
  constructor: Person
}

# 0x400:Object.prototype 原型链终点
{
  toString: ...,
  __proto__: null
}

五、原型链完整链条(一眼看懂)

执行 new 后,p1 的原型链是:

复制代码
p1 → Person.prototype → Object.prototype → null

验证代码(直接复制运行)

js 复制代码
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(原型链终点)

属性查找规则(原型链的作用)

  1. 找 p1.name → 先查实例自身内存 → 找到
  2. 找 p1.say → 实例自身没有 → 沿 __proto__ 查 Person.prototype → 找到
  3. 找 p1.toString → 自身没有 → 查 Person.prototype 没有 → 查 Object.prototype → 找到

六、特殊情况:构造函数有 return 时的变化

new 第四步的返回规则:

  1. return 基本类型 (数字/字符串/布尔/null/undefined)→ 无效,仍返回新对象
  2. return 对象 ({} / \[\] / 实例)→ 生效 ,覆盖 new 创建的对象,原型链失效

示例:

js 复制代码
function Person() {
  this.name = "张三";
  return { age: 18 }; // 返回对象
}
const p1 = new Person();
console.log(p1); // { age: 18 } (不是 Person 实例,原型链断裂)

七、手动实现 new 操作符(彻底吃透原理)

我们用代码还原 new 的 4 步逻辑,看完你会 100% 理解:

js 复制代码
function myNew(constructor, ...args) {
  // 1. 创建空对象
  const obj = {};
  // 2. 绑定原型:obj.__proto__ = 构造函数.prototype
  Object.setPrototypeOf(obj, constructor.prototype);
  // 3. 执行构造函数,绑定 this
  const res = constructor.apply(obj, args);
  // 4. 返回:如果构造函数返回对象则用它,否则返回 obj
  return res instanceof Object ? res : obj;
}

// 测试
const p2 = myNew(Person, "李四");
p2.say(); // 我是李四
console.log(p2.__proto__ === Person.prototype); // true

总结(核心记忆点)

  1. new 4 步:创建空对象 → 绑定原型 → 绑定 this 执行构造函数 → 返回对象
  2. 原型核心等式 :实例.__proto__ === 构造函数.prototype
  3. 内存:实例存自身属性,原型存共享属性,节省内存
  4. 原型链:实例 → 构造函数原型 → Object原型 → null
  5. return:返回对象会覆盖 new 创建的实例,基本类型无效

这就是 new 操作符内存、原型、原型链的全部底层逻辑,也是 JS 面向对象的核心基石。

相关推荐
y1su3 分钟前
Leetcode 单调栈模板
java·开发语言·数据结构·算法·leetcode
涛涛ing5 分钟前
Navigation API正式Baseline:那个让无数前端头疼的History API,终于可以退休了
前端
2601_962885729 分钟前
如何用 Python 计算索提诺比率和卡玛比率?
开发语言·python
Hilaku12 分钟前
为什么我劝应届生不要去创业公司写前端?
前端·javascript·程序员
郑州光合科技余经理15 分钟前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程
kimnoic22 分钟前
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
开发语言·python
苏辰澈25 分钟前
C++独属于自己的变身:多态
c语言·开发语言·c++·visualstudio·编辑器
谢亮_vipxieliang26 分钟前
PHP 异常处理全解析:try-catch、自定义异常与全局异常处理器
开发语言·php
外收内放28 分钟前
Python基础语法练习题(65)
开发语言·python
data analyse 45629 分钟前
前端自建访问统计,访客标识怎么设计才稳?Cookie、本地存储与设备指纹的取舍
前端·数据分析