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 面向对象的核心基石。

相关推荐
TE-茶叶蛋1 小时前
结合登录页-PHP基础知识点解析
android·开发语言·php
无巧不成书02181 小时前
Java包(package)全解:从定义、使用到避坑,新手零基础入门到实战
java·开发语言·package·java包
robch2 小时前
python3 -m http.server 8001直接启动web服务类似 nginx
前端·nginx·http
吴声子夜歌2 小时前
ES6——数组的扩展详解
前端·javascript·es6
WangJunXiang62 小时前
Python网络编程
开发语言·网络·python
guhy fighting2 小时前
new Map,Array.from,Object.entries的作用以及使用方法
开发语言·前端·javascript
lsx2024062 小时前
操作系统统计
开发语言
大漠_w3cpluscom2 小时前
CSS 技巧:CSS 单位使用指南
前端
_下雨天.2 小时前
Python 网络编程
开发语言·网络·python