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

相关推荐
IT_陈寒2 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰2 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
山河木马2 小时前
渲染管线-计算得到gl_Position(顶点着色器)之后续GPU流程
javascript·webgl·图形学
竹林8183 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花3 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu12274 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
咪库咪库咪4 小时前
Vue3-生命周期
前端
莪_幻尘5 小时前
你的 AI Skill 越多越蠢?Token 上下文爆炸的求生指南
前端·ai编程
lichenyang4535 小时前
从 has.echo 到异步 API 注册表:一次 ASCF API 回调不触发的排查复盘
前端
林瞅瞅5 小时前
Nuxt3 项目部署 Nginx 防盗链后特定 JS 文件 403 问题修复方案
前端