面试官:能否三行代码实现JS的New关键字
谁能不相思,独在机中织。
探索
凡实践,需理论先行,在开始之前,我们要先具体了解一下new创建对象的具体过程。
new的this指向
或者说构造函数的this指向,先来看一个小的示例,思考一下,log打印出来的是什么?
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = new Person("后俊生", 18);
console.log(person.name); //后俊生
console.log(person.age); //18
很显然,大家都知道打印出来的分别是"后俊生", 18
,那么,你有没有思考过这样的简单问题,为什么打印出来的是这些数据?
->我明明把参数传递给了构造函数Person
,而不是实例person
?参数为什么会附加到实例上边去了?
OK,带着这些思考,我们将代码稍稍改动,思考一下,打印出来的会是什么?
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = new Person("后俊生");
console.log(person.name); //后俊生
console.log(person.age); //undefined
结果是"后俊生", undefined
,我们把函数中this赋值语句注释,实例中的属性就没了,好像这两句话是给实例赋值的?是不是有了一些眉目了?
既然this.name = name
是给实例person
复制的,那么是不是this.name
就是person.name
,是不是this = person
?
bingo~,恭喜你,答对了,
构造函数中的this,指向的是实例本身!!!
构造函数的原型
我们将代码继续改造,向他的原型链上添加数据
function Person(name, age) {
this.name = name;
this.age = age;
function logIfo() {
console.log(age, 1);
return 1;
}
}
Person.prototype.habit = "Games";
Person.prototype.sayHi = function() {
console.log("Hi " + this.name);
};
let person = new Person("后俊生", 18);
console.log(person.name); //后俊生
console.log(person.age); //18
console.log(person.habit); //Games
person.sayHi(); //Hi 后俊生
由上面的代码,不难发现,当函数被使用new创建的时候,构造函数的原型链上的数据也会被添加到实例上。
返回值
以上都是没有返回值的情况,那么,如果函数有返回值呢?
那么我们将代码再次改造一下:
function Person(name, age) {
this.name = name;
this.age = age;
return {
hair: 'black',
gender: 'man'
}
}
let person = new Person("后俊生", 18);
console.log(person.name); //undefined
console.log(person.age); //undefined
console.log(person.hair); //black
console.log(person.gender); //man
我们发现,实例person上不存在name、age属性了,只包含返回对象的属性,好像我们构造的是返回对象的实例,那么,真的是这样吗?
再来看看这个代码
function Person(name, age) {
this.name = name;
this.age = age;
return 1;
}
let person = new Person("后俊生", 18);
console.log(person.name); //后俊生
console.log(person.age); //18
咦?什么情况,为什么这次又存在name、age属性了?
事实上: 如果函数没有返回对象类型Object
(包含Functoin
, Array
, Date
, RegExg
, Error
),那么new表达式中的函数调用会自动返回这个新的对象。
new方法思路
我们来总结一下new方法做的事情:
- 改边this,指向实例
- 将构造函数的原型复制到实例上
- 根据返回值类型决定实例的属性
这就是我们的new方法需要实现的功能,
最终实现:
之前写过一下实现方式,功能一样,但是不够优雅,这是我见过最优雅的解决方案,三行代码解决问题
function _new(fn, ...arg) {
//以一个现有对象作为原型,创建一个新对象,继承fn原型链上的属性
const obj = Object.create(fn.prototype);
// 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
const ret = fn.apply(obj, arg);
// 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。
return ret instanceof Object ? ret : obj;
}
测试
我们来做一下测试:
function Person(name, age) {
this.name = name;
this.age = age;
return {
hair: 'black',
gender: 'man'
}
}
let person = _new(Person,"后俊生", 18);
console.log(person.name); //undefined
console.log(person.age); //undefined
console.log(person.hair); //black
console.log(person.gender); //man
function Person(name, age) {
this.name = name;
this.age = age;
return 1;
}
Person.prototype.habit = "Games";
let person = _new(Person,"后俊生", 18);
console.log(person.name); //后俊生
console.log(person.age); //18
console.log(person.habit); //Games
发现,和我们使用new方法的结果一模一样,至此,new方法实现完成。
注意
这里的_new方法只能传入函数,不能传入class,因为class在使用apply时会报错。
const ret = fn.apply(obj, arg);
^
TypeError: Class constructor Person cannot be invoked without 'new'
引用
Object.create() - JavaScript | MDN
来源:juejin.cn/post/7280436307914309672