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

相关推荐
远山无期16 分钟前
解决Tailwind任意值滥用:规范化CSS开发体验
前端·css·eslint
用户542778485154025 分钟前
Vue 3 中开发高阶组件(HOC)与 Renderless 组件
前端
幼儿园老大35 分钟前
告别代码屎山!UniApp + Vue3 自动化规范:ESLint 9+ 扁平化配置全指南
javascript·vue.js
HIT_Weston39 分钟前
67、【Ubuntu】【Hugo】搭建私人博客(一)
前端·ubuntu·hugo
阿里巴啦1 小时前
用React+Three.js 做 3D Web版搭建三维交互场景:模型的可视化摆放与轻量交互
前端·react·three.js·模型可视化·web三维·web三维交互场景
Liu.7741 小时前
vue3组件之间传输数据
前端·javascript·vue.js
|晴 天|1 小时前
前端闭包:从概念到实战,解锁JavaScript高级技能
开发语言·前端·javascript
开发者小天1 小时前
react的拖拽组件库dnd-kit
前端·react.js·前端框架
用户4445543654261 小时前
在Android开发中阅读源码的指导思路
前端
用户54277848515401 小时前
ESM 模块(ECMAScript Module)详解
前端