10分钟搞懂 js 的 new 运算符

new 的实现非常简单,10分钟就能搞懂!!!

实例化

没错,new 的作用就是为了实例化一个对象,仅此而已

构造函数

既然涉及到对象的实例化,那就需要我们先添加一个 构造函数 ,如下:

js 复制代码
function GirlFriend() {}

const gf = new GirlFriend()

console.log(gf) // {}
console.log(gf.__proto__ === GirlFriend.prototype) // true

通过打印结果可以得出:

  1. 构造函数 使用 new 关键字最终会返回了一个新的 实例对象
  2. gf 作为 GirlFriend实例 ,它的 __proto__ 属性指向 GirlFriend.prototype

参数

假设我希望在实例化 gf 对象 时,为它初始化一些相关的属性,比如 nameage,怎么做?

只需对 构造函数 稍作修改,如下:

js 复制代码
function GirlFriend(name, age) {
  this.name = name
  this.age = age
}

const gf = new GirlFriend('小美', 18)

console.log(gf) // {name: '小美', age: 18}

通过打印结果可以得出:

  1. 构造函数 会被执行
  2. 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 指向的方式有 callapplybind 方法,下面通过代码简单演示一下 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])

有兴趣可以自行尝试下 callbind,与 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 的相关基础知识并合理的运用到实际的开发中去

相关推荐
一丝晨光21 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
Front思22 分钟前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
NiNg_1_2342 小时前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦2 小时前
如何在 Axios 中封装事件中心EventEmitter
javascript·http