new
的实现非常简单,10分钟就能搞懂!!!
实例化
没错,new
的作用就是为了实例化一个对象,仅此而已
构造函数
既然涉及到对象的实例化,那就需要我们先添加一个 构造函数 ,如下:
js
function GirlFriend() {}
const gf = new GirlFriend()
console.log(gf) // {}
console.log(gf.__proto__ === GirlFriend.prototype) // true
通过打印结果可以得出:
- 对 构造函数 使用
new
关键字最终会返回了一个新的 实例对象 - gf 作为
GirlFriend
的 实例 ,它的__proto__
属性指向GirlFriend.prototype
参数
假设我希望在实例化 gf 对象 时,为它初始化一些相关的属性,比如 name
和 age
,怎么做?
只需对 构造函数 稍作修改,如下:
js
function GirlFriend(name, age) {
this.name = name
this.age = age
}
const gf = new GirlFriend('小美', 18)
console.log(gf) // {name: '小美', age: 18}
通过打印结果可以得出:
- 构造函数 会被执行
- 被
new
实例化的过程中, 构造函数 内部的this
指向实例对象(gf)
但是,这里需要思考一个问题?
- 构造函数 是如何执行的?
this
又是如何指向 实例对象 的?
默认情况下,构造函数 会在全局作用域中进行声明,如果直接执行该函数,this
指向 window
js
function GirlFriend() {
console.log(this) // window
}
GirlFriend()
当使用 new
作用于 构造函数 时,this
会指向被创建的那个 实例对象
js
function GirlFriend() {
console.log(this) // {}
}
new GirlFriend()
因此,执行 this.xxx = xxx
时,就会将相关属性直接添加到 实例对象 上
js
function GirlFriend(name, age) {
this.name = name
this.age = age
}
const gf = new GirlFriend('小美', 18)
console.log(gf) // {name: '小美', age: 18}
最终,我们就得到了初始化后的 实例对象
所以,针对以上过程,最关键的一步就是 更改 this 的指向
在 js 中,我们常用更改 this
指向的方式有 call
、apply
和 bind
方法,下面通过代码简单演示一下 apply
的使用
- 添加一个方法可以为
this
指向的对象添加新的属性
js
function GirlFriend(name, age) {
console.log(this)
this.name = name
this.age = age
}
- 调用
GirlFriend
方法查看this
指向
js
// 普通方法调用
GirlFriend('小美', 18) // window
// new 的实例化调用
new GirlFriend('小美', 18) // GirlFriend {name: '小美', age: 18}
- 添加一个新的
obj
js
const obj = {}
- 通过
apply
借用 构造方法 为obj
添加新的属性
js
GirlFriend.apply(obj, ['小美', 18])
有兴趣可以自行尝试下 call
和 bind
,与 apply
的区别仅在于对 所接收参数的处理方式
返回值
在真实的开发里,我们很少会在 构造函数 中手动 return
一个新值,但如果需要这么去做,最终的结果又会如何呢?
基本数据类型
当返回 string / number / boolean / undefined / null / symbol 等 基本数据类型 时
js
function GirlFriend(name, age) {
this.name = name
this.age = age
return '小美' // 这里可以自行替换成不同的基本数据类型
}
const gf = new Person('小美', 18)
console.log(gf) // {name: '小美', age: 18}
对返回值的结果丝毫 没有 任何影响!!!
引用数据类型
当返回如 array / object / function 等 引用数据类型 时
js
function GirlFriend(name, age) {
this.name = name
this.age = age
return [] // 这里可以自行替换成不同的引用数据类型
}
const gf = new GirlFriend('小美', 18)
console.log(gf) // 返回的类型为数组 [] 时 打印 []
console.log(gf) // 返回的类型为数组 {} 时 打印 {}
console.log(gf) // 返回的类型为数组 fn (){} 时 打印 fn (){}
// ...
当 return
引用类型的数据时,最终会打印的 实例化对象 就是手动 return
的数据,这一点需要明确!
实现
这里我们通过自定义 myNew
方法进行实现,下面是具体的步骤:
- 接收需要被处理的 构造函数 记为 myConstructor
- 创建一个新的 对象 obj ,作为 实例化后的对象
- 处理 实例对象 和 构造函数 之间的关系,本质上就是 原型之间 的关联
- 返回 实例对象
js
function myNew (myConstructor) {
const obj = {}
// 实例和原型相关联
obj.__proto__ = myConstructor.prototype
return obj
}
- 处理 构造函数 在调用过程中需要使用的所有参数
- 利用
apply
方法,将 构造函数 内部的this
指向 实例对象 - 获取 构造函数 被调用后的返回值并判断返回值的类型决定最终返回的 实例对象
js
function myNew (myConstructor, ...args) {
const obj = {}
obj.__proto__ = myConstructor.prototype
const res = myConstructor.apply(obj, args) // 关键!关键!关键!
return res instanceof Object ? res : obj
}
args
参数类型为array
类型,因为我们不确定用户在使用时会传入多少个具体的参数,因此可以利用function
的 剩余参数(Rest Parameters) 来进行接收,也就解释了为什么在这里 使用 apply 会更加合适myConstructor.apply(obj, args)
的返回值,本质上就是 myConstructor 被调用之后的返回值,默认返回undefined
;如果手动return
了那就是return
的值instanceof
方法用于检测res
是否位于Object
的原型链上,借此来区分res
是 基本数据类型 还是 引用数据类型
验证
js
// 构造函数
function GirlFriend(name, age) {
this.name = name
this.age = age
}
// myNew 完整代码
function myNew (myConstructor, ...args) {
const obj = {}
obj.__proto__ = myConstructor.prototype
const res = myConstructor.apply(obj, args)
return res instanceof Object ? res : obj
}
const gf1 = myNew(GirlFriend, '小美', 18)
const gf2 = myNew(GirlFriend, '小丽', 20)
console.log(gf1) // {name: '小美', age: 18}
console.log(gf1.__proto__ === GirlFriend.prototype) // true
console.log(gf2) // {name: '小丽', age: 20}
console.log(gf2.__proto__ === GirlFriend.prototype) // true
总结
代码的核心实现非常简单,关键在于是否可以熟练掌握 js 的相关基础知识并合理的运用到实际的开发中去