# ES7~11 新增内容

在这里插入图片描述

通过 nrm 来管理 npm 源;有下面一些命令:

# 查看可选源
nrm ls

# 测试源响应时间
nrm test taobao

# 切换源
nrm use taobao

# 增加定制源
nrm add smart http://192.168.12.122:4546

# 删除源
nrm del smart

# ES7 相关

# Array.prototype.includes()

在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:

console.log(array1.find((item) => {
  return item === 2
}))

// 或者
console.log(array1.filter((item) => {
  return item === 2
}).length > 0)

ES7 引入的 Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

  • 基本用法
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es6')) // true
console.log(arr.includes('es9')) // false
  • 接收俩个参数:要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es7', 1)) // true
console.log(arr.includes('es7', 2)) // false
console.log(arr.includes('es7', -1)) // false
console.log(arr.includes('es7', -2)) // true
  • 与 indexOf() 比较
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].indexOf('a') > -1 // true

console.log(arr.indexOf('es7')) // 1
console.log(arr.indexOf('es7') > -1) // true

注意:只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的.

const arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
  • 优缺点比较

两者都是采用 === 的操作符来作比较的,不同之处在于:对于 NaN 的处理结果不同。我们知道 js 中 NaN === NaN 的结果是 false, indexOf() 也是这样处理的,但是 includes() 不是这样的。

const demo = [1, NaN, 2, 3]
demo.indexOf(NaN) // -1
demo.includes(NaN) // true

如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用 includes()。如果想获取一个值在数组中的位置,那么只能使用 indexOf 方法。

# 幂运算符**

如果不使用任何函数,如何实现一个数的求幂运算?

function pow(x, y) {
  let res = 1
  for (let i = 0; i < y; i++) {
    res *= x
  }
  return res
}

pow(2, 10)
// 1024

除了自己封装函数来实现,也可是使用 Math.pow() 来完成。

Math.pow() 函数返回基数(base)的指数(exponent)次幂。

console.log(Math.pow(2, 10)) // 1024

在 ES7 可以这样写了:

console.log(2 ** 10) // 1024

# ES8 相关

# async / await

async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展,在我们处理异步的时候,比起回调函数,Promise的then方法会显得较为简洁和清晰,但是在处理多个彼此之间相互依赖的请求的时候,就会显的有些繁琐。这时候,用async/await更加优雅。

我们知道 JavaScript 是单线程的,使用 Promise 之后可以让我们书写异步操作更加简单,而 async 是让我们写起 Promise 像同步操作。

  • 基本语法

前面添加了 async 的函数在执行后都会自动返回一个 Promise 对象:

async function foo() {
  return 'imooc' // Promise.resolve('imooc')
}
console.log(foo()) // Promise

await 后面需要跟异步操作,不然就没有意义,而且 await 后面的 Promise 对象不必写 then,因为 await 的作用之一就是获取后面Promise 对象成功状态传递出来的参数。

function timeout() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(1)
      resolve() // resolve('success')
    }, 1000)
  })
}

// 不加 async 和 await 是2、1 ;加了是 1、2
async function foo() {
  await timeout() // let res = await timeout() res 是 success
  console.log(2)
}
foo()

对于失败的处理:

function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve('success')
      reject('error')
    }, 1000)
  })
}
async function foo() {
  return await timeout()
}
foo().then(res => {
  console.log(res) // resolve success
}).catch(err => {
  console.log(err) // reject error
})

# Object 扩展

之前的语法如何获取对象的每一个属性值:

const obj = {
  name: 'jie',
  web: 'jiegiser.github.io',
  course: 'webgis'
}
console.log(Object.keys(obj))
const res = Object.keys(obj).map(key => obj[key])
console.log(res)
// ["jie", "jiegiser.github.io", "webgis"]

ES8 中对象扩展补充了两个静态方法,用于遍历对象:Object.values(),Object.entries()

  • Object.values()

Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in,但是 for...in 还会遍历原型上的属性值)。

const obj = {
  name: 'jie',
  web: 'jiegiser.github.io',
  course: 'webgis'
}
console.log(Object.values(obj))
// ["jie", "jiegiser.github.io", "webgis"]
  • Object.entries() Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)
const obj = {
  name: 'jie',
  web: 'jiegiser.github.io',
  course: 'webgis'
}
console.log(Object.entries(obj))
// [["name", "jie"]
// ["web", "jiegiser.github.io"]
// ["course", "webgis"]]

for (let [key, value] of Object.entries(obj)) {
  console.log(key, value)
}

// name jie
// web jiegiser.github.io
// course webgis
  • Object.getOwnPropertyDescriptors()

对象有以下描述符:

  1. value [属性的值]
  2. writable [属性的值是否可被改变]
  3. enumerable [属性的值是否可被枚举]
  4. configurable [描述符本身是否可被修改,属性是否可被删除]

获取对象指定属性的描述符:

const obj = {
  name: 'jie',
  web: 'jiegiser.github.io',
  course: 'webgis'
}
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))
// {value: "jie", writable: true, enumerable: true, configurable: true}

如果想获取对象的所有属性的描述符:

console.log(Object.getOwnPropertyDescriptors(obj))

# String 扩展

在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾;

  • String.prototype.padStart()

把指定字符串填充到字符串头部,返回新字符串;该方法有两个参数,第一个参数是目标字符要保持的长度值;第二个参数是如果目标字符的长度不够需要的补白字符,默认为空;如下例子:

const str = 'gis'
console.log(str.padStart(8, 'x')) // xxxxxgis
console.log(str.padEnd(8, 'y')) // gisyyyyy
console.log(str.padStart(8)) //      gis
  1. 场景1:日期格式化

希望把当前日期格式化城:yyyy-mm-dd的格式:

const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = (now.getDate()).toString().padStart(2, '0')
console.log(`${year}-${month}-${day}`)
  1. 场景2:数字替换
// 数字替换,比如手机号,身份证号
const tel = '13012345678'
const newTel = tel.slice(-4).padStart(tel.length, '*')
console.log(newTel) // *******5678
  • String.prototype.padEnd()

方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

const str = 'gis'
console.log(str.padEnd(8, 'y')) // gisyyyyy
  1. 场景:时间戳统一长度

在 JS 前端我们处理时间戳的时候单位都是 ms 毫秒,但是,后端同学返回的时间戳则不一样是毫秒,可能只有 10 位,以 s 秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个 13 位的补全,保证单位是毫秒。

// 伪代码
console.log(new Date().getTime()) // 时间戳 13 位的
timestamp = +String(timestamp).padEnd(13, '0')

# 尾逗号 Trailing commas

ES8 允许函数的最后一个参数有尾逗号(Trailing comma)。

此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

function clownsEverywhere(
  param1,
  param2
) {}

clownsEverywhere(
  'foo',
  'bar'
)

上面代码中,如果在param2或bar后面加一个逗号,就会报错。

如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数 clownsEverywhere 添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。

function clownsEverywhere(
  param1,
  param2,
) {}

clownsEverywhere(
  'foo',
  'bar',
)

这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

# ES9 相关

# for await of

异步迭代器(for-await-of):循环等待每个 Promise 对象变为 resolved 状态才进入下一步。

下面是实现迭代的方法:

const arr = ['es5', 'es6', 'es7']
arr[Symbol.iterator] = function () {
  let nextIndex = 0
  return {
    next() {
      return nextIndex < arr.length ? {
        value: arr[nextIndex++],
        done: false
      } : {
        value: undefined,
        done: true
      }
    }
  }
}

我们知道 for...of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?

function Gen(time) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(time)
    }, time)
  })
}

async function test() {
  let arr = [Gen(2000), Gen(100), Gen(3000)]
  for (let item of arr) {
    console.log(Date.now(), item.then(console.log))
  }
}

test()
// 1560090138232 Promise {<pending>}
// 1560090138234 Promise {<pending>}
// 1560090138235 Promise {<pending>}
// 100
// 2000
// 3000

这里写了几个小任务,分别是 2000ms 、100ms、3000ms 后任务结束。在上述遍历的过程中可以看到三个任务是同步启动的,然后输出上也不是按任务的执行顺序输出的,这显然不太符合我们的要求。

我们可以使用 async 来实现异步迭代:

function Gen(time) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(time)
    }, time)
  })
}

async function test() {
  let arr = [Gen(2000), Gen(100), Gen(3000)]
  for (let item of arr) {
    console.log(Date.now(), await item.then(console.log))
  }
}

test()
// 2000
// 1560091834772 undefined
// 100
// 1560091836774 undefined
// 3000
// 1560091836775 undefined

在 ES9 中也可以用 for...await...of 的语法来操作:

function Gen(time) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(time)
    }, time)
  })
}

async function test() {
  let arr = [Gen(2000), Gen(100), Gen(3000)]
  for await (let item of arr) {
    console.log(Date.now(), item)
  }
}

test()
// 1560092345730 2000
// 1560092345730 100
// 1560092346336 3000

上面的 for...await...of 的语法是异步可迭代协议(Symbol.asyncIterator),类似 for of 这种循环需要遵循 Symbol.interator 可迭代协议一样;Symbol.asyncIterator 异步可迭代协议如下:也是 next 函数返回 done、value 两个属性,不过遍历的值是异步函数;

let obj = {
  count: 0, 
  Gen (time) {
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        resolve({ done: false, value: time })
      }, time)
    })

  }, 
  [Symbol.asyncIterator] () {
    let self = this
    return {
      next () {
        self.count++
        if (self.count < 4) {
          return self.Gen(Math.random() * 1000)
        } else {
          return Promise.resolve({
            done: true,
            value: ''
          })
        }
      }
    }

  }
}

async function test () {
  for await (let item of obj) {
    console.log(Date.now(), item)
  }
}
// 1560093560200 649.3946561938179
// 1560093560828 624.6310222512955
// 1560093561733 901.9497480464518

# RegExp Updates 正则扩展

  • dotAll 模式 正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用 u 修饰符解决;另一个是行终止符(line terminator character)。
  1. U+000A 换行符(\n)
  2. U+000D 回车符(\r)
  3. U+2028 行分隔符(line separator)
  4. U+2029 段分隔符(paragraph separator)

dotAll 模式就是 s 修饰符;他可以匹配换行、回车、行分割、段分割符;

console.log(/foo.bar/.test('foo\nbar')) // false
console.log(/foo.bar/s.test('foo\nbar')) // true

在 ES5 中我们都是这么解决的:

console.log(/foo[^]bar/.test('foo\nbar')) // true
// or
console.log(/foo[\s\S]bar/.test('foo\nbar')) // true

那如何判断当前正则是否使用了 dotAll 模式呢?

const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's')` .
console.log(re.test('foo\nbar')) // true
console.log(re.dotAll) // true
console.log(re.flags) // 's
  • 具名组匹配

我们在写正则表达式的时候,可以把一部分用()包裹起来,被包裹起来的这部分称作“分组捕获”。

console.log('2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/))
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: undefined]

这个正则匹配很简单,按照 match 的语法,没有使用 g 标识符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2020, 05, 01)。

此外 match 返回值还有几个属性,分别是 index、input、groups。

index [匹配的结果的开始位置] input [搜索的字符串] groups [一个捕获组数组 或 undefined(如果没有定义命名捕获组)]

我们通过数组来获取这些捕获:

let t = '2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(t[1]) // 2020
console.log(t[2]) // 05
console.log(t[3]) // 01

上文中重点看下 groups 的解释,这里提到了命名捕获组的概念,如果没有定义 groups 就是 undefined。很明显,我们上述的返回值就是 undefined 间接说明没有定义命名捕获分组。那什么是命名捕获分组呢?

console.log('2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/))
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]

这段代码的返回值 groups 已经是 Object 了,具体的值是:

groups: {
  year: "2020",
  month: "05",
  day: "01"
}

这个 Object 的 key 就是正则表达式中定义的,也就是把捕获分组进行了命名。想获取这些捕获可以这样做:

let t = '2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]
console.log(t.groups.year) // 2020
console.log(t.groups.month) // 05
console.log(t.groups.day) // 01
  • 后行断言

在 ES9 之前 JavaScript 正则只支持先行断言,不支持后行断言。简单复习下先行断言的知识:

let test = 'hello world'
console.log(test.match(/hello(?=\sworld)/))
// ["hello", index: 0, input: "hello world", groups: undefined]

这段代码要匹配后面是 world 的 hello,但是反过来就不成:

let test = 'world hello'
console.log(test.match(/hello(?=\sworld)/))
// null

比如我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:

let test = 'world hello'
console.log(test.match(/(?<=world\s)hello/))
// ["hello", index: 6, input: "world hello", groups: undefined]

(?<...)是后行断言的符号,(?...)是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。

# Object Rest & Spread 对象扩展

在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:

const input = {
  a: 1,
  b: 2
}

const output = {
  ...input,
  c: 3
}

console.log(output) // {a: 1, b: 2, c: 3}

我们再来看下 Object rest 的示例:

const input = {
  a: 1,
  b: 2,
  c: 3
}

let { a, ...rest } = input

console.log(a, rest) // 1 {b: 2, c: 3}

注意这是浅拷贝

# Promise.prototype.finally()

指定不管最后状态如何都会执行的回调函数。 Promise.prototype.finally() 方法返回一个 Promise,在promise执行结束时,无论结果是 fulfilled 或者是 rejected,在执行 then() 和 catch() 后,都会执行 finally 指定的回调函数。这为指定执行完promise后,无论结果是 fulfilled 还是 rejected 都需要执行的代码提供了一种方式,避免同样的语句需要在 then() 和 catch() 中各写一次的情况。例如,我们一般写得 loading 状态;可以直接写在 finally 里面去停止 loading;

其他,比如数据库断开链接:

let connection
db.open()
  .then(conn => {
    connection = conn
    return connection.select({
      name: 'Jane'
    })
  })
  .then(result => {
    // Process result
    // Use `connection` to make more queries
  })···
  .catch(error => {
    // handle errors
  })
  .finally(() => {
    connection.close()
  })

# 字符串扩展

放松对标签模板里字符串转义的限制, 遇到不合法的字符串转义返回 undefined,并且从 raw 上可获取原字符串。

ES9 开始,模板字符串允许嵌套支持常见转义序列,移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。

  • 带标签的模板字符串
const foo = (a, b, c, d) => {
  console.log(a)
  console.log(b)
  console.log(c)
  console.log(d)
}

const name = 'jie'
const age = 18
foo `这是${name},他的年龄是${age}` 

// ["这是", ",他的年龄是", "岁", raw: Array(3)]
// raw: (3) ["这是", ",他的年龄是", "岁"]
// jie
// 18
// undefined

ES9 标准移除了对 ECMAScript 带标签的模板字符串中转义序列的语法限制。

function tag(strs) {
  console.log(strs)
  // strs[0] === undefined
  // strs.raw[0] === "\\unicode and \\u{55}"
}

// 在标签函数中使用
tag `\u{61} and \u{62}`  //
tag `\u{61} and \unicode`  // 结果是 undefined

// 之前的版本会报错:Invalid Unicode escape sequence
// 无效的Unicode转义序列

// 报错:
let bad = `bad escape sequence: \unicode` 

# ES10 相关

# Object.fromEntries()

方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。

const obj = {
  name: 'imooc',
  course: 'es'
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]

// ES10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: "imooc", course: "es"}

我们可以将 Map 转 Object:

const map = new Map()
map.set('name', 'imooc')
map.set('course', 'es')
console.log(map)
// Map(2) {"name" => "imooc", "course" => "es"}

const obj = Object.fromEntries(map)
console.log(obj)
// {name: "imooc", course: "es"}

也可以过滤对象的属性:

const course = {
  math: 80,
  english: 85,
  chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // 0: (2) ["english", 85]  1: (2) ["chinese", 90]
console.log(Object.fromEntries(res))
// {english: 85, chinese: 90}

# String 扩展

  • String.prototype.trimStart()

trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。

  • String.prototype.trimEnd()

trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。

const str = '  jie   '
// 去掉前面的空格
console.log(str.replace(/^\s+/g, ''))
// 去掉后面的空格
console.log(str.replace(/\s+$/g, ''))

// 去掉前面的空格
console.log(str.trimStart())
console.log(str.trimLeft())

// 去掉后面的空格
console.log(str.trimEnd())
console.log(str.trimRight())

// 去掉所有空格
console.log(str.trim())

# Array 扩展

  • Array.prototype.flat() flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

语法:const newArray = arr.flat(depth) depth 指定要提取嵌套数组的结构深度,默认值为 1

const numbers = [1, 2, [3, 4, [5, 6]]]
// 此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]

console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]

// 使用 Infinity 也是一样的效果
console.log(numbers.flat(Infinity))
// [1, 2, 3, 4, 5, 6]
  • Array.prototype.flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。

const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]

这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:

let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]

# 修订 Function.prototype.toString()

函数是对象,并且每个对象都有一个 .toString() 方法,因为它最初存在于Object.prototype.toString() 上。所有对象(包括函数)都是通过基于原型的类继承从它继承的。这意味着我们以前已经有 funcion.toString() 方法了。

Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。

这意味着还将返回注释、空格和语法详细信息。

function foo() {
  // es10新特性
  console.log('imooc')
}
console.log(foo.toString())
// function foo() {
  // es10新特性
  // console.log('imooc')
// }

// 直接在方法名 toString()
console.log(Number.parseInt.toString())
// function parseInt() { [native code] }

# 可选的 Catch Binding

在 ES10 之前我们都是这样捕获异常的:

try {
  // tryCode
} catch (err) {
  // catchCode
}

在这里 err 是必须的参数,在 ES10 可以省略这个参数:

// 省略 catch 绑定的参数和括号
try {
  console.log('Foobar')
} catch {
  console.error('Bar')
}

# JSON 扩展

  • JSON superset (JSON 超集)

什么是 JSON 超集?,简而言之就是让 ECMAScript 兼容所有 JSON 支持的文本。 ECMAScript 曾在标准 JSON.parse 部分阐明 JSON 确为其一个子集,但由于 JSON 内容可以正常包含 \u2028 行分隔符 与 \u2029段分隔符,而 ECMAScript 却不行。

// 在这之前是不支持分隔符在代码中
eval('var str = "ji"; \u2029 function foo() { return str; };')
console.log(foo())
// ji
  • JSON.stringify() 增强能力

SON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。

// \uD83D\uDE0E  emoji 多字节的一个字符
console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸

// 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify('\uD83D')) // "\ud83d"

# Symbol 扩展

  • Symbol.prototype.description

我们知道,Symbol 的描述只被存储在内部的 Description ,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:

const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true

现在可以通过 description 方法获取 Symbol 的描述:

const name = Symbol('es')
console.log(name.description) // es
console.log(name.description === 'es') // true

description 是只读的属性;

# ES11 相关

# String 扩展

  • String.prototype.matchAll()

matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器;

下面例子是想要获取到 div 里面的内容

const str = `
  <html>
    <body>
      <div>第一个div</div>
      <p>这是一个p</p>
      <span>span</span>
      <div>第二个div</div>
    <body>
  </html>
`

// 1. exec 正则规则匹配获取
function selectDiv(regExp, str) {
  let matches = []
  while (true) {
    // console.log(regExp.lastIndex)
    const match = regExp.exec(str)
    if (match === null) {
      break
    }
    matches.push(match[1])
  }
  return matches
}
// 小括号表示捕获组
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]

// 2. 字符串 match 方法
console.log(str.match(regExp)) // ["<div>第一个div</div>", "<div>第二个div</div>"]

// 3. replace 方法
function selectDiv(regExp, str) {
  let matches = []
  str.replace(regExp, (all, first) => {
    console.log(all, first)
    // <div>第一个div</div> 第一个div
    // <div>第二个div</div> 第二个div
    matches.push(first)
  })
  return matches
}
const res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]

使用 ES11 的 matchAll() 方法:

const str = `
  <html>
    <body>
      <div>第一个div</div>
      <p>这是一个p</p>
      <span>span</span>
      <div>第二个div</div>
    <body>
  </html>
`
const regExp = /<div>(.*)<\/div>/g

function selectDiv(regExp, str) {
  let matches = []
  for (let match of str.matchAll(regExp)) {
    matches.push(match[1])
  }
  return matches
}
const res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]

# Dynamic Import

现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。

如下例子:页面上有一个按钮,点击按钮才去加载 ajax 模块。这里的 ajax 是自己封装的一个方法

const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
  import('./ajax').then(mod => {
    mod.default('static/a.json', res => {
      console.log(res)
    })
  })
})

# BigInt

BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出 2 的 53 次方。

Js 中 Number 类型只能安全的表示-(2^53-1)至 2^53-1 范的值

console.log(2 ** 53) // es7 幂运算符
console.log(Number.MAX_SAFE_INTEGER) // 最大值-1

使用 BigInt 有两种方式:

  • 方式一:数字后面增加 n
const bigInt = 9007199254740993n
console.log(bigInt) // 9007199254740993n
console.log(typeof bigInt) // bigint

console.log(1n == 1) // true
console.log(1n === 1) // false 值相同,类型不同
  • 方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993n)
console.log(bigIntNum) // 9007199254740993n

也可以进行相加:

const bigIntNum1 = BigInt(9007199254740993n)
const bigIntNum2 = BigInt(9007199254740993n)
console.log(bigIntNum1 + bigIntNum2) // 18014398509481986n

# Promise.allSettled()

Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入 reject 状态。

场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据:

Promise.all([
  Promise.reject({
    code: 500,
    msg: '服务异常'
  }),
  Promise.resolve({
    code: 200,
    data: ['1', '2', '3']
  }),
  Promise.resolve({
    code: 200,
    data: ['4', '5', '6']
  })
]).then(res => {
  console.log(res)
  console.log('成功')
}).catch(err => {
  console.log(err)
  console.log('失败')
})

我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态:

Promise.allSettled([
  Promise.reject({
    code: 500,
    msg: '服务异常'
  }),
  Promise.resolve({
    code: 200,
    data: ['1', '2', '3']
  }),
  Promise.resolve({
    code: 200,
    data: ['4', '5', '6']
  })
]).then(res => {
  console.log(res)
  console.log('成功')
}).catch(err => {
  console.log(err)
  console.log('失败')
})

# globalThis

提供了一个标准的方式去获取不同环境下的全局对象;

Javascript 在不同的环境获取全局对象有不通的方式:

  • node 中通过 global
  • web 中通过 window, self 等.

self:打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个 window 对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window 对象的常用方法和函数都可以用 self 代替 window。

self.setTimeout(() => {
  console.log(123)
}, 1000)

以前想要获取全局对象,可通过一个全局函数

const getGlobal = () => {
  if (typeof self !== 'undefined') {
    return self
  }
  if (typeof window !== 'undefined') {
    return window
  }
  if (typeof global !== 'undefined') {
    return global
  }
  throw new Error('无法找到全局对象')
}

const globals = getGlobal()
console.log(globals)

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。

console.log(globalThis)

# 可选链 Optional chaining

可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。

const user = {
  address: {
    street: 'xx街道',
    getNum() {
      return '80号'
    }
  }
}

在之前的语法中,想获取到深层属性或方法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让你整个应用挂掉。

const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)

用了 Optional Chaining ,上面代码会变成:

const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)

# 空值合并运算符(Nullish coalescing Operator)

空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。

当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。

// 如果设置为 false '' 0 都会判断为 false;执行 5
const b = 0 // 或者 null undefined false ''
const a = b || 5
console.log(a)

空值合并运算符 ?? 我们仅在第一项为 null 或 undefined 时设置默认值

// false 0  无效,只有值为 null 或 undefined 时才会取后面的数
const a = b ?? 123
console.log(a)
更新: 6/16/2021, 5:10:48 PM