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
操作符会尊重这个返回值,直接返回该对象,之前创建的空对象将被舍弃。这赋予了构造函数极大的灵活性,开发者可以根据需求控制最终返回的实例形态。javascriptfunction 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 10
或return "string"
)时,new
操作符会无视这个返回值,仍然返回最初创建的新对象。这是因为new
操作符的核心目标是创建对象实例,基本数据类型的返回值与这一目标不符。javascriptfunction 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 功能
-
基于对
new
操作符原理的理解,我们可以动手编写一个函数,模拟new
的功能。以下是具体的实现代码:javascriptfunction 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
操作符一致的返回逻辑。
- 首先创建一个空对象
-
测试
javascriptfunction 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();