前言
要实现手写new
,关键在于先吃透它的本质。new
作为 JavaScript 中创建对象的核心操作符,看似简单的语法背后,藏着一套严谨的执行逻辑。只有先弄清楚它究竟是什么------ 是连接构造函数与实例对象的桥梁 ?还是激活原型链关联的开关 ?再明确它能完成哪些核心功能------ 是初始化对象属性 ,还是建立继承关系 ?最后拆解它的执行过程中每一步都发生了什么------ 从创建空对象到绑定原型,从执行构造函数到返回实例...... 搞懂了这些,手写new的思路才会清晰起来。接下来,我们一起来揭露这个东西
new 是什么?
new
是JavaScript中用于创建对象实例的关键操作符,它是面向对象编程中对象实例化的核心机制。
我们通常利用new
来实例化一个对象:
js
function Person(name, age) {
this.name = name;
this.age = age;
this.Talk = function () {
console.log('我是' + this.name);
}
}
// 使用new创建实例
const person1 = new Person('张三', 25);
console.log(person1); // 输出: Person { name: '张三', age: 25 }
person1.Talk(); // 输出: 我是张三
我们可以看到利用new
产生的实例继承了构造函数的属性和方法,并且成功实例化了一个对象。
new 的过程中发生了什么?
当使用new
调用函数时,JavaScript引擎会执行以下步骤:
- 创建新对象 :创建一个全新的空对象
{}
- 链接原型 :将这个新对象的
[[Prototype]]
(即__proto__
)链接到构造函数的prototype
对象- 绑定this :将构造函数中的
this
绑定到这个新对象- 执行构造函数:执行构造函数中的代码(通常用于初始化对象)
- 返回对象:如果构造函数没有返回对象,则自动返回这个新对象
当然,对于这5条,1
,3
就不用说了,
1
是基础步骤,我们要返回一个实例化对象,必须得初始化一个,没有米怎么蒸米饭呢?
3
的话,上一期的this
指向中有讲过,传送门🚪=>美丽的地方0^0~
2
,我们可以在浏览器中console.log(person1)
,查看它的__proto__
是否等于Person.prototype
4
,我们也可以随便在构造函数中加一句console.log('whatever u want')
,观察它是否会执行
5
,重点来咯!当构造函数显式返回一个对象时,new
操作符会忽略原本创建的新对象,直接返回构造函数返回的这个对象。此时新对象的属性初始化将被丢弃,只有返回对象的内容会被保留。
构造函数返回对象完整规则:
- 返回对象类型(包括数组、函数等)
→ 完全替代new
默认创建的对象 - 返回原始值(数字、字符串等)
→ 被忽略,仍然返回new
创建的新对象 - 没有return语句
→ 正常返回new
创建的新对象
示例:
js
// 情况1:返回对象
function Case1() {
this.a = 1;
return { b: 2 }; // 对象优先
}
console.log(new Case1()); // { b: 2 }
// 情况2:返回原始值
function Case2() {
this.a = 1;
return 123; // 原始值被忽略
}
console.log(new Case2()); // { a: 1 }
// 情况3:无返回
function Case3() {
this.a = 1;
}
console.log(new Case3()); // { a: 1 }
手写new
okk,既然我们都知道了你new
出一个对象安慰自己没有对象的事实时会发生的事情了,我们开始手写一个new
吧!
记住这灵魂的舞步~ :
- 创建新对象 :创建一个全新的空对象
{}
- 链接原型 :将这个新对象的
[[Prototype]]
(即__proto__
)链接到构造函数的prototype
对象- 绑定this :将构造函数中的
this
绑定到这个新对象- 执行构造函数:执行构造函数中的代码(通常用于初始化对象)
- 返回对象:如果构造函数没有返回对象,则自动返回这个新对象
ok,假设我们有一个构造函数Person
,要利用我们的手写new
创建一个实例,
那么首先我们要创建一个函数,用来实现new
:
js
function Person(name, age) {
this.name = name;
this.age = age;
}
function myNew(){}
现在我们要实现创建一个新的空对象:
js
function myNew(){
var obj = {};
}
接下来,我们要确立原型,链接到构造函数,那么该怎么做呢?
既然我们要用到构造函数,那么我们应该至少应该拿到构造函数吧,所以我们就要把构造函数和所需要的形参传递进来!
js
function myNew(constructor,...args){
var obj = new Object();
}
// tips: ...args为rest运算符,负责接收其余剩下的参数
接下来我们开始绑定prototype:
js
function myNew(constructor,...args){
var obj = new Object();
obj.__proto__ = constructor.prototype;
// obj 继承构造函数的prototype,prototype包含 构造函数 的各种属性和方法
}
Next,开始激情♂地绑定this
到新的实例对象上,那么怎么样能够将构造函数的this
绑定到obj
到上面呢?上一期我们学习了显式绑定,我们可以利用call
、apply
、bind
方法来将其绑定到理想目标上:
js
function myNew(constructor,...args){
var obj = new Object();
obj.__proto__ = constructor.prototype;
constructor.bind(obj,...args);
}
这样我们就成功了,下一步就是要执行构造函数了,我们可以用一个变量接受bind
的结果并执行它:
js
function myNew(constructor,...args){
var obj = new Object();
obj.__proto__ = constructor.prototype;
var before = constructor.bind(obj,...args);
before();
}
最后利用一个变量接受结果,判断构造函数返回的结果是否符合要求,
如果不符合则返回新对象,
如果符合则返回构造函数准备好的结果:
js
function myNew(constructor,...args){
var obj = new Object();
obj.__proto__ = constructor.prototype;
var before = constructor.bind(obj,...args);
var res = before();
return typeof res === 'object' ? res || obj : obj;
// 三目运算符,res为函数执行的结果,如果return的结果为object,则返回res,没有就返回obj
// res || obj? 这么写是因为res可能为null,而 typeof只会判断二进制前三位,
// null 和 object二进制前三位存储为0,所以判断不准
// 因此这么写当res为null时也会返回obj,只有其为object时才会返回res
}
OK,看到这里大家肯定也知道我们可以优化一部分了。我们可以把创建对象到绑定prototype
以及 从绑定this
到判断结果这一段优化:
js
function myNew(constructor,...args){
// 创建空对象,obj的__proto__继承constructor.prototype
const obj = Object.create(constructor.prototype);
// 利用apply绑定this并执行构造函数直接获得结果
const res = constructor.apply(obj, args);
return typeof res === 'object' ? res || obj : obj;
}
总结
想要手写一个new
,就要深深记住它在创建过程中会发生的事情,所以要记住以下五点!!!!!!
(我再提一提哥哥们不会烦我吧~)
- 创建新对象 :创建一个全新的空对象
{}
- 链接原型 :将这个新对象的
[[Prototype]]
(即__proto__
)链接到构造函数的prototype
对象- 绑定this :将构造函数中的
this
绑定到这个新对象- 执行构造函数:执行构造函数中的代码(通常用于初始化对象)
- 返回对象:如果构造函数没有返回对象,则自动返回这个新对象
OK就是这样,拜拜哥哥们!