之前写有关于 前端面试系列-JS篇-this指向 的文章时,挖了一个有关于手写 new 的坑,秉承着挖坑尽快填的理念,今天就来填坑了
new 运算符我们通常用来实例化对象,在 JavaScript 中已经屡见不鲜了,但都明白它内部的原理嘛?
今天我们就一起来看看 new 运算符的原理
我们要模拟 new 运算符,首先我们要知道它都做了什么
原理
new 关键字用于创建一个新对象,并将该对象的原型指向构造函数的原型。
它的实现原理可以简单描述为以下几个步骤:
1. 创建一个新对象
创建出一个新对象,来作为第三步构造函数的函数上下文
2. 将新对象的原型指向构造函数的原型
将新对象的 proto 属性会被设置为构造函数的 prototype 属性,从而将新对象的原型指向构造函数的原型。
这样,新对象就可以继承构造函数的属性和方法,从而实现了面向对象编程中的继承机制。
3. 使用新对象作为构造函数的上下文(即 this),执行构造函数
构造函数会被执行,并将新对象作为函数上下文(即 this)传递给构造函数。在构造函数中,我们可以为新对象设置属性和方法,从而对新对象进行初始化。
4. 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象
- 构造函数的返回值如果是基础数据类型(包括
undefined和null),那么返回新创建的对象 - 如果返回的是函数,数组或者对象等复杂数据类型,那么返回构造函数的对象
实现
基础版本
function myNew(constructor, ...args) {
//判断是否为函数
if(typeof constructor !== 'function') {
throw 'constructor must be a function';
}
// 用new Object() 的方式新建了一个对象obj
var obj = new Object()
// 给该对象的__proto__赋值为constructor.prototype,即设置原型链
obj.__proto__ = fn.prototype.prototype
// 调用构造函数,将新对象作为上下文
const result = constructor.apply(obj, args);
// 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
return result instanceof Object ? result : obj;
}
这个方法接受两个参数:构造函数和任意数量的参数。它首先使用 new Object() 方法创建一个新对象,并将该对象的 proto 属性指向构造函数的 prototype 属性。然后,它使用 apply() 方法调用构造函数,并将新对象作为上下文。如果构造函数返回了一个对象,则返回该对象;否则,返回新对象。
instanceof是 JavaScript 中的一个运算符,用于检查一个对象是否是另一个对象的实例。它的语法如下:其中,
object是要检查的对象,constructor是构造函数。如果object是constructor的实例,则返回true,否则返回false。
instanceof运算符的工作原理是基于 JavaScript 中的原型链。在 JavaScript 中,每个对象都有一个内部属性[[Prototype]],也称为原型。原型可以是另一个对象,如果一个对象的原型是另一个对象,那么这个对象就可以访问原型对象的属性和方法。
这里面还有很多种版本,但无外乎是对第一步和第二步的改写或者简写
够用版本
function myNew(constructor, ...args) {
//判断是否为函数
if(typeof constructor !== 'function') {
throw 'constructor must be a function';
}
// 用new Object() 的方式新建了一个对象obj
var obj = new Object()
// 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
Object.setPrototypeOf(obj, constructor.prototype)
// 调用构造函数,将新对象作为上下文
const result = constructor.apply(obj, args);
// 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
return result instanceof Object ? result : obj;
}
这里和第一个版本唯一区别就是使用setPrototypeOf将 __proto__ 替换掉了
__proto__是一个非标准的属性,它允许您访问一个对象的原型。在 ECMAScript 6 中,推荐使用Object.getPrototypeOf方法来访问一个对象的原型,Object.setPrototypeOf方法来设置一个对象的原型使用
__proto__属性来设置原型可能会导致一些兼容性问题,并且在一些 JavaScript 引擎中,该属性可能被禁用。
进阶版本
function myNew(constructor, ...args) {
//判断是否为函数
if(typeof constructor !== 'function') {
throw 'constructor must be a function';
}
// 创建一个新对象,原型指向构造函数的原型
const obj = Object.create(constructor.prototype);
// 调用构造函数,将新对象作为上下文
const result = constructor.apply(obj, args);
// 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
return result instanceof Object ? result : obj;
}
这里是将通过 Object.create 将创建对象和修改原型合并为一步,使用它直接就能生成一个__proto__指向构造函数的 prototype 的新对象
Object.create是一个用于创建新对象并将其原型设置为现有对象的方法。使用
Object.create方法,您可以创建一个新的对象,该对象的原型指向传入的另一个对象。这样,新对象就可以继承传入对象的属性和方法。
这个方法接受两个参数:构造函数和任意数量的参数。它首先使用 Object.create() 方法创建一个新对象,并将该对象的原型指向构造函数的原型。然后,它使用 apply() 方法调用构造函数,并将新对象作为上下文。如果构造函数返回了一个对象,则返回该对象;否则,返回新对象。这个方法模拟了 JavaScript 中 new 操作符的行为。
当我们使用 new 关键字创建一个对象时,JavaScript 引擎会执行以上的步骤,以创建一个新的对象,并将该对象的原型指向构造函数的 prototype 属性。这样,新对象就可以继承构造函数的属性和方法,从而实现了面向对象编程中的继承机制。
测试
这里面使用进阶版的手写 new 来进行和原生 new 运算符进行测试
扩展
function Person(name, age) {
this.name = name;
this.age = age;
console.log(this, 'this')
return {
name: 'Bob',
age: 30,
};
}
const person = new Person('Alice', 20);
console.log(person);
经过上面的学习,我们可以知道 person 打印出来的是 { name: 'Bob', age: 30 } ,那此时 this指向的是谁呢?
this 指向的是 {name: 'Alice', age: 20},这是为什么呢?
在这种特殊情况下,当构造函数显式地返回一个对象时,new 运算符会返回这个显式指定的对象,而不是构造函数内部的 this 指向的对象。这意味着这个返回的对象将成为实例化对象,而构造函数中的 this 并不会指向这个实例化对象。
一般情况下,构造函数模式下的 this 指向实例化对象。但是当构造函数显式地返回一个对象时,此时构造函数中的 this 不会指向实例化对象。这种情况并不常见,但的确需要注意。
我们总说构造函数模式下 this 指向实例化对象,但实际上指向的是 new 运算符内部新创建的对象,而不是实例化之后的对象
结束
在这篇文章中,我们理解了 new 运算符的原理,也手写了一个 new运算符
希望能够帮助大家更好地理解 new 运算符原理,理解 JavaScript
祝大家变得更强!