2024年一份精心总结的3万字ES6实用指南(下)(1),面试经历过程

最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端视频资料:

  • 如果 async 函数里的多个异步操作之间没有依赖关系,建议将他们写到一起减少执行时间:

// 写法一

let [foo, bar] = await Promise.all([getFoo(), getBar()])

// 写法二

let fooPromise = getFoo()

let barPromise = getBar()

let foo = await fooPromise

let bar = await barPromise

  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

共享内存和Atomics对象

  • SharedArrayBuffer

  • Atomics

ES2018


Promise.prototype.finally()

Promise.prototype.finally() 用于给 Promise 对象添加 onFinally 函数,这个函数主要是做一些清理的工作,只有状态变化的时候才会执行该 onFinally 函数。

function onFinally() {

console.log(888)  // 并不会执行

}

new Promise((resolve, reject) => {

}).finally(onFinally)

finally() 会生成一个 Promise 新实例,finally 一般会原样后传父 Promise,无论父级实例是什么状态:

let p1 = new Promise(() => {})

let p2 = p1.finally(() => {})

setTimeout(console.log, 0, p2)  // Promise {}

let p3 = new Promise((resolve, reject) => {

resolve(3)

})

let p4 = p3.finally(() => {})

setTimeout(console.log, 0, p3)  // Promise {: 3}

上面说的是一般,但是也有特殊情况,比如 finally 里返回了一个非 fulfilledPromise 或者抛出了异常的时候,则会返回对应状态的新实例:

let p1 = new Promise((resolve, reject) => {

resolve(3)

})

let p2 = p1.finally(() => new Promise(() => {}))

setTimeout(console.log, 0, p2)  // Promise {}

let p3 = p1.finally(() => Promise.reject(6))

setTimeout(console.log, 0, p3)  // Promise {: 6}

let p4 = p1.finally(() => {

throw new Error(‘error’)

})

setTimeout(console.log, 0, p4)  // Promise {: Error: error}

参考:

  • 深入理解Promise

异步迭代器

想要了解异步迭代器最好的方式就是和同步迭代器进行对比。我们知道可迭代数据的内部都是有一个 Symbol.iterator 属性,它是一个函数,执行后会返回一个迭代器对象,这个迭代器对象有一个 next() 方法可以对数据进行迭代,next() 执行后会返回一个对象,包含了当前迭代值 value 和 标识是否完成迭代的 done 属性:

let iterator = [1, 2]Symbol.iterator

iterator.next()  // { value: 1, done: false }

iterator.next()  // { value: 2, done: false }

iterator.next()  // { value: undefinde, done: true }

上面这里的 next() 执行的是同步操作,所以这个是同步迭代器,但是如果 next() 里需要执行异步操作,那就需要异步迭代了,可异步迭代数据的内部有一个 Symbol.asyncIterator 属性,基于此我们来实现一个异步迭代器:

class Emitter {

constructor(iterable) {

this.data = iterable

}

Symbol.asyncIterator {

let length = this.data.length,

index = 0;

return {

next:() => {

const done = index >= length

const value = !done ? this.data[index++] : undefined

return new Promise((resolve, reject) => {

resolve({value, done})

})

}

}

}

}

异步迭代器的 next() 会进行异步的操作,通常是返回一个 Promise,所以需要对应的处理函数去处理结果:

let emitter = new Emitter([1, 2, 3])

let asyncIterator = emitterSymbol.asyncIterator

asyncIterator.next().then(res => {

console.log(res)  // { value: 1, done: false }

})

asyncIterator.next().then(res => {

console.log(res)  // { value: 2, done: false }

})

asyncIterator.next().then(res => {

console.log(res)  // { value: 3, done: false }

})

另外也可以使用 for await...of 来迭代异步可迭代数据:

let asyncIterable = new Emitter([1, 2, 3])

async function asyncCount() {

for await (const x of asyncIterable ) {

console.log(x)

}

}

asyncCount()

// 1 2 3

另外还可以通过异步生成器来创建异步迭代器:

class Emitter {

constructor(iterable) {

this.data = iterable

}

async *Symbol.asyncIterator {

let length = this.data.length,

index = 0;

while (index < length) {

yield this.data[index++]

}

}

}

async function asyncCount() {

let emitter = new Emitter([1, 2, 3])

const asyncIterable = emitterSymbol.asyncIterator

for await (const x of asyncIterable ) {

console.log(x)

}

}

asyncCount()

// 1 2 3

参考:

  • Iteration_protocols

  • for-await…of

s修饰符(dotAll模式)

正则表达式新增了一个 s 修饰符,使得 . 可以匹配任意单个字符:

/foo.bar/.test(‘foo\nbar’)   // false

/foo.bar/s.test(‘foo\nbar’)  // true

上面这又被称为 dotAll 模式,表示点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式:

/foo.bar/s.dotAll  // true

具名组匹配

正则表达式可以使用捕获组来匹配字符串,但是想要获取某个组的结果只能通过对应的索引来获取:

let re = /(\d{4})-(\d{2})-(\d{2})/

let result = re.exec(‘2015-01-02’)

// result[0] === ‘2015-01-02’

// result[1] === ‘2015’

// result[2] === ‘01’

// result[3] === ‘02’

而现在我们可以通过给捕获组 (?<name>...) 加上名字 name ,通过名字来获取对应组的结果:

let re = /(?\d{4})-(?\d{2})-(?\d{2})/

let result = re.exec(‘2015-01-02’)

// result.groups.year === ‘2015’

// result.groups.month === ‘01’

// result.groups.day === ‘02’

配合解构赋值可以写出非常精简的代码:

let {groups: {year, month, day}} = /(?\d{4})-(?\d{2})-(?\d{2})/.exec(‘2015-01-02’)

console.log(year, month, day)  // 2015 01 02

具名组也可以通过传递给 String.prototype.replace 的替换值中进行引用。如果该值为字符串,则可以使用 $<name> 获取到对应组的结果:

let re = /(?\d{4})-(?\d{2})-(?\d{2})/

let result = ‘2015-01-02’.replace(re, ‘ < d a y > / <day>/ <day>//$’)

// result === ‘02/01/2015’

参考:

  • proposal-regexp-named-groups

后行断言

后行断言:(?<=y)xx 只有在 y 后面才能匹配:

/(?<=$)\d+/.exec(‘I have $100.’)  // [‘100’]

后行否定断言:(?<!y)xx 只有不在 y 后面才能匹配:

/(?<!$)\d+/.exec(‘I have $100.’)  // [‘00’]

Unicode属性转义

允许正则表达式匹配符合 Unicode 某种属性的所有字符,\p{...} 是匹配包含,\P{...} 是匹配不包含的字符,且必须搭配 /u 修饰符才会生效:

/\p{Emoji}+/u.exec(‘???笑死我了???不行了’)  // [‘???’]

/\P{Emoji}+/u.exec(‘???笑死我了???不行了’)  // [‘笑死我了’]

这里可以查询到更多的 Unicode 的属性 Full_Properties

对象扩展运算符

对象的扩展运算符可以用到解构赋值上,且只能应用到最后一个变量上:

let {x, …y} = {x: 1, a: 2, b: 3}

console.log(y)  // {a: 2, b: 3}

对象扩展运算符不能解构原型上的属性:

let obj = { x: 1 }

obj.proto = { y: 2 }

let {…a} = obj

console.log(a.y)  // undefined

应用一:可以实现浅拷贝,但是不会拷贝原始属性:

let person = Object.create({ name: ‘布兰’ })

person.age = 12

// 浅拷贝写法一

let { …pClone1 } = person

console.log(pClone1)  // { age: 12 }

console.log(pClone1.name)  // undefined

// 浅拷贝写法二

let pClone2 = {…person}

console.log(pClone2)  // { age: 12 }

console.log(pClone2.name)  // undefined

应用二:合并两个对象:

let ab = {…a, …b}

// 等同于

let ab = Object.assign({}, a, b);

应用三:重写对象属性

let aWithOverrides = { …a, x: 1, y: 2 };

应用四:给新对象设置默认值

let aWithDefaults = { x: 1, y: 2, …a };

应用五:利用扩展运算符的解构赋值可以扩展函数参数:

function baseFunction({ a, b }) {}

function wrapperFunction({ x, y, …restConfig }) {

// 使用 x 和 y 参数进行操作

// 其余参数传给原始函数

return baseFunction(restConfig)

}

参考:

  • Object Spread Initializer

  • Object Rest Destructuring

放松对标签模板里字符串转义的限制

参考:

  • ECMAScript 6 入门

ES2019


允许省略catch里的参数

异常被捕获的时候如果不需要做操作,甚至可以省略 catch(err) 里的参数和圆括号:

try {

} catch {

}

JSON.stringify()变动

UTF-8 标准规定,0xD8000xDFFF 之间的码点,不能单独使用,必须配对使用。所以 JSON.stringify() 对单个码点进行操作,如果码点符合 UTF-8 标准,则会返回对应的字符,否则会返回对应的码点:

JSON.stringify(‘\u{1f600}’)  // “”???“”

JSON.stringify(‘\u{D834}’)  // “”\ud834""

Symbol.prototype.description

Symbol 实例新增了一个描述属性 description

let symbol = Symbol(‘foo’)

symbol.description  // ‘foo’

Function.prototype.toString()

函数的 toString() 会原样输出函数定义时候的样子,不会省略注释和空格。

Object.fromEntries()

Object.fromEntries() 方法是 Object.entries() 的逆操作,用于将一个键值对数组转为对象:

let person = { name: ‘布兰’, age: 12 }

let keyValueArr = Object.entries(person)   // [[‘name’, ‘布兰’], [‘age’, 12]]

let obj = Object.fromEntries(arr)  // { name: ‘布兰’, age: 12 }

常用可迭代数据结构之间的装换:

let person = { name: ‘布兰’, age: 12 }

// 对象 -> 键值对数组

let keyValueArr = Object.entries(person)  // [[‘name’, ‘布兰’], [‘age’, 12]]

// 键值对数组 -> Map

let map = new Map(keyValueArr)  // Map {“name”: “布兰”, “age”: 12}

// Map -> 键值对数组

let arr = Array.from(map)  // [[‘name’, ‘布兰’], [‘age’, 12]]

// 键值对数组 -> 对象

let obj = Array.from(arr).reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {})  // { name: ‘布兰’, age: 12 }

参考:

  • Object.fromEntries

字符串可直接输入行分隔符和段分隔符

JavaScript 规定有 5 个字符,不能在字符串里面直接使用,只能使用转义形式。

  • U+005C:反斜杠(reverse solidus)

  • U+000D:回车(carriage return)

  • U+2028:行分隔符(line separator)

  • U+2029:段分隔符(paragraph separator)

  • U+000A:换行符(line feed)

但是由于 JSON 允许字符串里可以使用 U+2028U+2029,所以使得 JSON.parse() 去解析字符串的时候可能会报错,所以 ES2019 允许模板字符串里可以直接这两个字符:

JSON.parse(‘“\u2028”’)  // “”

JSON.parse(‘“\u2029”’)  // “”

JSON.parse(‘“\u005C”’)  // SyntaxError

String.prototype.trimStart

消除字符串头部空格,返回一个新字符串;浏览器还额外增加了它的别名函数 trimLeft()

let str = ’  hello world ’

let newStr = str.trimStart()

console.log(newStr, newStr === str)

// 'hello world ’  false

String.prototype.trimEnd

消除字符串尾部空格,返回一个新字符串;浏览器还额外增加了它的别名函数 trimRight()

let str = ’  hello world ’

let newStr = str.trimEnd()

console.log(newStr, newStr === str)

// ’  hello world’  false

Array.prototype.flat()

arr.flat(depth) 按照 depth (不传值的话默认是 1)深度拍平一个数组,并且将结果以新数组形式返回:

// depth 默认是 1

const arr1 = [1, 2, [3, 4]]

console.log(arr1.flat())  // [1, 2, 3, 4]

// 使用 Infinity,可展开任意深度的嵌套数组;自动跳过空数组;

const arr2 = [1, , [2, [3, [4]]]]

console.log(arr2.flat(Infinity))

// [1, 2, 3, 4]

reduce 实现拍平一层数组:

const arr = [1, 2, [3, 4]]

// 方法一

let newStr = arr.reduce((acc, cur) => acc.concat(cur), [])

// 方法二

const flattened = arr => [].concat(…arr)

flattened(arr)

参考:

  • flat

Array.prototype.flatMap()

flatMap(callback) 使用映射函数 callback 映射每个元素,callback 每次的返回值组成一个数组,并且将这个数组执行类似 arr.flat(1) 的操作进行拍平一层后最后返回结果:

const arr1 = [1, 2, 3, 4]

arr1.flatMap(x => [x * 2])

// 将 [[2], [4], [6], [8]] 数组拍平一层得到最终结果:[2, 4, 6, 8]

参考:

  • flatMap

ES2020


String.prototype.matchAll()

String.prototype.matchAll() 方法,可以一次性取出所有匹配。不过,它返回的是一个 RegExpStringIterator 迭代器同是也是一个可迭代的数据结构,所以可以通过 for...of 进行迭代:

let str = ‘test1test2’

let regexp = /t(e)(st(\d?))/g

let iterable = str.matchAll(regexp)

for (const x of iterable) {

console.log(x)

}

// [‘test1’, ‘e’, ‘st1’, ‘1’, index: 0, input: ‘test1test1’, groups: undefined]

// [‘test2’, ‘e’, ‘st2’, ‘2’, index: 5, input: ‘test1test2’, groups: undefined]

注意当使用 matchAll(regexp) 的时候,正则表达式必须加上 /g 修饰符。

也可以将这个可迭代数据转成数组形式:

// 方法一

[…str.matchAll(regexp)]

// 方法二

Array.from(str.matchAll(regexp))

动态import()

标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。

比如按需加载一个模块可以这样:

if (xxx) {

import(‘./module.js’)

}

import() 是异步导入的,结果会返回一个 Promise

import(‘/module.js’)

.then((module) => {

// Do something with the module.

})

动态 import() 的应用场景挺多的,比如 Vue 中的路由懒加载就是使用的动态导入组件。另外由于动态性不便于静态分析工具和 tree-shaking 工作,所以不能滥用。

BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。

为了区分 Number,定义一个 BigInt 需要在整数后面加上一个 n,或者用函数直接定义:

const num1 = 10n

const num2 = BigInt(20)

NumberBigInt 之间能进行比较,但他们之间是宽松相等;且由于他们表示的是不同类型的数字,所以不能直接进行四则运算:

10n == 10         // true

10n === 10        // false

10n > 8           // true

10 + Number(10n)  // 20

10 + 10n          // TypeError

Promise.allSettled

Promise.allSettled(iterable) 当所有的实例都已经 settled,即状态变化过了,那么将返回一个新实例,该新实例的内部值是由所有实例的值和状态组合成的数组,数组的每项是由每个实例的状态和内部值组成的对象。

function init(){

return 3

}

let p1 = Promise.allSettled([

new Promise((resolve, reject) => {

resolve(9)

}).then(res => {}),

new Promise((resolve, reject) => {

reject(6)

}),

init()

])

let p2 = p1.then(res => {

console.log(res)

}, err => {

console.log(err)

})

// [

//      {status: “fulfilled”, value: undefined},

//      {status: “rejected”, reason: 6},

//      {status: “fulfilled”, value: 3}

// ]

只要所有实例中包含一个 pending 状态的实例,那么 Promise.allSettled() 的结果为返回一个这样 Promise {<pending>} 的实例。

globalThis

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 windowself 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global

而现在只需要使用 globalThis 即可获取到顶层对象,而不用担心环境问题。

// 在浏览器中

globalThis === window  // true

import.meta

import.meta 是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URLimport.meta 必须在一个模块里使用:

// 没有声明 type=“module”,就使用 import.meta 会报错

// 在module.js里

console.log(import.meta)

// {url: “http://localhost/3ag/js/module.js”}

如果需要在配置了 Webpack 的项目,比如 Vue 里使用 import.meta 需要加一个包且配置一下参数,否则项目编译阶段会报错。

包配置详情参考:

  • @open-wc/webpack-import-meta-loader

比如我用的是 4.x 版本的 vue-cli,那我需要在 vue.config.js 里配置:

module.exports = {

chainWebpack: config => {

config.module

.rule(‘js’)

.test(/.js$/)

.use(‘@open-wc/webpack-import-meta-loader’)

.loader(‘@open-wc/webpack-import-meta-loader’)

.end()

}

}

可选链操作符(?.)

通常我们获取一个深层对象的属性会需要写很多判断或者使用逻辑与 && 操作符,因为对象的某个属性如果为 null 或者 undefined 就有可能报错:

let obj = {

first: {

second: ‘布兰’

}

}

// 写法一

let name1 = ‘’

if (obj) {

if (obj.first) {

name1 = obj.first.second

}

}

// 写法二

let name2 = obj && obj.first && obj.first.second

?. 操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。如果某个属性为 null 或者 undefined 则结果直接为 undefined。有了可选链操作符就可以使得表达式更加简明了,对于上面例子用可选链操作符可以这么写:

let name3 = obj?.first?.second

空值合并操作符(??)

对于逻辑或 || 运算符,当对运算符左侧的操作数进行装换为 Boolean 值的时候,如果为 true,则取左边的操作数为结果,否则取右边的操作数为结果:

最后

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

必须在一个模块里使用:

// 没有声明 type=“module”,就使用 import.meta 会报错

// 在module.js里

console.log(import.meta)

// {url: “http://localhost/3ag/js/module.js”}

如果需要在配置了 Webpack 的项目,比如 Vue 里使用 import.meta 需要加一个包且配置一下参数,否则项目编译阶段会报错。

包配置详情参考:

  • @open-wc/webpack-import-meta-loader

比如我用的是 4.x 版本的 vue-cli,那我需要在 vue.config.js 里配置:

module.exports = {

chainWebpack: config => {

config.module

.rule(‘js’)

.test(/.js$/)

.use(‘@open-wc/webpack-import-meta-loader’)

.loader(‘@open-wc/webpack-import-meta-loader’)

.end()

}

}

可选链操作符(?.)

通常我们获取一个深层对象的属性会需要写很多判断或者使用逻辑与 && 操作符,因为对象的某个属性如果为 null 或者 undefined 就有可能报错:

let obj = {

first: {

second: ‘布兰’

}

}

// 写法一

let name1 = ‘’

if (obj) {

if (obj.first) {

name1 = obj.first.second

}

}

// 写法二

let name2 = obj && obj.first && obj.first.second

?. 操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。如果某个属性为 null 或者 undefined 则结果直接为 undefined。有了可选链操作符就可以使得表达式更加简明了,对于上面例子用可选链操作符可以这么写:

let name3 = obj?.first?.second

空值合并操作符(??)

对于逻辑或 || 运算符,当对运算符左侧的操作数进行装换为 Boolean 值的时候,如果为 true,则取左边的操作数为结果,否则取右边的操作数为结果:

最后

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-mC0MWczR-1715477942070)]

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值