本文参考了大佬的心得,感谢大佬:JS 的 new 做了哪些事情呢? - 掘金 (juejin.cn)
根据上述文章+查找资料,我大概是理解通透了,进入正题
核心代码就是下面这些,不过可以先暂时跳过,往下看
function Person(name) {
this.name = name;
}
function myNew(Con, ...args) {
// 创建一个新的空对象
let obj = {};
// 将这个空对象的__proto__指向构造函数的原型
obj.__proto__ = Con.prototype;
// 将this指向空对象
let res = Con.apply(obj, args);
// 对构造函数返回值做判断,然后返回对应的值
return res instanceof Object ? res : obj;
}
let per = myNew(Person, '你好,new');
console.log(per); // {name: "你好,new"}
console.log(per.constructor === Person); // true
console.log(per.__proto__ === Person.prototype); // true
① 首先创建一个空对象
let obj = {};
② 接着完善原型链,将这个空对象的 __proto__ 指向构造函数的 prototype 属性
即 obj.__proto__ = Person.prototype;
③ 改变 this 指向,这里也是我不理解的点,此处重点解释一下:
首先请问:为什么要改变 this 指向,令 this 指向我们新建的空对象呢??
function Person(name, age) {
var obj = Object.create(null); // 创建一个空对象
obj.name = name;
obj.age = age;
return obj; // 返回这个空对象
}
var person1 = new Person('Tom', 20);
console.log(person1.name); // 输出 'Tom'
在这个例子中,我们在
Person
构造函数内部使用Object.create(null)
创建了一个空对象obj
,并将name
和age
属性添加到该空对象上。最后,我们使用return
将该空对象作为返回值,从而使得person1
对象指向了这个空对象。这种方式虽然有些麻烦,但是它可以避免直接修改构造函数内部的
this
对象,从而保持构造函数的纯净性和可重用性。
继续,回到正题:
apply()方法的第一个参数为 this 指向的对象,第二个参数为传参。所以,this 指向了新创建的对象obj(这里重点注意),args进行传参。那么现在回到 Person 构造函数,因为this改变,所以 this.name 可以理解为就是 obj.name , 然后右边的 name 即被传入的 args 参数,接着进行左右的赋值,此时 obj 已经新增了该有了属性,换句话说,已经从空对象变成了拥有相关属性及值的完整对象,目的达到
④ 对构造函数返回值做判断,然后返回对应的值
-
一般是返回第一步创建的空对象;
-
但是当 构造函数有返回值时 则需要做判断再返回对应的值,是 对象类型 则 返回该对象,是 原始类型 则 返回第一步创建的空对象。
一般情况下构造函数是没有返回值的,但是作为函数,是可以有返回值的。
function Person(name) {
this.name = name;
return {
age: 22
}
}
let per = myNew(Person, '你好,new');
// 当构造函数返回对象类型的数据时,会直接返回这个数据, new 操作符无效
console.log(per); // {age: 22}
function Person(name) {
this.name = name;
return '十二点的程序员'
}
let per = myNew(Person, '你好,new');
// 而当构造函数返回基础类型的数据,则会被忽略
console.log(per); // {name: "你好,new"}