深入理解JS的new关键字
手撕代码
手写new
是面试题高频考点,实现代码也非常简单,但在实现后我对其为什么要这样设计有着很深的疑惑,也看了许多文章,今天也来讲一下为什么要这样设计new
的实现
先来看下实现代码:
function MyNew(Con,...args){
const obj = Object.create(Con);
const result = Con.apply(obj, args);
return result instanceof Object ? result : obj;
}
我最初的感受是三行代码很费解,又是原型继承,又是this
绑定,又是判断类型的,其实这其中涉及到的知识点很多,虽然实现了new
的功能,但不知道为什么要这样做
知其所以然
举例
假设我们要开始全国人口普查,所以我们要制造一个表示人信息的对象,有名字,年龄,省份等信息,会吃东西,说话,行走,睡觉等行为
const person = {
ID: i,
name: "pangyu",
age: "18",
province: "山东",
say() {
console.log("说话");
},
eat() {
console.log("吃东西");
},
walk() {
console.log("行走");
},
sleep() {
console.log("睡觉");
},
};
接下来查了100个人,那我们就利用for循环创建100个人的对象,实现批量统计
const roster = []; // 花名册
for (let i = 0; i < 100; i++) {
const person = {
ID: i,
name: "pangyu",
age: "18",
province: "山东",
say() {
console.log("说话");
},
eat() {
console.log("吃东西");
},
walk() {
console.log("行走");
},
sleep() {
console.log("睡觉");
},
};
roster.push(person);
}
避免重复
统计了100个人之后,暴露了一个问题:
每个人的say,eat,walk,sleep方法都是一样的,但每创建一个人都会重新创建这四个方法,那么100个人就创建了400个函数,要是统计成千上万人,消耗的内存相当大,这样不妥
如何解决:
既然方法都是一样的,那么就单独抽离到一个对象中,实现复用这个对象就不会再重新创建了
const roster = []; // 花名册
const personBehavior = {
say() {
console.log("说话");
},
eat() {
console.log("吃东西");
},
walk() {
console.log("行走");
},
sleep() {
console.log("睡觉");
},
};
for (let i = 0; i < 100; i++) {
const person = {
ID: i,
name: "pangyu",
age: "18",
province: "山东",
__proto__: personBehavior,
};
roster.push(person);
}
现在person的__proto__
属性是personBehavior
公共方法,这其实就是原型的作用,避免公共方法的重复声明,节省内存,利于维护
功能模块化
这样实现了复用,但还不够完美,我们可以把创建人信息放在一个单独的createPerson
函数中,并让公共属性和这个方法联系起来
const roster = []; // 花名册
function createPerson(i) {
const person = {}; // 创建临时对象
person.__proto__ = createPerson.prototype; // 临时对象绑定原型(公共属性)
person.ID = i;
person.name = "pangyu";
person.age = "18";
person.province = "山东";
return person; // 返回临时对象
}
createPerson.prototype = {
say() {
console.log("说话");
},
eat() {
console.log("吃东西");
},
walk() {
console.log("行走");
},
sleep() {
console.log("睡觉");
},
};
for (let i = 0; i < 100; i++) {
roster.push(createPerson(i));
}
这样的功能模块划分较为清晰,那么在createPerson
函数中,JS之父发现这样创建对象的操作非常常见,于是他就发明了new关键字来简化createPerson
中的操作
其中红框内的代码其实属于模版代码,每创建一个对象都会走这样的一套逻辑,只有给person
赋值自身属性时才需要用户手动编写,所以红框中的代码片段都会在new
中实现
使用new
创建person
const roster = []; // 花名册
function createPerson(i) {
// const person = {}; // 【省略】创建临时对象
// person.__proto__ = createPerson.prototype; // 【省略】临时对象绑定原型
// 绑定自身属性
this.ID = i;
this.name = "pangyu";
this.age = "18";
this.province = "山东";
// return person; // 【省略】返回临时对象
}
createPerson.prototype = {
say() {
console.log("说话");
},
eat() {
console.log("吃东西");
},
walk() {
console.log("行走");
},
sleep() {
console.log("睡觉");
},
};
for (let i = 0; i < 100; i++) {
roster.push(new createPerson(i));
}
回看new的实现
function MyNew(Con,...args){
const obj = Object.create(Con); // 创建临时对象,绑定原型
const result = Con.apply(obj, args); // 调用构造函数,绑定this到临时对象(赋值this自身属性)
return result instanceof Object ? result : obj; // 如果构造函数返回值为对象类型,那么就返回,否则返回临时对象
}
现在是不是对于new
有了更深的理解,其实它就是一个语法糖,把生成对象的模版代码实现了, 用户只需要关注自身属性即可
总结
- new执行后做了什么
- 创建临时对象/新对象
- 绑定原型(公共属性)
- 指定this = 临时对象(为this赋值自身属性做准备)
- 执行构造函数(this赋值自身属性)
- 返回临时对象(判断构造函数返回值是否为对象)
- new的本质是什么
- 语法糖,省略了创建对象所需的模版代码
参考链接:
转载自:https://juejin.cn/post/7235493849072975930