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 的相关基础知识并合理的运用到实际的开发中去

相关推荐
朝阳58137 分钟前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter1 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友1 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog3 小时前
低端设备加载webp ANR
前端·算法
LKAI.3 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
刺客-Andy4 小时前
React 第七十节 Router中matchRoutes的使用详解及注意事项
前端·javascript·react.js