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

相关推荐
counterxing1 小时前
我整理了一个免费开发资源目录,还做成了 CLI 和 MCP
前端·agent·ai编程
子兮曰8 小时前
Bun v1.3.14 深度解析:Image API、HTTP/3、全局虚拟存储与五十项变革
前端·后端·bun
kyriewen9 小时前
今天,百年巨头一次砍了9200人,而一个离职科学家的实话让全网睡不着觉
前端·openai·ai编程
问心无愧05139 小时前
ctf show web 入门42
android·前端·android studio
kyriewen10 小时前
老板逼我上AI,我偷偷在浏览器里跑LLaMA,省下20万API费
前端·react.js·llm
Beginner x_u10 小时前
前端八股整理(手写 02)|数组转树、数组扁平化、随机打乱一个数组
前端·数组·数组转树·数组扁平化
KaMeidebaby10 小时前
卡梅德生物技术快报|禽类成纤维细胞 FISH 实验:鸟类性别染色体基因定位技术实现与数据验证
前端·数据库·其他·百度·新浪微博
天若有情67310 小时前
前端高阶性能优化:跳出传统懒加载与预加载,基于用户行为做轻量预判加载
前端·性能优化
小小小小宇10 小时前
前端转后端:SQL 是什么
前端
张元清11 小时前
React Observer Hooks:7 种监听 DOM 而不写样板代码的方式
前端·javascript·面试