JavaScript 中 new 操作符的原理与手写实现

JavaScript 中 new 操作符的原理与手写实现详解

在 JavaScript 的编程世界里,new操作符是构建对象实例的核心工具。当我们写下new Person()这样简洁的代码时,其背后究竟隐藏着怎样精密的运作流程?接下来,就让我们深入剖析new操作符的工作原理,并一步步实现一个具备相似功能的自定义函数。

一、new 操作符如何创建对象

创建空对象

new操作符被调用时,JavaScript 引擎会立刻生成一个空的对象,形式上表现为{} 。这个空对象就像一座刚刚奠基的建筑,后续构造函数中的属性与方法会如同砖瓦一般,逐步为其增添功能与特性,它是最终实例对象的初始形态。例如,当执行new Car()时,引擎会率先创建一个空对象,为后续定义汽车的属性(如颜色、品牌)和方法(如启动、加速)做好准备。
2.

绑定原型链

新创建的空对象会与构造函数的原型对象建立起紧密的联系。具体而言,新对象的__proto__属性会被指向构造函数的prototype原型对象,从而构建起一条至关重要的原型链 。这条原型链是 JavaScript 实现继承机制的关键,它使得实例对象能够访问原型对象上定义的属性和方法。以Person构造函数为例,若在其原型上定义sayHello方法:

javascript 复制代码
function Person() {}
Person.prototype.sayHello = function() {
 	console.log("Hello!");
};
javascript 复制代码
const p1 = new Person() // p1是Person的实例
p1.sayHello()

当使用new Person()创建实例时,该实例便能调用sayHello方法,正是原型链发挥了作用,让实例拥有了从原型继承而来的能力。
3.

绑定 this 指向

在执行构造函数之前,JavaScript 会将构造函数内部的this关键字精准地指向新创建的空对象 。这一操作意义非凡,构造函数中所有对this的操作,无论是为对象添加属性,还是定义方法,都将围绕这个新对象展开。例如在Person构造函数中使用this.name = "Bob",实际上就是在为新创建的对象赋予name属性,并赋值为"Bob",塑造对象的个性化特征。

javascript 复制代码
function Person(name) {
  // 经过new之后 此时 this 已经指向了一个新的空对象 {} 
  this.name = name; // 给这个新对象添加属性 name
}

const p1 = new Person("Bob");
console.log(p1.name); // 输出 "Bob"

执行构造函数

构造函数内部的代码执行,初始化新对象 。由于构造函数的this指向的新对象,所以可以根据传入的参数,为新对象设置不同的属性值,定义专属的方法。比如,我们可以通过参数传递来初始化Person对象的姓名和年龄:

javascript 复制代码
function Person(name, age) {
	this.name = name;
	this.age = age;
}
const bob = new Person("Bob", 30);

​ 通过这种方式,每个新创建的Person对象都能拥有独特的属性值,展现出不同的状态。

返回实例对象

构造函数执行结束后,会涉及到返回值的处理,这一步决定了最终返回的对象形态。这里存在两种情况:

  • 返回值为对象 :若构造函数显式返回一个对象(例如return { job: "Engineer" }),new操作符会尊重这个返回值,直接返回该对象,之前创建的空对象将被舍弃。这赋予了构造函数极大的灵活性,开发者可以根据需求控制最终返回的实例形态。

    javascript 复制代码
    function Person(name,age){
        this.name = name
        this.age = age
        return {
            lable:'lable'
    	}
    }
    const p1 = new Person()
    console.log(p1 instanceof Person) // false
    console.log(p1)  // { lable: 'lable' }
  • 返回值为基本数据类型 :当构造函数返回基本数据类型(如return 10return "string")时,new操作符会无视这个返回值,仍然返回最初创建的新对象。这是因为new操作符的核心目标是创建对象实例,基本数据类型的返回值与这一目标不符。

    javascript 复制代码
    function Person(name, age) {
      this.name = name;
      this.age = age;
      return 1
    }
    
    Person.prototype.say = function () {
      console.log("name", this.name, "age", this.age);
    };
    
    const p = new Person("张三", 18);
    console.log(p instanceof Person) // true
    console.log(p); //Person { name: '张三', age: 18 }

二、手写实现 new 功能

  1. 基于对new操作符原理的理解,我们可以动手编写一个函数,模拟new的功能。以下是具体的实现代码:

    javascript 复制代码
    function objectFactory() {
      //创建空对象
      let obj = {};
    
      // 获取argument中第一个参数  由于argument没有shift方法 这里将this指向argument
      let Constructor = [].shift.call(arguments);
    
      //绑定原型链
      obj.__proto__ = Constructor.prototype;
        
       // 绑定this指向  调用apply 让Constructor的this指向obj
      let result = Constructor.apply(obj, arguments); // 执行构造函数this 指向obj
    
    
      //   return typeof result === 'object'?result:obj;
      // 若构造函数返回的是一个不为null的对象则返回 result 否则返回obj
      // 若是一个null对象 result || obj 返回的是obj
      return typeof ret === "object" ? result || obj : obj;
    }

    在这个**objectFactory**函数中:

    • 首先创建一个空对象obj,对应new操作符创建空对象的步骤。
    • 接着通过**obj.__proto__ = Constructor.prototype建立原型链关系**,确保实例能够继承原型上的属性和方法。
    • 然后使用apply方法将构造函数的this指向obj ,并传入参数执行构造函数,执行结果存储在result中。
    • 最后根据result的类型进行判断,如果result是对象类型且不为null,则返回result否则返回最初创建的newObject ,实现与new操作符一致的返回逻辑。
  2. 测试

    javascript 复制代码
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.say = function () {
      console.log("name", this.name, "age", this.age);
    };
    
    //与这个相同 let p = new Person("张三",18)
    let p = objectFactory(Person, "张三", 18);
    console.log(p); // Person { name: '张三', age: 18 } (这里由Person原型链指向 代表是谁的实例化)
    console.log(p instanceof Person); // 构造时有  obj.__proto__ = Constructor.prototype; 所以为true
    p.say();
相关推荐
TimelessHaze13 分钟前
🔥 一文掌握 JavaScript 数组方法(2025 全面指南):分类解析 × 业务场景 × 易错点
前端·javascript·trae
EndingCoder1 小时前
React 19 与 Next.js:利用最新 React 功能
前端·javascript·后端·react.js·前端框架·全栈·next.js
ITMan彪叔1 小时前
Nodejs打包 Webpack 中 __dirname 的正确配置与行为解析
javascript·后端
风中凌乱的L1 小时前
vue 一键打包上传
前端·javascript·vue.js
GHOME1 小时前
Vue2与Vue3响应式原理对比
前端·vue.js·面试
张元清2 小时前
useMergedRefs: 组件封装必不可少的自定义Hook
前端·javascript·面试
openInula前端开源社区2 小时前
【openInula茶话会】第四期:openInula API2.0编译器原理
前端·javascript
sorryhc2 小时前
【AI解读源码系列】ant design mobile——CapsuleTabs胶囊选项卡
前端·javascript·react.js
巧克力793 小时前
js数组去重的方法
javascript·面试
Sheeep3 小时前
Cursor 的使用之学会使用 cursor rule
javascript·后端