前言
当我们对函数进行实例化时,就需要new操作符。那么,对于它的底层实现原理你是否清楚呢?本文就来分享下我对它的理解并用一个函数来模拟实现它,如有不对的地方欢迎指正。
过程分析
我们通过一个具体的例子来看下一个函数在new之后都能做些什么,如下所示:
function Person(name, age) {
this.name = name;
this.age = age;
this.height = "198cm";
this.bodyWeight = "65kg";
return "some anything";
}
Person.prototype.dailyExercise = "280 kcal";
Person.prototype.printBodyWeight = function() {
console.log(this.name + "体重为: " + this.bodyWeight);
};
接下来,我们用new关键字将Person函数进行实例化,我们发现实例化后,可以访问到:
- 函数内部的属性
- 函数原型上的属性
const person = new Person("Tom", "22");
console.log(person.age);
console.log(person.bodyWeight);
console.log(person.dailyExercise);
person.printBodyWeight()
眼尖的你可能发现我们的构造函数中返回了一个字符串,它属于基本类型,如果说返回一个对象会发生什么?
function Person(name, age) {
this.name = name;
this.age = age;
this.height = "198cm";
this.bodyWeight = "65kg";
return {
weight: this.bodyWeight
};
}
再次运行代码后,我们发现:
- 只能访问我们在构造函数中所返回的属性
- 构造函数中声明的其它属性以及挂载在原型上的属性均无法访问
实现思路
经过前面的分析,我们知道了函数在new完之后会返回一个新的对象,这个对象上挂载了构造函数内的所有属性以及函数原型上的所有属性。
我们在实现的时候,也需要建立一个新的对象,这个对象上需要包含构造函数里的属性,因此我们可以使用apply方法来给此对象添加新属性。
相信理解原型和原型链的人,都知道实例的 __proto__属性会指向构造函数的prototype,建立起这样的关系后,实例才可以访问原型上的属性。
有了这些知识点作为铺垫后,我们就可以写出这个模拟函数了,如下所示:
-
- 创建一个对象用来存储构造函数的属性
-
- 从
arguments中取出第一个参数,这个参数便是调用时的构造函数
- 从
-
- 将新对象的原型通过
__proto__指向构造函数的prototype
- 将新对象的原型通过
-
- 通过
apply方法改变构造函数的this指向,从而实现将构造函数内部的属性添加进新创建的对象中
- 通过
-
- 判断构造函数是否有返回值
- 有返回值且其类型为一个对象或者一个函数,则返回构造函数的返回值
- 否则就返回我们新创建的对象
function myNew() {
const newObj = Object.create(null);
const constructor = [].shift.call(arguments);
const args = [].slice.call(arguments).slice();
Object.setPrototypeOf(newObj, constructor.prototype);
const result = constructor.apply(newObj, args);
return ((result !== null && typeof result === 'object') || typeof result === 'function') ? result : newObj;
}
测试用例
我们用原理分析中的例子来验证下我们实现的这个工厂函数能否正确执行。
const factory = myNew(Person, "Tom", "22");
console.log(factory.age);
console.log(factory.bodyWeight);
console.log(factory.dailyExercise);
factory.printBodyWeight();
假设函数没有返回值或者返回值是一个字符串类型时,执行结果如下所示:
当函数的返回值是一个对象时,执行结果如下所示:
本文使用 文章同步助手 同步