引言:
学会手写 new 运算符的模拟实现,不仅可以深入理解 JavaScript 中对象创建和构造函数的工作原理,并且在面试中,面试官可能会要求我们解释或实现一个简化版的 new 操作符,以评估我们对 JavaScript 的核心概念的理解程度。那么就跟随本文来手写一个简化的new 操作符。
分析:
我们在实例化一个对象时,new在内部到底承担了什么作用,我们不妨来看看代码
function Person(name,age){
this.name=name;
this.age=age;
}
// 在Person的原型上添加方法getName来获取name属性
Person.prototype.getName = function(){
return this.name;
}
const awei = new Person('awei', 20)
在上述代码中我们使用 new 来实例化一个对象,而其在内部执行了一系列的操作。
创建新对象:new 操作符首先会创建一个新的空对象 {}。
设置原型链:新创建的对象会被链接到构造函数的 prototype 属性,即新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)会被设置为构造函数的 prototype 对象。
绑定 this:构造函数内部的 this 关键字被绑定到这个新创建的对象上。
了解这些操作后,我们就基于上述代码来手写一个new,来帮助我们更好的理解new。
实战:
首先创建一个构造函数作为初始化新对象的模板。
function Person(name, age) {
this.name = name;
this.age = age;
}
此时的函数 Person的原型上是没有方法的,我们可以通过打印Person.prototype来查看。
console.log(Person.prototype);
// Person.prototype = { constructor: [Function: Person] }
那我们不妨添加一个方法进去,再打印Person.prototype查看就会添加我们的新方法。
Person.prototype.getName = function () {
return this.name;
}
console.log(Person.prototype);
// Person.prototype = { constructor: [Function: Person] , getName: [Function (anonymous)] }
arguments 是一个类数组对象,包含了传递给函数的所有参数。
在这里我们创建一个objectFactory()函数来模拟new的作用,并且我们需要依次传入参数Person、name、age。因此函数的arguments为{ '0': [Function: Person], '1': name, '2': age },我们也可以打印查看一下。
function objectFactory() {
console.log(arguments);
}
let awei = objectFactory(Person, 'awei', 20)
我们可以得到这样的打印结果:
在创建函数后我们就需要为其中添加点"内饰"了,首先new运算符在执行的时候会创建一个空对象,并且这个 空对象 与 构造函数 没有任何“血缘关系”,并不像 java 那样的父子关系什么的,所以我们在函数内部也需要创建一个空对象。
javascript 代码解读复制代码
const obj = new Object(); // 空对象创建
然后我们需要将空对象的原型指向构造函数,在我们传入的参数内就包含构造函数,所以我们需要在arguments内将其取出,而在这里我们就可以开始“炫技”了
// 常规方法:
const Constructor = arguments[0];
// 炫技:
const Constructor = [].shift.call(arguments); // 取出第一个参数,相当于arguments[0]
// 打印查看一下
console.log(Constructor);
我们可以得到这样的结果,证明现在Constructor = Person
当面试官看到你用常规方法时可能就已经开始打瞌睡了,但是当我们拿出[].shift.call(arguments)这一套组合拳下来,面试官就会欣慰的笑笑了。
在这里shift是数组的一个方法,可以返回并移除数组的开头,但是由于arguments是类数组,其并不包含这个方法,所以这里我们通过call() 来将 shift 内部的 this 指向了 arguments,这样就使arguments也可以使用shift方法了,并且我们巧妙的使用一个[].shift创建了一个对数组原型方法 shift 的引用,只能用完美来形容。
接下来就是将obj的原型指向Constructor(构造函数) 的原型,并且绑定this。
// 将 obj 的原型指向 Constructor(构造函数) 的原型
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments); // 将 obj 作为 this 指向,执行构造函数
// 并且由于上述进行了[].shift操作,将argument[0] = Person 移除了,剩下的就是给 obj 添加属性和方法,并且apply第二个参数是可以传入数组的
// 打印查看一下效果
console.log(obj);
得到这样的结果:
最后再返还obj就完成了
return obj;
实例:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function () {
return this.name;
}
function objectFactory() {
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
}
let awei = objectFactory(Person, 'awei', 20)