我们平时在 JavaScript 中使用构造函数创建对象时,都会用到 new
关键字。但很多八股一上来就抛出“new 的四个步骤”,看着很标准,但也很抽象。到底 new
做了什么?怎么一步步执行的?我们用代码来复现整个过程
new做了什么
1. 创建一个空对象
我们先来从最基本的开始,定义一个构造函数 foo
,然后用 new
关键字来调用它
function foo(){
}
console.log(new foo);
看看控制台输出了什么
到这一步我们确认:使用 new
调用构造函数,最基本的效果就是创建了一个空对象
2. 将空对象的原型,指向于构造函数的原型
这一步是让我们创建的对象能够“继承”构造函数原型上的方法和属性。我们可以通过对比 __proto__
来验证这一点
console.log(foo.prototype == new foo().__proto__)
// 构造函数的原型 == 空对象的原型
// true
控制台输出为true
3. 将空对象作为构造函数的上下文(改变this指向
如果你直接调用构造函数,不加 new
,this
默认会指向全局对象(非严格模式下是 window
)
function foo() {
console.log(this); // 全局状态下指向window
this.name = "张三"; // undefined
}
console.log(foo());
控制台输出:
添加new关键字后,
console.log(new foo());
控制台输出:
this指向改变了。所以使用 new
,就是为了让 this
正确地指向我们新建的对象
4. 对构造函数有返回值的处理判断
当构造函数返回的是引用类型(对象或函数)时,返回这个值;若返回的是原始类型(例如数字、字符串、null、undefined),则忽略,返回新建对象
可以写成
return (result !== null && (typeof result === 'object' || typeof result === 'function')) ? result : obj;
手动实现new
理解了上述四步之后,我们就可以自己手写一个 _new
方法,模拟 new
关键字的全部行为。下面是完整代码:
function _new(fn, ...args) {
// 1. 创建了一个空对象
const obj = {}; // 或 var obj = Object.create({});
// 2. 将空对象的原型,指向构造函数的原型
Object.setPrototypeOf(obj, fn.prototype);
// 3. 将空对象作为构造函数的上下文(改变this指向
const result = fn.apply(obj, args);
// 4. 对构造函数有返回值的处理判断
return result instanceof Object ? result : obj;
}
测试一下
function Person(age, name) {
this.age = age;
this.name = name;
}
console.log(_new(Person, 18, "张三"));
控制台输出:
成功创建
注:如果不熟悉.apply()或.setPrototypeOf()的去看一下官方文档
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
面试怎么答
首先,创建一个新对象,并为其设置原型链,将对象的 [[Prototype]] 指向构造函数的 prototype,使其能够访问原型链上的方法和属性 ;再让构造函数在新对象上执行,此时 this
指向新对象;最后判断返回值,如果构造函数返回一个对象,则直接返回它;否则返回新对象
建议不止停留在八股文记忆层面,而是用浏览器、调试器一步步验证、实现,哪怕是造个轮子,也比背一百遍更有效
参考
https://www.bilibili.com/video/BV1FF411H7ig/?spm_id_from=333.337.search-card.all.click&vd_source=25de877a1d1cc18eac3e3d919f023f70
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new