手动实现new操作
使用new
操作符可以实例化一个对象,那么,要模拟实现一个new
,首先我们需要知道new
在背后到底做了什么。
看下一个普通的类被实例化之后发生了什么变化。
function Person(name, age) {
this.name = name;
this.age = age;
this.number = 123;
}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.commonProperty = 'common property';
const person = new Person('leevare', 18);
console.log(person);
将上述这段代码放到浏览器中运行,可以看到输出的person
实例中的age
、name
等属性被赋予了具体传入的值,并且实例的[[Prototype]]
上能找到类Person
的原型方法。
分析这些现象,可以知道new
使用我们传入的属性生成了一个新的实例对象,并且这个对象还能基于原型链找到类的原型方法和属性。所以,实现的基本思路找到了。
function myNew(...args) {
const [Constructor, ...restArgs] = args;
// 新创建一个对象,模拟每一个新创建的实例对象
const obj = {};
// 让obj的原型链上能找到Constructor的原型信息
obj.__proto__ = Constructor.prototype;
// 将传入的属性一一绑定到obj的实例上
Constructor.call(obj, ...restArgs);
return obj;
}
const person = myNew(Person, 'leevare', 18);
console.log(person);
受限于js,我们是不能够实现类似于new xxx()
这种调用形式的,还是以函数调用的方式去实现。
那么,函数第一个参数为需要被实例化的类,剩余参数为类初始化时需要传递的参数。得益于js的arguments
不定参数,我们可以传递不同类的不同参数(这里为了简单,我采用了es6的展开...
运算符)。
第一步,需要创建一个新的对象,用于模拟每一个新创建的实例对象。然后再将这个新建对象的原型链指向正确,即设置其__proto__
为类的prototype
。
之后,将传入的参数作为属性一一绑定为obj
对象的实例属性,返回即可。
将上述代码拷贝到浏览器控制台执行一下看看吧,输出的结果和直接new
是类似的。
到这里,好像已经完全实现了new
。先别着急,我们把Person
类改写一下。
function Person(name, age) {
this.name = name;
this.age = age;
this.number = 123;
return {
hello: 'world',
};
}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.commonProperty = 'common property';
const person = new Person('leevare', 18)
console.log(preson)
在浏览器控制台输出结果如下。
// 输出结果:
{ hello: 'world' }
返回的是我在构造函数中return
的内容。这种情况涉及到一个概念。
如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
默认情况下,类构造函数会在执行之后返回 this 对象。构造函数返回的对象会被用作实例化的对象,如果没有什么引用新创建的this
对象,那么这个对象会被销毁。不过,如果返回的不是this
对象,而是其他对象,那么这个对象不会通过instanceof
操作符检测出跟类有关联,因为这个对象的原型指针并没有被修改。
所以,还需要对上面new
的实现进行一些改写,只需要判断构造函数中是否有显式返回的非空对象即可。
function Person(name, age) {
this.name = name;
this.age = age;
this.number = 123;
return {
hello: 'word',
};
}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.commonProperty = 'common property';
function myNew(...args) {
const [Constructor, ...restArgs] = args;
// 新创建一个对象,模拟每一个新创建的实例对象
const obj = {};
// 让obj的原型链上能找到Constructor的原型信息
obj.__proto__ = Constructor.prototype;
// 将传入的属性一一绑定到obj的实例上
const constructorReturns = Constructor.call(obj, ...restArgs);
return typeof constructorReturns === 'object' ? constructorReturns : obj;
}
const person = myNew(Person, 'leevare', 18);
console.log(person);
再次运行,结果正确。
当然,实现的方式有很多种,这里只是其中的一种实现。比如上述声明obj
和修改obj
的__proto__
指向,我们还可以这样。
const obj = Object.create(Constructor.prototype);
看起来有点像之前文章中讲到的继承了。
本文完。😊
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=2877