JS高级 - ES6 ~ ES13 新增语法(四)

157 阅读7分钟

bigInt

在早期的JavaScript中,我们不能正确的表示过大的数字

大于MAX_SAFE_INTEGER的数值,表示的可能是不正确的,也可能是正确的,这是不确定的

所以在ES11中,引入了bigInt这个数据类型,用于表示大于MAX_SAFE_INTEGER的整数

表示方式是在数值的最后加上n即可

const num = Number.MAX_SAFE_INTEGER
console.log(num) // => 9007199254740991

// 在数值后边加上n 可以将一个数值转换为大整数类型
const bingInt1 =  9007199254740992n

// 任意数值后边加上n 都会转换为大整数类型,不一定是非常大的值
console.log(1n)

// 大整数类型和其它普通类型值的存储方式是不一致的
// 所以大整数类型只能和大整数类型 进行运算
// 大整数类型不能和其它类型值 进行运算
console.log(1n + 2n) // => 3n
console.log(1n + 2) // error

空值合并运算符(Nullish Coalescing Operator)

// ES11之前,设置默认值 最为常用的方式是使用逻辑或
const num = 1

// 但是这种写法是存在问题的
// num的值在运算的时候会被转换为boolean类型值
// 而某些值在转换为boolean类型值 值为false,如 空字符串,0等
// 但有的时候 我们对应的值 就是 0, null, false等
// 此时使用这种方式去进行默认值判断就会出现问题
const foo = num || 'default value'
// 所以在ES11中 推出了空值合并运算符 ??
// 当且仅当 空值合并运算符前边的操作符的值为null或undefined的时候
// 才会触发空值合并运算符后的值,否则返回第一个操作符所对应的值
console.log(0 ?? 'default') // => 0
console.log(false ?? 'default') // => false

console.log(null ?? 'default') // => default
console.log(undefined ?? 'default') // => default

可选链操作符

可选链也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁

const user = {
  name: 'Klaus',
  friend: {
    name: 'Steven',
    running() {
      console.log('running')
    }
  }
}

// 早期写法
if (user.friend && user.friend.running) {
  user.friend.running()
}

// 早期简化写法
user.friend && user.friend.running && user.friend.running()

// 使用可选链操作符
// 可选链操作符之前的表达式的结果为引用为空的类型(nullish 即 值为null 或 undefined)
// 则该表达式短路 并返回undefined
// 否则正常执行对应的表达式
user?.friend?.running?.()

globalThis

在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的

  • 比如在浏览器中可以通过this、window来获取
  • 比如在Node中我们需要通过global来获取

在ES11中对获取全局对象进行了统一的规范:globalThis

FinalizationRegistry

FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调

当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调。(清理回调有时被称为 finalizer )

FinalizationRegistry在业务代码中很少使用,主要用于进行一些测试的时候使用

// FinalizationRegistry是一个构造器方法
// 该构造方法会返回一个“注册表”对象
const finalRegister = new FinalizationRegistry(value => {
  console.log(`${value}被GC清除了`)
})

let obj = { name: 'obj' }
let foo = { name: 'foo' }

// 将需要监听的对象注册到注册表对象中
// 参数一: 需要监听的变量
// 参数二: 标识符 - 该标识符会在回调被触发的时候,作为callback的参数被传入
// 该标识符可以是任何合法的数据类型
finalRegister.register(foo, 'foo')
finalRegister.register(obj, 'obj')

// 一个对象没有任何强引用指向的时候就会被认为是需要被回收的垃圾对象
// 但GC不是立即回收的,而是会等待一个时间点,在CPU和浏览器空闲的时候在一起进行统一的回收
obj = null
foo = null

weakRef

默认情况下,我们对一个复杂数据类型的引用都是强引用

也就是说我们不仅仅可以通过这个引用找到对应的复杂数据类型

并且因为该引用的存在,对应的复杂数据类型也是不会被GC销毁的

但是有的时候,我们希望的是仅仅通过对应的引用去找到对应的复杂数据类型并使用

而不会因为该引用的存在,而导致对应的复杂数据类型被GC所销毁,这个时候就可以私有弱引用

const user = { name: 'Klaus' }

const weak = new WeakRef(user) // => 此时weak对于user对象的引用就是弱引用


// 我们虽然可以通过弱引用去查找到对应的对象,但是我们不能保证对应的对象一定存在
// 所以在使用弱引用之前,需要先使用deref方法进行判断
// 如果对应的对象依旧存在,则可以继续使用
// 如果对于的对象已经被GC所销毁的时候,该方法就会返回undefined,告诉我们对应的对象已经不可使用了
if (weak.deref()) {
  // 注意这里需要使用weak.deref().name
  // 不可以使用let ref = weak.deref()
  // 因此此时ref是一个强引用,需要手动进行ref = null操作
  
  // 其实,如果打印这个weak.deref(), user是不会被销毁的
  // 因为此时控制台中打印的是一个可交互的对象,也就是其对于user对象也是一个强引用
  // 但是如果打印的是属性,那么就不是对user的强引用,user会被移除
  console.log(weak.deref().name) // => Klaus
}

逻辑赋值运算符(logical assignment operators)

let username = 'Klaus'

username ??= 'Klaus' // 等价于 username = username ?? 'Alex'

// 其余同理 存在 ||= &&= ...

replaceAll

const msg = 'Hello World, Hello JavaScript'

console.log(msg.replace('Hello', 'Bye')) // => Bye World, Hello JavaScript

console.log(msg.replaceAll('Hello', 'Bye')) // => Bye World, Bye JavaScript

// replaceAll 等价于 replace第一个参数使用正则 同时加上g
console.log(msg.replace(/Hello/g, 'Bye')) // => Bye World, Bye JavaScript

at方法

const str = 'Hello World'
const arr = [1, 2, 3]

// 字符串和数组 有一个新增方法 at
// 可以通过索引值 去获取对应位置上的元素

// at方法支持负值,最后一位是-1,没有-0这种概念
console.log(str.at(1)) // => e
console.log(str.at(-1)) // => d

console.log(arr.at(1)) // => 2
console.log(arr.at(-1)) // => 3

Object.hasOwn

Object中新增了一个静态方法(类方法): hasOwn(obj, propKey)

Object.hasOwn方法用于判断一个对象中是否有某个自己的属性

const user = {
  name: 'Klaus',
  __proto__: {
    age: 23
  }
}

// hasOwn是Object的静态方法
// 参数一: 需要查找的那个对象
// 参数二: 对应的需要查找的属性
console.log(Object.hasOwn(user, 'name')) // => true
console.log(Object.hasOwn(user, 'age')) // => false

Object.hasOwn方法的功能和以前的Object.prototype.hasOwnProperty方法的功能是一致的,其使用用来替换hasOwnProperty方法的

const user = Object.create(null, {
  name: {
    value: 'Klaus'
  }
})

// 此时使用Object.prototype.hasOwnProperty方法 就会出错
// 因为user的原型对象为null
console.log(user.hasOwnProperty('name')) // error

// 此时就只能使用Object.hasOwn方法
console.log(Object.hasOwn(user, 'name')) // => true

类的成员变量(New members of classes)

class Person {
  // 类实例对象属性定义位置 二
  // 在这里定义实例属性的方式 被称之为 类的成员表达式
  // 1. 任何Person的实例对象上都会存在对应的height属性
  // 2. 如果该属性和构造函数中初始化的属性同名,新初始化的值会覆盖该值的属性
  height = 1.88
  static intro = 'static public field'

  constructor(name, age) {
    // 类的实例对象属性定义位置 一
    this.name = name
    this.age = age
  }

  // 类的成员表达式的值也可以是函数
  // 但是不推荐,因为使用这种方式定义的函数
  // 是存在在每一个实例对象上的, 而不是存放于原型对象上的
  running = function() {
    console.log('running')
  }

  // 使用#开头的属性和方法 表示的是 私有的属性和方法
  // 只能在类的内部进行访问,不可以通过类的实例或子类来进行访问
  // 之前定义私有属性的时候使用的是下划线开头 但这是一种约定俗称的方式,并没有在语法层面对其进行限制
  // 而#开头的属性在ES13中正式被定为标准,其会在语法层面将对于的属性和方法 设定为私有属性或私有方法
  #address = 'shanghai'

  // 无论是实例成员属性还是静态属性 都是可以定义为私有属性或私有方法的
  static #msg = 'private static msg'

  // 静态代码块 会在类被解析的时候执行
  // 也就是静态代码块中的代码 只会在类初始化的时候被执行一次
  // 因此 静态代码块中的代码 可以是一些对类进行初始化的操作
  static {
    console.log('静态代码块')
  }
}

const per = new Person('Klaus', 23)
console.log(Object.hasOwn(per, 'running')) // => true