文章目录
-
-
-
- [1. ES6 - 对象字面量增强](#1. ES6 - 对象字面量增强)
- [2. ES6 - 对象和数组的解构](#2. ES6 - 对象和数组的解构)
- [3. ES6 - let / const 的基本使用](#3. ES6 - let / const 的基本使用)
- [4. ES6 - let / const 的作用域提升](#4. ES6 - let / const 的作用域提升)
- [5. ES6 - let / const 和 window 的关系](#5. ES6 - let / const 和 window 的关系)
- [6. ES6 - 新增的块级作用域](#6. ES6 - 新增的块级作用域)
- [7. ES6 - 模板字符串的使用](#7. ES6 - 模板字符串的使用)
- [8. ES6 - 标签模板字符串](#8. ES6 - 标签模板字符串)
- [9. ES6 - 函数参数的默认值](#9. ES6 - 函数参数的默认值)
- [10. ES6 - 函数的剩余参数](#10. ES6 - 函数的剩余参数)
- [11. ES6 - 箭头函数的补充](#11. ES6 - 箭头函数的补充)
- [12. ES6 - 展开语法的使用](#12. ES6 - 展开语法的使用)
- [13. ES6 - 二进制和八进制连接符](#13. ES6 - 二进制和八进制连接符)
- [14. ES6 - Symbol 数据类型的使用](#14. ES6 - Symbol 数据类型的使用)
- [15. ES6 - 数据结构 Set(元素不可重复)](#15. ES6 - 数据结构 Set(元素不可重复))
- [16. ES6 - 数据结构 WeakSet(存放对象)](#16. ES6 - 数据结构 WeakSet(存放对象))
- [17. ES6 - 数据结构 Map(支持对象属性 key)](#17. ES6 - 数据结构 Map(支持对象属性 key))
- [18. ES6 - 数据结构 WeakMap(key只能是对象)](#18. ES6 - 数据结构 WeakMap(key只能是对象))
- [19. ES7 - Array.includes 方法和指数运算符](#19. ES7 - Array.includes 方法和指数运算符)
- [20. ES8 - Object.values、Object.entries 方法](#20. ES8 - Object.values、Object.entries 方法)
- [21. ES8 - String.padStart、String.padEnd 方法](#21. ES8 - String.padStart、String.padEnd 方法)
- [22. ES10 - Array.flat、Array.flatMap 方法](#22. ES10 - Array.flat、Array.flatMap 方法)
- [23. ES10 - Object.fromEntries、String.trimStart 方法](#23. ES10 - Object.fromEntries、String.trimStart 方法)
- [24. ES11 - 大整数类型 Bigint](#24. ES11 - 大整数类型 Bigint)
- [25. ES11 - 空值合并运算符 ??](#25. ES11 - 空值合并运算符 ??)
- [26. ES11 - 可选链操作符 ?.](#26. ES11 - 可选链操作符 ?.)
- [27. ES11 - globalThis 和 for...in 标准化](#27. ES11 - globalThis 和 for...in 标准化)
- [28. ES12 - FinalizationRegistry 执行清理回调](#28. ES12 - FinalizationRegistry 执行清理回调)
- [29. ES12 - WeakRef 创建对象的弱引用](#29. ES12 - WeakRef 创建对象的弱引用)
- [30. ES12 - 逻辑赋值运算符、String.replaceAll()](#30. ES12 - 逻辑赋值运算符、String.replaceAll())
- [31. ES13 - Array/String.at()、Object.hasOwn()](#31. ES13 - Array/String.at()、Object.hasOwn())
- [32. ES13 - 类中新增的成员(私有化成员)](#32. ES13 - 类中新增的成员(私有化成员))
-
-
1. ES6 - 对象字面量增强
对象字面量的增强写法主要体现在以下几个方面,以提高代码的可读性、可维护性和功能性:
- 属性简写:当属性名和变量名相同时,可以省略属性名,直接写变量名
- 方法简写:可以省略 function 关键字,直接写方法名
- 计算属性名:使用表达式的结果作为对象属性的键名
当前有多个变量,想要把它们放入到对象中,并且在对象中定义一个方法,在 ES6 之前是这样写的:
javascript
const name = 'liang'
const age = 20
const object = {
name: name,
age: age,
foo: function () { }
}
属性简写和方法简写的使用示例:
javascript
const name = 'liang'
const age = 20
const object = {
// property shorthand (属性简写)
name,
age,
// method shorthand (属性简写)
foo() {
}
}
可以使用箭头函数作为方法,这样可以更简洁地定义函数,但是要注意箭头函数中的 this 指向
javascript
const object = {
foo() {
console.log(this) // { foo: ƒ, bar: ƒ }
},
bar: () => {
console.log(this) // Window { }
}
}
计算属性名:[] 是计算属性名的语法,使用表达式的结果作为对象属性的键名
javascript
const name = 'liang'
const object = {
// ES6 计算属性名,等价于 liang456
// 它告诉 JavaScript 引擎,属性名不是直接写死的标识符或字符串,而是要计算 [] 内部表达式的结果
[name + 456]() {
console.log('ES6 的计算属性写法');
}
}
object[name + '123'] = function () {
console.log('ES6 之前的计算属性写法');
}
object.liang123()
object.liang456()
2. ES6 - 对象和数组的解构
ES6 解构赋值是一种强大的语法特性,它允许你从数组或对象中提取数据,并将其赋值给变量
这种语法使得从复杂的数据结构中提取数据变得更加简洁和直观,这个语法非常重要,请移步:解构赋值用法详解
javascript
// 现有一个数组或对象,要将里面的多个值取出来
const arr = ['html', 'css', 'js']
// 在 ES6 之前,是这样获取的
const item1 = arr[0]
const item2 = arr[1]
const item3 = arr[2]
// ES6 新增解构赋值,可以这样获取
const [value1, value2, value3] = arr
// 对象的解构赋值
const { name, age } = { name: 'liang', age: 20 }
3. ES6 - let / const 的基本使用
在 ES5 中声明变量都是使用的 var 关键字,从 ES6 开始新增了两个关键字可以声明变量:let、const
javascript
// variable 变量
var a = 1
let b = 2
// constant 常量
const c = 3
通过 let、const 声明的变量不可以重复定义
javascript
var a = 1
var a = 1
let b = 2
// let b = 2 // Uncaught SyntaxError: Identifier 'b' has already been declared
const c = 3
// const c = 3 // Uncaught SyntaxError: Identifier 'c' has already been declared
const 本质上是传递的值不可以修改,如果传递的是引用类型,可以通过引用找到对象,去修改对象内部的属性
javascript
// const 本质上是传递的值不可以修改
// 但是,如果传递的是一个引用类型(内存地址),可以通过引用找到对应的对象,去修改对象内部的属性
const obj = { name: 'liang' }
obj.name = 'hello'
4. ES6 - let / const 的作用域提升
let、const 和 var 的另一个重要区别是作用域提升:
- var 声明的变量是会进行作用域提升的,在声明之前使用该变量不会报错,只是没有值
- 使用 let、const 声明的变量,在声明之前使用该变量会报错
javascript
console.log(a) // undefined
var a = 1
console.log(b) // Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 1
那么是不是意味着 let、const 声明的变量只有在代码执行阶段才会创建呢 ?(很多人以为是没有被创建的)
- 事实上并不是这样的,可以看一下 ECMA262 对 let 和 const 的描述:
- 这些变量会被创建在包含它们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值
对作用域提升的理解:
- 在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么可以称之为作用域提升
- 在这里,它虽然被创建出来了,但是不能被访问,不能称之为作用域提升
暂时性死区:
- 它表达的意思是在一个代码中,使用 let/const 声明的变量,在声明之前变量都是不可访问的
- 我们将这种现象称之为暂时性死区
5. ES6 - let / const 和 window 的关系
Window 对象添加属性:
- 全局通过 var 声明的变量,会被添加到 window 对象上,作为 window 对象的属性
- let、const 是不会给 window 上添加任何属性的
javascript
var name = 'liang'
console.log(window.name) // liang
let title = 'vue'
console.log(window.title) // undefined
6. ES6 - 新增的块级作用域
在 ES5 中只有两个东西会形成作用域:全局作用域、函数作用域
javascript
// 全局作用域
var name = 'liang'
// 函数作用域
function foo() {
var bar = 'function'
}
块代码的结构:
javascript
// {} 块代码(block code)
{
var a = 1
}
// 这里的 {} 不是块代码,这是对象字面量
var obj = { name: 'liang' }
在 ES6 中新增了块级作用域,并且通过 let、const、function、class 声明的标识符是具备块级作用域限制的
我们会发现函数拥有块级作用域,但是在外面依然是可以访问的
- 这是因为引擎会对函数的声明进行特殊的处理,允许像 var 那样进行提升
javascript
{
var a = 1
let foo = 'liang'
function demo() {
console.log('demo')
}
}
console.log(a) // 1(对 var 声明的变量是无效的,所以这里也能正常输出)
// console.log(foo) // Uncaught ReferenceError: foo is not defined
// 不同的浏览器有不同的实现(大部分浏览器为了兼容以前的代码,让 function 没有块级作用域)
demo() // 此时可以正常调用(如果使用只支持 ES6 的浏览器,是无法访问的)
if、switch、for 语句都是块级作用域
javascript
// if 语句的代码就是块级作用域
if (true) {
let foo = 'foo'
}
console.log(foo) // Uncaught ReferenceError: foo is not defined
// switch 语句的代码也是块级作用域
let color = 'red'
switch (color) {
case 'red':
var bar = 'bar'
let bgColor = '#000'
}
console.log(bar)
console.log(bgColor) // Uncaught ReferenceError: bgColor is not defined
// for 语句的代码也是块级作用域(这个场景用的最多,强烈推荐使用 let)
for (var i = 0; i < 5; i++) { }
console.log(i) // 5
for (let j = 0; j < 5; j++) { }
console.log(j) // Uncaught ReferenceError: j is not defined
我们来看一下 for 循环语句中,起始变量使用 var 声明的导致的问题:无论点击哪个按钮输出结果都是一样的
html
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
javascript
const btns = document.getElementsByTagName('button')
// 特别注意:这里是 var 声明的起始变量 i
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log('第' + i + '个按钮被点击') // 第3个按钮被点击
}
}
这是因为 var 声明的变量 i 是全局作用域,函数中没有变量 i 就会往上级作用域找,循环结束后 i 就已经变为 3
学习过块级作用域后,只需要将 i 的声明从 var 改为 let 即可,这就是块级作用域给我们带来的好处
javascript
// 使用 let 声明起始变量 i,使其形成块级作用域
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log('第' + i + '个按钮被点击')
}
}
7. ES6 - 模板字符串的使用
ES6 模板字符串是一种增强型的字符串表示方式,使用反引号包裹内容,支持在字符串中嵌入变量、表达式以及多行文本,极大的简化了字符串拼接和格式化操作。这是一个非常实用的特性,提升了的字符串处理的灵活性和代码的可读性
javascript
// ES6 之前拼接字符串和其他标识符
const name = 'liang'
const age = 20
const height = 180
console.log('my name is ' + name + ', age is ' + age + ', height is ' + height);
基本语法:模板字符串通过反引号定义,可以包含变量和表达式,使用 ${} 语法进行插入
javascript
const string = `my name is ${name}, age is ${age + 5}, height is ${height}`
多行文本:模板字符串天然支持多行文本,无需使用 \n 和字符串拼接,可以直接换行书写
plaintext
const html = `<div>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
</div>`
8. ES6 - 标签模板字符串
标签模板字符串是 ES6 中的一个高级特性,它允许你通过一个函数来处理模板字符串
这个函数接收模板字符串的各个部分作为参数,并且可以根据需要对这些部分进行处理,从而实现自定义的字符串处理逻辑
- 我们自己很少这样编写函数,但是有些框架会让我们这样调用它编写的函数(比如: React 框架)
javascript
// 当使用标签模板字符串调用函数时
// 第一个参数:依然是模版字符串中的整个字符串,只是被切成了多块,放到了一个数组中
// 第二个参数:模板字符串中的第一个 ${}
// 第三个参数:模板字符串中的第二个 ${}
function foo(m, n, x) {
console.log({ m, n, x });
}
// 普通调用
// foo('hello', 'world', '!')
// 标签模板字符串调用
const name = 'liang'
const age = 18
foo`hello${name}world${age}!`
9. ES6 - 函数参数的默认值
在 ES6 中,函数参数的默认值功能极大的简化了函数定义和调用过程,相比 ES5 时代的写法更加直观和安全
javascript
// ES5 及之前给参数默认值,写起来很麻烦,并且代码的阅读性比较差,还有 bug(比如:传入一个 0)
function foo(m) {
// 没有传递参数 m 默认是 undefined
m = m || '默认值'
}
foo()
// ES6 可以给函数的参数设置默认值
function foo(m = '默认值') {
// ...
}
// 对象参数和默认值以及解构
function foo({ name, age, gender = '男' } = { name: 'liang', age: 20 }) {
// ...
}
函数的 length 属性值为其参数数量,对于有参数默认值的函数,从有默认值的参数开始,后续参数不做数量统计
javascript
// 函数的 length 属性值为其参数数量
function foo(a, b) { }
console.log(foo.length) // 2
// 对于有参数默认值的函数,从有默认值的参数开始,后续参数不做数量统计
function bar(a, b, c = 3) { }
console.log(bar.length) // 2
// 实际上有默认值的参数一般都是放到最后,下面这种写法不推荐
function info(a, b = 2, c) { }
console.log(info.length) // 1 (有默认值的参数后续其他参数不做数量统计)
10. ES6 - 函数的剩余参数
ES6 中的剩余参数(Rest Parameter)是一种强大特性,可以将不定数量的参数放入到一个数组
javascript
// 如果最后一个参数是以 ... 为前缀的,那么它会将剩余参数放到该参数中,并且作为一个数组
// 并且剩余参数只能放在最后一个参数(放在前面会报错)
function foo(a, b, ...args) {
console.log(a) // 1
console.log(b) // 2
console.log(args) // [3, 4, 5]
}
foo(1, 2, 3, 4, 5)
那么,剩余参数和 arguments 有什么区别呢 ?
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参
- arguments 对象并不是一个真正的数组,而剩余参数是一个真正的数组,可以进行数组的所有操作
- arguments 是早期 ECMAScript 中为了方便去获取所有的参数提供的一个数据结构,而剩余参数是 ES6 中提供的,并且希望以此来替代 arguments 的
11. ES6 - 箭头函数的补充
箭头函数是没有显式原型的,所以不能作为构造函数,无法通过 new 来创建对象
javascript
const foo = () => { }
console.log(foo.__proto__) // undefined
new foo(); // Uncaught TypeError: foo is not a constructor
12. ES6 - 展开语法的使用
展开语法(Spread Syntax)最初在 ES6 中引入,主要用于数组操作,到了 ES9,展开语法被扩展到了对象处理
展开语法允许我们将数组或对象的元素展开为独立的元素,它使用三个 ... 表示:
- 可以在函数调用、数组构造时,将数组表达式或 string 在语法层面展开
- 还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开
展开语法的场景:
- 在函数调用时使用
- 在数组构造时使用
- 在构建对象字面量时,也可以使用展开运算符,这个是在 ES9 中添加的新特性
javascript
// 1. 在函数调用时使用展开语法
function foo(a, b, c) {
console.log({ a, b, c })
}
foo(...['html', 'css', 'js'])
// foo(...'html') // 也可以展开一个字符串
// 2. 构造数组时
const name = 'liang'
const hobby = ['html', 'css']
const newArr = [...name, ...hobby]
console.log(newArr)
// 3. 构建对象字面量 ES9(ES2018)
const info = { name: 'liang', age: 20 }
const object = { title: 'profile', ...info, gender: 1 }
console.log(object)
展开运算符进行的是一个浅拷贝
javascript
const info = { name: 'liang', profile: { age: 18 } }
const user = { ...info }
user.profile.age = 20
console.log(info.profile.age) // 20
13. ES6 - 二进制和八进制连接符
javascript
const num1 = 100 // 十进制
// b -> binary
const num2 = 0b100 // 二进制
// o -> octonary
const num3 = 0o100 // 八进制
// x -> hex
const num4 = 0x100 // 十六进制
console.log(num1) // 100
console.log(num2) // 4
console.log(num3) // 64
console.log(num4) // 256
// ES12(ES2011)对于大的数组,可以使用连接符 _
// 这种写法常用于提高大数字的可读性,划线 _ 作为数字分隔符,方便识别位数
const num = 10_000_000_000
console.log(num) // 10000000000
14. ES6 - Symbol 数据类型的使用
Symbol 是 ES6 中新增的一个基本数据类型,翻译为:符号
那么,Symbol 有什么用,为什么需要 Symbol 呢 ?
- 在 ES6 之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
- 比如: 原来有一个对象,想在其中添加一个属性和值,但是在我们不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖它内部的某个属性
javascript
// ES6 之前,对象的属性名 key 都是字符串形式
var obj = { name: 'liang' }
// 如果给 obj.name 赋值,会覆盖原来的值
obj.name = 'hello'
console.log(obj.name) // hello
Symbol 就是为了解决上面的问题,用来生成一个独一无二的值
- Symbol 值是通过 Symbol() 函数来生成的,生成后可以作为属性名
- 也就是在 ES6 中,对象的属性名可以使用字符串,也可以使用 Symbol 值
Symbol 即使多次创建值,它们也是不同的:Symbol 函数执行后,每次创建出来的值都是独一无二的
我们也可以在创建 Symbol 值的时候传入一个描述 description:这个是 ES10(ES2019)新增的特性
javascript
// Symbol() 可以生成一个独一无二的值,可以让它作为对象的 key
const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2) // false
// 创建 Symbol 值的时候传入一个描述
const s3 = Symbol('aaa')
console.log(s3.description) // aaa
Symbol 值作为对象的属性 key 的多种写法:
javascript
const s1 = Symbol()
const s2 = Symbol()
const s3 = Symbol()
const s4 = Symbol()
// 1. 在定义对象字面量时使用
const obj = {
[s1]: '111',
[s2]: '222',
}
// 2. 新增属性
obj[s3] = '333'
// 3. Object.defineProperty 方式
Object.defineProperty(obj, s4, {
enumerable: true,
configurable: true,
writable: true,
value: '444',
})
// 注意:不能通过.语法获取(obj.s4 是获取不到的,字符串 key 才能这样用)
// 获取属性值的正确写法: 对象[Symblod值]()
console.log(obj[s1]) // 111
使用 Symbol 作为 key,在遍历或 Object.keys 等中是获取不到这些 Symbol 值的,有专门的方法去获取
javascript
const s1 = Symbol()
const s2 = Symbol()
const obj = { [s1]: '111', [s2]: '222' }
// 使用 Symbol 作为 key,无法获取不到这些 Symbol 值
console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertyNames(obj)) // []
// 但是,可以使用 Object.getOwnPropertySymbols() 来获取所有 Symbol 的 key
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const item of symbolKeys) {
console.log(obj[item]);
}
前面我们说过,Symbol() 返回的值都是独一无二的,那么有没有办法让它返回相同的值呢 ?
Symbol.for(key) 是 ES6 中的一个静态方法,用于创建或查找全局注册表中的 Symbol
- 如果全局注册表中已经存在一个与给定键 key 关联的 symbol, 则返回该 symbol
- 如果不存在,会创建一个新的 symbol,并将其与该键关联后放入全局注册表中
javascript
// Symbol.for(key)
const s1 = Symbol.for('aaa')
const s2 = Symbol.for('aaa')
console.log(s1 === s2) // true
// Symbol.keyFor() 用于获取全局注册表中 Symbol 对应的键名
const key = Symbol.keyFor(s1)
console.log(key) // aaa
15. ES6 - 数据结构 Set(元素不可重复)
在 ES6 之前,存储数据的结构主要有两种:数组、对象
- 在 ES6 中新增了另外两种数据结构:Set、Map,以及它们的另外形式:WeakSet、WeakMap
Set 是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是 Set 的元素不能重复
- 创建 Set 我们需要通过 Set 构造函数(暂时没有字面量创建的方式)
- 因为 Set 中存放的元素是不会重复的,那么它有一个很常用功能,就是数组去重
javascript
// 通过构造函数创建 Set 结构
const set = new Set()
// set.add 向 Set 结构中添加元素
set.add(10)
set.add(20)
set.add(30)
set.add(30) // 不会被重复添加,因为结构中已有该元素
// 添加对象时,要注意,下面这样可以被重复添加,因为它们引用地址是不同的
set.add({})
set.add({})
console.log(set);
因为 Set 结构具有元素不会重复的特性,所以我们可以用于 数组去重
javascript
// 数组去重
const arr = [32, 10, 25, 10, 32, 15]
// 传统方式
const newArr = []
arr.forEach(item => {
if (newArr.indexOf(item) === -1) newArr.push(item);
})
console.log(newArr)
// 利用 set 的特性去重
const arrSet = new Set(arr)
// const newArr2 = Array.from(arrSet)
const newArr3 = [...arrSet]
Set 的常见属性和方法:
javascript
const set = new Set([32, 10, 25, 10, 32, 15])
console.log(set) // Set(4) { 32, 10, 25, 15 }
// size 属性:获取元素数量
console.log(set.size) // 4
// 添加和删除元素
set.add(18)
set.delete(10)
// 判断元素是否在结构中
console.log(set.has(25)) // true
// 清空 set 中的元素
// set.clear()
console.log(set) // Set(4) { 32, 25, 15, 18 }
对 Set 结构进行遍历:
javascript
const set = new Set([32, 10, 25, 10, 32, 15])
console.log(set) // Set(4) { 32, 10, 25, 15 }
set.forEach(item => {
console.log(item)
})
console.log('- - - - - -');
for (const item of set) {
console.log(item)
}
16. ES6 - 数据结构 WeakSet(存放对象)
和 Set 类似的另外一个数据结构称之为 WeakSet,也是内部元素不能重复的数据结构
那么和 Set 有什么区别呢 ?
- WeakSet 中只能存放对象类型,不能存放基本数据类型
- WeakSet 对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么 GC 可以对该对象进行回收
WeakSet 常见的方法:
- add(value):添加某个元素,返回 WeakSet 对象本身
- delete(value):从 WeakSet 中删除和这个值相等的元素,返回 boolean 类型
- has(value):判断 WeakSet 中是否存在某个元素,返回 boolean 类型
javascript
const weakSet = new WeakSet()
// 区别一:只能存放对象类型,无法添加基本类型数据
// weakSet.add(10) // Uncaught TypeError: Invalid value used in weak set
// 区别二:WeakSet 对数据是一个弱引用,而 Set 对数据是一个强引用
let obj = { name: 'liang' }
weakSet.add(obj)
对强引用和弱引用的理解:
javascript
// 因为 Set 是强引用,所以当 user 修改为 null 时,user 不会被 GC 回收
let user = { name: 'liang' }
const set = new Set(user)
user = null
// 因为 WeakSet 是弱引用,所以当 profile 修改为 null 时,profile 就被 GC 回收掉了
let profile = { name: 'liang' }
const weakSet = new WeakSet(profile)
profile = null
17. ES6 - 数据结构 Map(支持对象属性 key)
Map 是一种新的数据结构,用于存储键值对,它与传统对象(Object)相比,具有更灵活的键类型和更好的性能特性
Map 用于存储映射关系,之前我们可以使用对象来存储映射关系,它们有什么区别呢 ?
- 事实上对象存储映射关系只能用字符串(ES6 新增了 Symbol)作为属性名(key)
- 某些情况下,可能希望使用其他数据类型作为 key。比如: 对象,这个时候会自动将对象转为字符串来作为 key
javascript
// 传统的对象属性名 key,只能是字符串或Symbol值
const user = { name: 'liang' }
const profile = { age: 20 }
const object = { [user]: 'aaa', [profile]: 'bbb' }
// [] 是计算属性名的写法,将表达式的结果作为属性名
// 因为会将属性名转为字符串,所以都变为了 [object Object],后者覆盖前面,然后输出以下结果
console.log(object) // { [object Object]: 'bbb' }
// Map 就是允许对象类型作为 key 的
const map = new Map()
map.set(user, 'aaa')
map.set(profile, 'bbb')
map.set('name', 'liang') // 也可以传普通字符串
console.log(map)
可以通过构造函数来创建一个空的 Map,也可以传入一个包含键值对数组的参数来初始化 Map
javascript
// 创建一个空的 Map
let map = new Map()
// 创建一个带有初始值的 Map
let map2 = new Map([['name', 'liang'], ['music', '天空之外']])
Map 常见属性和方法:
- size 属性:获取 Map 中键值对的数量
- set() 方法:添加或更新键值对,返回整个 Map 对象,支持链式调用
- get() 方法:根据键获取对应的值
- has() 方法:检测 Map 中是否存在指定的键
- delete() 方法:删除指定键对应的键值对
- clear() 方法:清空键值对
javascript
const map = new Map([['name', 'liang'], ['music', '天空之外']])
console.log(map.size) // 2
// map.set() 添加或更新键值对
map.set('age', 20).set('height', 190)
// map.get() 根据键获取对应的值
console.log(map.get('name')) // liang
// map.has() 检测是否存在指定的键
console.log(map.has('age')) // true
// map.delete() 删除指定键对应的键值对
map.delete('height')
// map.clear() 清空键值对
map.clear()
console.log(map)
遍历 Map 也有多种方式:
javascript
const map = new Map([['name', 'liang'], ['music', '天空之外']])
map.forEach((value, key) => {
console.log(key, value)
})
for (const [key, value] of map) {
console.log(key, value)
}
18. ES6 - 数据结构 WeakMap(key只能是对象)
和 Map 相似的另外一个数据结构称之为 WeakMap,也是以键值对的形式存在的
WeakMap 和 Map 有什么区别呢 ?
- WeakMap 的 key 只能使用对象,不接受其他的类型作为 key
- WeakMap 的 key 对对象的引用是弱引用,如果没有其他引用引用这个对象,那么 GC 可以回收该对象
javascript
// Map 是强引用,所以当 obj 修改为 null 时,obj 不会被 GC 回收
let obj = { name: 'liang' }
const map = new Map()
map.set(obj, 'aaa')
obj = null
// WeakMap 是弱引用,所以当 obj2 修改为 null 时,obj2 就被 GC 回收掉了
let obj2 = { name: 'liang' }
const map2 = new WeakMap()
map2.set(obj2, 'bbb')
obj2 = null
19. ES7 - Array.includes 方法和指数运算符
之前判断数组中是否存在某个元素,通常是判断索引值,ES7 新增了 includes 方法,可以直接判断数据是否在数组中
javascript
const arr = ['html', 'css', 'js']
// 判断索引值
if (arr.indexOf('css') !== -1) {
console.log('在数组中')
}
// 支持传入第二个参数指定从哪个位置判断 arr.includes('css', 1)
if (arr.includes('css')) {
console.log('在数组中')
}
那么 indexOf 和 includes 有没有什么区别呢 ?当然是有的
javascript
const arr = ['html', 'css', NaN]
// indexOf 无法正确判断 NaN
console.log(arr.indexOf(NaN)) // -1
// includes 可以正确判断 NaN
console.log(arr.includes(NaN)) // true
ES7 新增的指数运算符:
javascript
// 2 的 5 次方(之前指数运算的写法)
console.log(Math.pow(2, 5))
// ES7 增加了一个运算符 **
console.log(2 ** 5)
20. ES8 - Object.values、Object.entries 方法
之前我们可以通过 Object.keys() 获取一个对象的所有 key,在 ES8 中提供了 Object.values() 来获取所有的 value 值
javascript
// 常见用法
const user = { name: 'liang', age: 20, height: 180 }
console.log(Object.keys(user)) // ['name', 'age', 'height']
console.log(Object.values(user)) // ['liang', 20, 180]
// 用于数组和字符串(很少用)
console.log(Object.values(['html', 'css', 'js'])) // ['html', 'css', 'js'] (原数组本身)
console.log(Object.values('hello')) // ['h', 'e', 'l', 'l', 'o']
通过 Object.entries() 可以获取到一个数组,数组中会存放可以枚举属性的键值对数组
javascript
// 一帮情况下,使用它是为了让我们方便进行遍历操作的
const user = { name: 'liang', age: 20, height: 180 }
console.log(Object.entries(user))
console.log(Object.entries('html'))
console.log(Object.entries(['html', 'css', 'js']))
21. ES8 - String.padStart、String.padEnd 方法
在 ES8 中,引入了两个字符串方法:padStart、padEnd,用于在字符串的开头或结尾填充指定字符,使其达到指定长度
语法格式:
- targetLength:目标字符串长度
- padString:用于填充的字符串,默认为空格
- 返回值为填充后的字符串,不会修改原字符串
plaintext
string.padStart(targetLength[, padString])
string.padEnd(targetLength[, padString])
使用示例:
javascript
const string = 'hello'
console.log(string)
// 目标长度8,默认填充空格
console.log(string.padStart(8))
// 目标长度8,填充指定符号
console.log(string.padStart(8, '*'))
// 从结尾填充,目标长度8,填充指定符号
console.log(string.padEnd(8, '*'))
那么,有哪些场景可以用到字符串填充呢 ?
javascript
// 时间格式化
function formatTime(time) {
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 传入一个11位的手机号,将中间四位修改为 '*'
function formatMobile(mobile) {
return mobile.slice(0, 3).padEnd(7, '*') + mobile.slice(-4)
}
尾逗号(Trailing Commas)是指在数组、对象字面量或函数参数列表的最后一个元素后添加的额外逗号
这一特性自 ES5 起已被标准正式支持,并在 ES2017(ES8)中扩展至函数参数
尽管看似冗余,但它显著提升了代码的可维护性,尤其是在多行结构中增删项时,可以避免因遗漏逗号而引发的语法错误
javascript
// ES8 才开始支持函数接收参数时或调用时最后面还有逗号
function foo(m, n,) {
console.log({ m, n });
}
foo(111, 222,)
22. ES10 - Array.flat、Array.flatMap 方法
flat() 和 flatMap() 是两个用于处理数组扁平化的常用方法,它们各有不同的用途和特点
- flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历的子数组中的元素合并为一个新数组返回
- flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩为一个新数组
- flatMap 是先进行 map 操作,再做 flat 的操作
- flatMap 中的 flat 相当于深度为 1
语法格式:
javascript
// depth 是可选参数,表示要展开的嵌套层级,默认为 1。如果传入 Infinity,则会完全展开所有层级的嵌套数据
const newArray = array.flat([depth = 1])
使用示例:
javascript
const nums = [1, [2], [[3]], [[[4]]]]
console.log(nums)
console.log(nums.flat(1)) // [1, 2, Array(1), Array(1)]
console.log(nums.flat(2)) // [1, 2, 3, Array(1)]
console.log(nums.flat(Infinity)) // [1, 2, 3, 4]
flatMap() 方法的使用方法,通过以下代码示例学习:
javascript
// 需求:将数组元素中的每个单词抽离出来
const slogan = ['what are you doing', 'how old are you']
// 刚学习完 flat() 方法,你可能会这么做
const words = slogan.map(item => item.split(' ')).flat()
console.log(words) // ['what', 'are', 'you', 'doing', 'how', 'old', 'are', 'you']
// 现在告诉你,可以使用 flatMap() 简化代码,它会先执行 map() ,然后再进行 flat()
const words2 = slogan.flatMap(item => item.split(' '))
console.log(words2) // ['what', 'are', 'you', 'doing', 'how', 'old', 'are', 'you']
23. ES10 - Object.fromEntries、String.trimStart 方法
Object.fromEntries() 方法用于将键值对列表转换为一个普通对象
javascript
const user = { name: 'liang', age: 20 }
console.log(user) // { name: 'liang', age: 20 }
// 如何根据 entries 结果,再将数据处理成对象
const entries = Object.entries(user)
console.log(entries) // [ Array(2), Array(2) ]
// 传统方式是这样做的
const newObj = {}
for (const [key, value] of entries) {
newObj[key] = value
}
console.log(newObj) // { name: 'liang', age: 20 }
// 还可以通过 Object.fromEntries() 方法实现
console.log(Object.fromEntries(entries)) // { name: 'liang', age: 20 }
Object.fromEntries() 方法的应用场景,配置 URLSearchParams() 处理查询字符串
javascript
const queryStrig = 'name=liang&age=20&height=180'
const queryParams = new URLSearchParams(queryStrig)
// 它是一个可迭代的对象
for (const item of queryParams) {
console.log(item)
}
// 将它转为一个普通对象
const params = Object.fromEntries(queryParams)
console.log(params) // { name: 'liang', age: '20', height: '180' }
清除字符串中的空格
javascript
const string = ' hello world '
// 清除前后两边的空格(ES5)
console.log({ trim: string.trim() })
// 清除前面的空格(ES10)
console.log({ trimStart: string.trimStart() })
// 清除后面的空格(ES10)
console.log({ trimEnd: string.trimEnd() })
24. ES11 - 大整数类型 Bigint
在早期的 JavaScript 中,我们不能正确的表示过大的数字
- 大于
Number.MAX_SAFE_INTEGER的数值,表示的值可能是不正确的
javascript
// ES11 之前 max_safe_integer
const maxInt = Number.MAX_SAFE_INTEGER
console.log(maxInt) // 9007199254740991
// 可以发现 +1 和 +2 的值竟然是相同的
console.log(maxInt + 1) // 9007199254740992
console.log(maxInt + 2) // 9007199254740992
// ES11 之后:Bigint( 后面加一个 n )
const bigInt = 900719925474099100n
console.log(bigInt) // 900719925474099100n
// 直接进行运算是会报错的,需要在 10 后面加个 n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
// console.log(bigInt + 10)
console.log(bigInt + 10n) // 900719925474099110n
// 如果是一个变量,可以先使用 BigInt() 转为 bigint,再进行运算
const num = 20
console.log(bigInt + BigInt(20)) // 900719925474099120n
25. ES11 - 空值合并运算符 ??
空值合并运算符(Nullish Coalescing Operator),当左侧操作数为 null/undefined 时,返回右侧操作数
- 只有
null和undefined被视为空值,其他假值不会触发 fallback(右侧的后备方案)
javascript
// 之前获取值时,设置默认值大多使用逻辑或实现,这种是存在弊端的(比如:对于 0、false)
let foo = 0
console.log(foo || 'hello') // hello
// 空值合并运算符
let bar = 0
console.log(bar ?? 'hello') // 0
26. ES11 - 可选链操作符 ?.
ES11 进入了一个非常实用的特性:可选链操作符(Optional Chaining Operator)
它的核心作用是:安全的访问嵌套对象的属性或方法,避免因为中间某一层为 null 或 undefined 而抛出错误
javascript
const profile = { name: 'liang' }
// 以下代码会抛出错误,这是因为 profile.info 是 undefined,又访问了 undefined 的 age 属性
// Uncaught TypeError: Cannot read properties of undefined (reading 'age')
console.log(profile.info.age)
// 使用可选链操作符,属性不存在也不会报错
console.log(profile.info?.age)
当对接后端接口时,接口中的字段值可能出现意料之外的情况,导致前端代码报错,我们应该怎么避免这种问题
javascript
// 比如:当前查询某个列表,data 应该始终为一个数组,但是后端接口不规范
// 后端提供的接口,当没有数据时,data 给的是一个 null,导致前端代码报错
// 我对接Java 后端接口时,这个问题出现很多次,而且对方还不想修改,说是改起来比较麻烦/框架问题
const res = { code: 200, msg: 'success', data: null }
// 报错原因:res.data 为 null,调用了 null.forEach 就报错了
// Uncaught TypeError: Cannot read properties of null (reading 'forEach')
// res.data.forEach(item => { })
// 使用 ES11 的可选链,可以很简单的处理掉这个问题
res.data?.forEach(item => { })
27. ES11 - globalThis 和 for...in 标准化
在 ES11 之前,我们想要获取当前环境的全局对象,不同的环境获取方式是不一样的
- 比如:在浏览器中可以通过 this、window 来获取
- 在 node 中我们需要通过 global 来获取
globalThis 是 ES11 引入的一个标准化的全局对象引用,用于在任何 JavaScript 环境中统一访问全局对象
javascript
// 浏览器环境(使用浏览器打开)
console.log(this)
console.log(window)
// node 环境(终端运行命令: "node 文件名" 可查看)
console.log(global)
// ES11(在任何环境中都可以访问全局对象)
console.log(globalThis)
ES11 对 for...in 语句进行了标准化规范
javascript
const user = { name: 'liang', age: 20 }
// ES11 之前,ECMA 对 for...in 没有制定标准,部分浏览器中 item 指向的可能是 value,而不是 key
// 在 ES11 中,ECMA 对 for...in 进行标准化规范,至此 item 统一指向了 key
for (const item in user) {
console.log(item)
}
28. ES12 - FinalizationRegistry 执行清理回调
FinalizationRegistry 是 ES12 引入的一个高级特性,用于在对象被垃圾回收(GC)后执行清理回调。
FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调,它提供了这样的一种方法:
- 当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调(finalizer)
- 你可以通过调用 register 方法,注册任何你想要清理回调的对象,传入该对象和所含的值
javascript
// 创建一个目标对象
let user = { name: 'liang' }
// ES12:FinalizationRegistry 类,参数是一个回调函数,对象被垃圾回收时执行
const finalRegistry = new FinalizationRegistry(() => {
// 刷新页面并不会立即执行
// 因为 GC 垃圾回收机制是不定时来检测一下有没有垃圾,多等一会就行
console.log('注册到 finalRegistry 的某个对象被销毁');
})
// 将对象注册到 finalRegistry 上
finalRegistry.register(user)
// 当 user 不再被任何强引用持有时,未来某刻回调可能被调用(我测试是 30秒左右)
user = null // 移除强引用,使其可被 GC
可以注册多个对象到注册表,并且可以传入第二个参数(用于在回调中传递额外信息)
javascript
// 目标对象
let user = { name: 'liang' }
let info = { name: 'james' }
// 通过打印时间,可发现目标对象被移除强引用之后的30秒左右执行的清理回调
console.log(new Date().getTime() / 1000)
const finalRegistry = new FinalizationRegistry(value => {
console.log(new Date().getTime() / 1000)
console.log('注册到 finalRegistry 的某个对象被销毁 ' + value)
})
// 注册多个对象到注册表,并且可以传入第二个参数
finalRegistry.register(user, 'aaa')
finalRegistry.register(info, 'bbb')
// 移除强引用,使其可被 GC
user = null
info = null
29. ES12 - WeakRef 创建对象的弱引用
WeakRef 是 ES12 引入的一个特性,用于创建对对象的弱引用,它就是提供弱引用能力的内置类
它允许你引用一个对象,但不会阻止该对象被垃圾回收(GC)
- 强引用(Strong Reference):常规的对象引用。只要存在强引用,对象就不会被垃圾回收
- 弱引用(Weak Reference):不会阻止对象被回收。当对象仅被弱引用持有时,它可能在任意时间被 GC 回收
javascript
// 因为 user 的引用复制给了 info,所以当 user 设置为 null,也不会触发清理回调(引用还存在)
let user = { name: 'liang' }
let info = user // 强引用
console.log(new Date().getTime() / 100)
const finalRegistry = new FinalizationRegistry(value => {
console.log('注册到 finalRegistry 的某个对象被销毁 ' + value)
})
finalRegistry.register(user, 'aaa')
user = null
那么,我们如何将 info 变为 user 的弱引用呢 ?
javascript
let user = { name: 'liang' }
// 1. 强引用
// let info = user
// 2. 可以改为使用 WeakSet,使 info 变为弱引用,这种方式能实现
// let info = new WeakSet()
// info.add(user)
// 3. ES12 推出 WeakRef,让我们更方便的创建弱引用
let info = new WeakRef(user)
console.log(info.deref()) // 获取到对象数据
const finalRegistry = new FinalizationRegistry(value => {
console.log('注册到 finalRegistry 的某个对象被销毁 ' + value)
})
finalRegistry.register(user, 'aaa')
user = null
30. ES12 - 逻辑赋值运算符、String.replaceAll()
JavaScript 中的三种逻辑赋值运算符是 ES12 引入的语法糖,用于在满足特定条件时对变量或属性进行条件性赋值
javascript
// 逻辑或赋值运算 ||=
let message = ''
message ||= 'default' // 相当于 message = message || 'default'
// 逻辑与赋值运算 &&=
let a = 1
let b = 2
a &&= b // 相当于 a = a && b(当 a 有值,取 b 的值返回)
// 空值合并赋值 ??=
let c = ''
let d = 2
c ??= d // c = c ?? d
String.prototype.replaceAll() 是 ES12 引入的一个字符串方法,用于将字符串中所有匹配的子串替换为新值
- 它解决了长期以来
String.prototype.replace()默认只替换第一个匹配项的问题
为什么需要 replaceAll() ? 在它出现之前,要全局替换字符串,通常需要:
javascript
// 方法1:使用正则表达式 + 全局标志 g
str.replace(/old/g, 'new')
// 方法2:手动处理(不推荐)
str.split('old').join('new')
replaceAll() 提供了更简单、直观、安全的全局替换方式,语法格式:
- searchValue:可以是字符串或者正则表达式
- replaceValue:新的字符串
javascript
str.replaceAll(searchValue, replaceValue)
使用示例:
javascript
const string = 'good idea good'
// 第一个参数:使用字符串
console.log(string.replaceAll('good', 'aaa'))
// 第一个参数:正则表达式。必须带有全局标志 g , 否则会抛出错误
console.log(string.replaceAll(/good/g, 'bbb'))
31. ES13 - Array/String.at()、Object.hasOwn()
Array.prototype.at() 和 String.prototype.at() 是 ES13 引入的新方法,支持负数索引取值
- 它解决了长期以来 JavaScript 无法直接用负索引取值的问题
javascript
// index:整数,可以是正数(从 0 开始),也可以是负数(从 -1 开始,表示最后一个)
// 如果超出索引范围,返回 undefined,不会报错
array.at(index)
string.at(index)
在 ES13 之前,要访问数组最后一个元素,通常这样写:
javascript
const arr = ['html', 'css', 'js']
arr[arr.length - 1] // js
从 ES13 开始,可以使用 at() 直接获取指定负索引位置的元素,同样它也支持正索引
javascript
const arr = ['html', 'css', 'js', 'vue']
console.log(arr.at(0)) // vue(第一个)
console.log(arr.at(2)) // js(索引为2)
console.log(arr.at(-1)) // vue(最后一个)
ES13 中 Object 新增了一个静态方法(类方法):Object.hasOwn(obj, key)
javascript
const user = {
name: 'liang',
// 2. 在原型上添加属性也可以写在这里
__proto__: { height: 180 }
}
// 1. 在原型上添加属性
Object.prototype.age = 20
console.log(user.name) // liang
console.log(user.age) // 20
console.log(user.height) // 180
// ES13 之前
// 判断属性是否在对象自身上,在自身上返回 true(在原型上会返回 false)
console.log(user.hasOwnProperty('name')) // true
console.log(user.hasOwnProperty('age')) // false
// ES13 推出 Object.hasOwn(obj, key) 用于替代 Object.prototype.hasOwnProperty(key)
console.log(Object.hasOwn(user, 'name')) // true
console.log(Object.hasOwn(user, 'age')) // false
那么 Object.hasOwn(obj, key) 和 Object.prototype.hasOwnProperty(key) 有什么区别呢 ?
- 因为 hasOwnProperty 是一个实例方法,那么这个方法可能会 被"污染"或覆盖
- hasOwn 对无原型对象的数据也支持,而 hasOwnProperty() 不行
javascript
// 区别1: 因为 hasOwnProperty 是一个实例方法,那么这个方法可能会 被"污染"或覆盖
const user = {
name: 'liang',
// 重写该方法,那么原型上的 hasOwnProperty 就被覆盖了
hasOwnProperty() {
return 'aaa'
}
}
console.log(user.hasOwnProperty('name')) // aaa(显然是不对的)
// 区别2: hasOwn 对无原型对象也支持,而 hasOwnProperty() 不行
// Object.create() 用于创建一个新对象,并指定其原型
const info = Object.create(null)
info.name = 'liang'
// 此时不能使用 hasOwnProperty()
// Uncaught TypeError: info.hasOwnProperty is not a function
// console.log(info.hasOwnProperty('name'))
console.log(Object.hasOwn(info, 'name')) // true
32. ES13 - 类中新增的成员(私有化成员)
从 ES13 开始,正式支持在类中定义真正的私有化属性(private)
javascript
class Person {
// 2. 对象属性:public 公共的
// 在类的内部和外部都可以访问(并且所有类的实例都能访问到这个值)
height = 180
//(ES13 之前)对象属性:private 私有的,只是开发者之间的约定,在外面其实还是能访问的
_intro = '个人介绍'
//(ES13 开始)对象属性:private 私有的
#info = 'real private info'
constructor(name, age) {
// 1. 对象中的属性:在 constructor 通过 this 设置
this.name = name
this.age = age
}
running() {
console.log(this.#info) // 类内部可读取
this.#showName() // 类内部可调用
}
// 私有方法
#showName() {
console.log('name: ' + this.name);
}
}
const p = new Person('liang', 20)
console.log(p._intro) // 可以访问(不是真的私有属性)
p.running()
const p2 = new Person('james', 25)
console.log(p.height) // 180
console.log(p2.height) // 180
类的静态属性、静态方法使用示例:
javascript
class Person {
// 静态的公共属性(默认情况是 public)
static name = 'liang'
// 静态的私有属性 private(使用 # 表示)
static #age = 20
// 静态方法
static showName() {
console.log(this.#age);
}
// 静态代码块(在类初始化时立即执行的代码块)
static {
console.log('hello world');
}
}
console.log(Person.name)
Person.showName()