目录
- ES6
- let/const
- 不同特性
- 共同特性
- 不能重复声明
- 作用域提升问题
- 暂时性死区( `temporal dead zone`)
- 不添加window
- 形成块级作用域
- 三者区别
- 模板字符串
- 解构赋值
- 数组的解构
- 对象的解构
- 默认参数
- 箭头函数
- 展开运算符
- 数值表示
- Symbol
- Set
- WeakSet
- Map
- WeakMap
- Proxy 和 Reflect
- Promise
- 迭代器(`Iterator`)
- 生成器(`Generator`)
- Module模块化
- ES7
- Array Includes
- 指数运算符
- ES8
- Object.values
- Object.entries
- Object.getOwnPropertyDescriptors
- String Padding
- Trailing Commas
- async/await
- async
- await
- ES9
- `...` 的使用(`Object spread operators`)
- Promise finally
- ES10
- flat/flatMap
- Object.fromEntries(iterable)
- trimStart/trimEnd
- ES11
- BigInt
- 空值合并操作符 `??`(`Nullish Coalescing Operator`)
- 可选链(`Optional Chaining`)
- GlobalThis
- for..in
- 动态导入(Dynamic Import)
- Promise.allSettled
- import meta
- ES12
- FinalizationRegistry
- WeakRefs
- 逻辑赋值运算符`&&=`(`logical assignment operators`)
- 数字分隔符(Numeric Separator)
- String.replaceAll
- ES13
- method.at()
- Object.hasOwn(obj, propKey)
- New members of classes
ES6
let/const
在ES5
中我们声明变量都是使用的var
关键字,但var
所表现出来的特殊性:比如作用域提升、window
全局对象、没有块级作用域等都是一些历史遗留问题,是JavaScript
在设计之初的一种语言缺陷
因此从ES6
开始新增了两个关键字可以声明变量:let、const
不同特性
let
:- 用于声明变量,声明的值可以被修改
const
:- 用于声明常量,声明的值不可以被更改,在声明时必须赋值
- 但是如果赋值的是引用类型,不可以修改这个常量,但是可以修改引用类型里面的值,比如改数组的item和对象的属性
let message;
let message1 = "hello";
message1 = "你好";
console.log(message, message1); // undefined '你好'
// const obj // 报错:Uncaught SyntaxError: Missing initializer in const declaration
const arr = [123, 456, 789];
arr[0] = 321;
console.log(arr); // [321, 456, 789]
const info = {
name: "哈哈",
age: 18,
};
info.name = "我你他";
console.log(info); // {name: '我你他', age: 18}
const message2 = "world";
message2 = "世界"; // 报错:Uncaught TypeError: Assignment to constant variable
console.log(message2);
共同特性
不能重复声明
// 不能重复声明
let message = "hello";
// let message = "nihao"; // 报错:Uncaught SyntaxError: Identifier 'message' has already been declared
const height = 188;
// const height = 190; // 报错:Uncaught SyntaxError: Identifier 'height' has already been declared
作用域提升问题
-
我们知道
var
声明的变量是会进行作用域提升的,在声明之前访问不会报错值为undefined
-
但是
let/const
声明的变量/常量在声明之前访问会报错 -
那
let/const
的变量/常量是不是在代码执行的时候才会被创建?- 这些变量会被创建在包含它们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值
- 这些变量会被创建在包含它们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值
-
那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?
- 维基百科也没有相关解释,那么我们从字面上理解
- 作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,我们称为作用域提升
let/const没有作用域提升,但会在解析阶段被创建出来
console.log(age); // undefined
console.log(message, height); // 报错:Uncaught ReferenceError: Cannot access 'message' before initialization
var age = 18;
let message = "hello";
const height = 188;
暂时性死区( temporal dead zone
)
当程序的控制流程在新的作用域(module
function
或 block
作用域)进行实例化时,在此作用域中用let/const
声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
// let,const定义的变量在声明之前不能被访问
function bar() {
//在暂时性死区
// console.log(message, age);
let message = "hello";
let age = 90;
}
bar();
// 暂时性死区和定义的位置没有关系,和执行顺序有关
function foo() {
console.log(num); //90
}
let num = 90;
foo();
// 暂时性死区形成之后,在该区域内这个标识符不能被访问
// let count = 10;
var count = 10;
function fn() {
console.log(count); // 报错:// Uncaught ReferenceError: Cannot access 'count' before initializatio at fn
let count = 99;
}
fn();
不添加window
var count1 = 10;
let count2 = 20;
let count3 = 30;
console.log(window.count1); // 10
console.log(window.count2, window.count3); // undefined undefined
形成块级作用域
块级作用域的形成条件主要是基于代码块(如 {}
)的存在。在任何包围变量声明的代码块(包括控制结构、循环、函数等)中,使用 let
和 const
声明的变量都会有自己的块级作用域。这意味着这些变量只在当前块级作用域中有效,外部无法访问,有块级作用域就会形成新的块级词法环境
-
在
if
、while
、switch
等控制流结构中使用let/const
声明,{}
代表一个新的块级作用域 -
for
循环、for...of
和for...in
循环头部的变量使用let/const
声明时,每次循环迭代都会创建一个新的块级作用域,这个新的块级作用域会将上一次迭代的变量值复制过来(如果是第一次迭代,则使用初始值),这是ES6
引入的let/const
声明的一种行为 -
使用
try/catch
语句使用let/const
声明也会产生块级作用域
// 经典例子
const btns = document.querySelector(".btns");
// for (var i = 0; i < btns.children.length; i++) {
// btns.children[i].onclick = function () {
// 不管点那个按钮都会显示第四个按钮,因为var提示i在全局,当点击时for已执行完毕取到全局的i
// console.log(`点击了第${i}个按钮`);
// };
// }
for (let i = 0; i < btns.children.length; i++) {
btns.children[i].onclick = function () {
console.log(`点击了第${i}个按钮`);
};
}
在每次循环迭代时,JavaScript 引擎会做以下操作:
-
创建新块级作用域: 对于每次循环迭代,都会创建一个新的块级作用域
-
复制变量值: 新的块级作用域中的变量
i
会被初始化为前一次迭代时该变量的值,即使变量名相同都叫i
,它们在不同的块级作用域中指向不同的内存空间 -
在新的作用域中执行代码: 这意味着如果你在循环中使用闭包(如
setTimeout
),每次闭包中引用的变量都是该次迭代的值,而不会受到后续迭代的影响
图示如下:
三者区别
区别 | var | let | const |
---|---|---|---|
保存 | 变量环境 | 词法环境 | 词法环境 |
声明 | 变量 | 变量 | 常量 |
不赋值 | 可以 | 可以 | 不可以 |
值修改 | 可以 | 可以 | 不可以 |
重复声明 | 可以 | 不可以 | 不可以 |
作用域提升 | 有 | 无 | 无 |
暂时性死区 | 无 | 有 | 有 |
添加window | 添加 | 不加 | 不加 |
块级作用域 | 无 | 有 | 有 |
选择使用 | 最后 | 其次 | 首先 |
模板字符串
-
拼接字符串:在模板字符串中,我们可以通过
${expression}
来嵌入动态的内容const name = "哈哈"; const age = 18; const height = 188; function foo() { return "foo function"; } console.log(`我叫${name},今年${age},高${height}`); // 我叫哈哈,今年18,高188 console.log(`我是成年人吗?回答:${age >= 18 ? "是" : "不是"}`); // 我是成年人吗?回答:是 console.log(`这个函数是${foo()}`); // 这个函数是foo function
-
调用函数:可通过标签字符串如
test``
调用,在react
中css-in-js
时使用了
raw
属性是字符串模板字面量解析过程中生成的一个只读属性,包含模板字面量的原始字符串形const obj = { name: "小小", age: 18, height: 188, }; function foo(...args) { console.log(args); } foo`hello world`; foo`hello${obj.name},年龄${obj.age},height${obj.height}${obj}`;
解构赋值
数组的解构
- 按顺序赋值
- 只想解构后面元素时
- 可以有默认值
- 解构一个元素,后面的放一个数组中
const names = ["qwe", "rty", "uio"];
const [name1, name2, name3, name4 = "abc"] = names;
const [, , item3] = names;
const [item1, ...args] = names;
console.log(name1, name2, name3, name4); // qwe rty uio abc
console.log(item3); // uio
console.log(item1, args); // qwe ['rty', 'uio']
对象的解构
- 按
Key
赋值,没有顺序 - 可以重命名
- 可以有默认值
- 解构出一个,剩余内容放对象
const bar = {
type: "student",
name: "哈哈",
age: 18,
address: "北京",
school: "清华",
score: undefined,
};
const { name } = bar;
const { age, address } = bar;
const { school: reschool } = bar;
const { size = 6 } = bar;
const { score: rescore = 800 } = bar;
const { type, ...other } = bar;
console.log(type, other); // student {name: '哈哈', age: 18, address: '北京', school: '清华', score: undefined}
console.log(name, age, address, rescore, size); // 哈哈 18 北京 800 6
默认参数
-
传参为
undefined
时会使用默认值,参数的默认值我们通常会将其放到最后 -
默认值会改变函数的
length
的个数,默认值以及后面的参数都不计算在length
之内了 -
默认值也可以和解构一起来使用
function foo1(x, y = 20) {
console.log(x, y);
console.log(arguments.length);
}
// 和解构一起用
function foo2({ name, age } = { name: "小小", age: 18 }) {
console.log(name, age);
}
function foo3({ name = "小小", age = 20 } = {}) {
console.log(name, age);
}
foo1(10); // 10 20 这时参数arguments.length = 1
foo1(20, 40); // 20 40
foo1(30, 0); // 30 0
foo1(40, null); // 40 null
foo1(50, undefined); // 50 20
foo2(); // 小小 18
foo3(); // 小小 20
箭头函数
具体学习这篇文章:https://juejin.cn/post/7395868215623516172#heading-17
展开运算符
- 在函数调用时使用
- 在数组构造时使用
- 在构建对象字面量时,也可以使用展开运算符,这个是在
ES2018(ES9)
中添加的新特性
注意:展开运算符其实是一种浅拷贝
const obj = { name: "obj", age: 18, info: { name: "info" } };
const obj1 = { ...obj }; // { name: "obj", age: 18, info: { name: "info" } }
console.log(obj1);
const arr = ["123", "456", "678"];
const arr1 = [...arr];
console.log(arr1); // ['123', '456', '678']
const args = [12, 20];
function foo1(num1, num2) {
return num1 + num2;
}
console.log(foo1(...args)); // 32
数值表示
- 在
ES6
中规范了二进制和八进制的写法 - 在
ES2021
新增特性:数字过长时,可以使用_
作为连接符
const num = 10_0000;
const num1 = 100;
const num2 = 0b100; // binary 二进制
const num3 = 0o100; // octonary 八进制
const num4 = 0x100; // hexadecimal 十六进制
Symbol
Symbol
是ES6
中新增的一个基本数据类型,翻译为符号
为什么需要Symbol
呢?
-
ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
-
比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突从而覆盖掉它内部的某个属性
Symbol
就是为了解决上面的问题,用来生成一个独一无二的值
-
Symbol
值是通过Symbol
函数来生成的,生成后可以作为属性名 -
在
ES6
中,对象的属性名可以使用字符串,也可以使用Symbol
值 -
可以在创建
Symbol
值的时候传入一个描述description
:这个是ES2019(ES10)
新增的特性const bar = {}; const s1 = Symbol(); const s2 = Symbol("abc"); bar[s1] = "s1"; bar[s2] = "s2"; console.log(bar, bar[s1]); // {Symbol(): 's1', Symbol(abc): 's2'} 's1'
-
Symbol
即使多次创建值,它们也是不同的:Symbol
函数执行后每次创建出来的值都是独一无二的,相同值要使用Symbol.for
方法,通过Symbol.keyFor
方法来获取对应的key
const s1 = Symbol("abc"); const s2 = Symbol("abc"); console.log(s1 === s2); // false console.log(s1.description); // abc // 需要使用Symbol.for创建相同的值 const s3 = Symbol.for("abc"); const s4 = Symbol.for("abc"); console.log(s3 === s4); // true console.log(Symbol.keyFor(s3)); // abc
-
Symbol
不能参与运算,因为它是独一无二的,不可隐式转换为字符串 -
Symbol
可以作为对象的私有属性来使用,不会被普通的属性遍历方法列举出来const bar = { name: "bar", }; const s = Symbol(); bar[s] = "s1"; console.log(bar[s]); // s1 console.log(Object.keys(bar)); // ['name'] console.log(Object.getOwnPropertySymbols(bar)); // [Symbol()]
Set
Set
是一种用于存储唯一值的数据结构,它的特点是:
-
不允许重复值:
Set
会自动去除重复的元素,因此其中的每个值都是唯一的 -
可存储任何类型的值:可以在
Set
中存储原始值或对象的引用 -
迭代顺序:
Set
中的值按照插入顺序排列,可以通过for...of
循环或其他迭代方法来遍历
Set
常见的属性:
size
:返回Set
中元素的个数
Set
常用的方法:
-
add(value)
:添加某个元素,返回Set
对象本身 -
delete(value)
:从set
中删除和这个值相等的元素,返回boolean
类型 -
has(value)
:判断set
中是否存在某个元素,返回boolean
类型 -
clear()
:清空set
中所有的元素,没有返回值 -
forEach(callback, [thisArg])
:通过forEach
遍历set
const set = new Set();
console.log(set); // Set(0) {size: 0}
const set1 = new Set([123, 321, 456, 789, 456]);
console.log(set1); // Set(4) {123, 321, 456, 789}
console.log(set1.size); // 4
set1.forEach((f) => {
console.log(f); // 循环打印 123 321 456 789
});
for (const s of set1) {
console.log(s); // 循环打印 123 321 456 789
}
set1.add(987);
console.log(set1); // Set(5) {123, 321, 456, 789, 987}
console.log(set1.has(123)); // true
set1.delete(321);
console.log(set1.delete(321), set1); // false Set(4) {123, 456, 789, 987}
set1.clear();
console.log(set1.clear(), set1); // undefined Set(0) {size: 0}
WeakSet
和Set
类似的另外一个数据结构称之为WeakSet
,也是内部元素不能重复的数据结构
-
区别一:
WeakSet
中只能存放对象,不能存放基本数据类型 -
区别二:
WeakSet
对元素的引用是弱引用,当对象只被弱引用持有时,如果没有其他的强引用存在,那么这个对象会被视为可回收,GC
会对该对象进行回收
WeakSet
常见的方法:
-
add(value)
:只能添加对象,返回WeakSet
对象本身 -
delete(value)
:从WeakSet
中删除和这个值相等的元素,返回boolean
类型 -
has(value)
:判断WeakSet
中是否存在某个元素,返回boolean
类型
const weakSet = new WeakSet();
const baz1 = { name: "baz1" };
const baz2 = { name: "baz2" };
console.log(weakSet.add(baz1));
console.log(weakSet.add(baz2)); // WeakSet {{...}, {...}}
console.log(weakSet.delete(baz1)); // true
console.log(weakSet.has(baz1)); // false
console.log(weakSet); // WeakSet {{…}}
WeakSet
的应用:
-
WeakSet
不能遍历,因为WeakSet
只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁 -
所以存储到
WeakSet
中的对象是没办法获取的 -
那么这个东西有什么用呢?事实上这个问题并不好回答,我们来使用一个
Stack Overflow
上的答案- 存储私有数据:可以用于存储对象的私有数据或标记,而不需要将这些数据或标记直接附加到对象上。例如,在实现某些 API 时,可以使用
WeakSet
来标记哪些对象具有某些私有特性或行为,而不用污染对象本身
const privateDataSet = new WeakSet(); class MyClass { constructor() { privateDataSet.add(this); } someMethod() { if (!privateDataSet.has(this)) { throw new Error("不能通过其他对象调用此方法"); } // 安全执行代码 } } let instance = new MyClass(); instance.someMethod(); // 正常执行 privateDataSet.delete(instance); // 解除私有数据标记 instance.someMethod(); // 抛出错误
- 存储私有数据:可以用于存储对象的私有数据或标记,而不需要将这些数据或标记直接附加到对象上。例如,在实现某些 API 时,可以使用
Map
Map
是一种用于存储键值对的数据结构,与普通的对象不同Map
可以使用任何类型的值作为键,不仅仅是字符串或符号。它的主要特点是:
-
键值对存储:
Map
存储的是键值对,每个键只能对应一个值,键可以是任何类型(包括对象、函数、原始类型等) -
有序性:
Map
中的元素按照插入顺序迭代,可以通过for...of
循环或其他迭代方法来遍历 -
大小可动态调整:
Map
提供了方法来增加、删除和查询键值对
Map
常见的属性:
size
:返回Map
中元素的个数
Map
常见的方法:
-
set(key, value)
:在Map
中添加key、value
,并且返回整个Map
对象 -
get(key)
:根据key
获取Map
中的value
-
has(key)
:判断是否包括某一个key
,返回Boolean
类型 -
delete(key)
:根据key
删除一个键值对,返回Boolean
类型 -
clear()
:清空所有的元素 -
forEach(callback, [thisArg])
:通过forEach
遍历Map
const map = new Map();
console.log(map); // Map(0) {size: 0}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
console.log(map.set(obj1, "obj1")); // Map(1) {{name: 'obj1} => 'obj1'}
console.log(map.set(obj2, "obj2"), map.size); // Map(1) {{name: 'obj2} => 'obj2'} 2
map.forEach((f) => {
console.log(f); // 循环打印 obj1 obj2
});
for (const m of map) {
console.log(m); // 循环打印 [{name: 'obj1}, 'obj1'] [{name: 'obj2}, 'obj2']
}
console.log(map.get(obj1)); // obj1
console.log(map.has(obj2)); // true
map.clear();
console.log(map); // Map(0) {size: 0}
WeakMap
和Map
类型的另外一个数据结构称之为WeakMap
,也是以键值对的形式存在的
那么和Map
有什么区别呢?
-
区别一:
WeakMap
的key
只能使用对象,不接受其他的类型作为key
-
区别二:
WeakMap
的key
对象的引用是弱引用,如果没有其他强引用引用这个对象,那么GC
可以回收该对象
WeakMap
常见的方法有四个:
-
set(key, value)
:在Map
中添加key、value
,并且返回整个Map
对象 -
get(key)
:根据key
获取Map
中的value
-
has(key)
:判断是否包括某一个key
,返回Boolean
类型 -
delete(key)
:根据key
删除一个键值对,返回Boolean
类型
const weakMap = new WeakMap();
const bar1 = { name: "bar1" };
const bar2 = { name: "bar2" };
console.log(weakMap.set(bar1, "bar111"));
console.log(weakMap.set(bar2, "bar222")); // WeakMap {{…} => 'bar222', {…} => 'bar111'}
console.log(weakMap.get(bar1)); // bar111
console.log(weakMap.delete(bar1)); // true
console.log(weakMap.has(bar1)); // false
console.log(weakMap.has(bar2)); // true
WeakMap
的应用:
-
WeakMap
也是不能遍历的,没有forEach
方法,也不支持通过for of
的方式进行遍历 -
管理
DOM
元素的状态:需要为DOM
元素存储一些状态信息,使用WeakMap
可以防止内存泄漏,因为当元素被删除时,相关信息会自动被清除const elementState = new WeakMap(); const button = document.querySelector('button'); elementState.set(button, { clicked: false }); button.addEventListener('click', () => { const state = elementState.get(button); state.clicked = !state.clicked; console.log('Button clicked:', state.clicked); }); // 当 button 元素被删除时,状态信息会被垃圾回收
Proxy 和 Reflect
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/142111056?spm=1001.2014.3001.5501
Promise
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141263067?spm=1001.2014.3001.5501
迭代器(Iterator
)
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141364174?spm=1001.2014.3001.5501
生成器(Generator
)
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141364174?spm=1001.2014.3001.5501
Module模块化
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/142479941?spm=1001.2014.3001.5501
ES7
Array Includes
在ES7
之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf
获取结果,并且判断是否为 -1
在ES7中,我们可以通过includes来判断一个字符串或数组中中是否包含一个指定的元素,根据情况,如果包含则返回 true,否则返回false
string.includes(searchString, position)
:-
对于字符串,
includes
是大小写敏感的 -
searchString
: 要搜索的子字符串 -
position
(可选): 从哪个索引位置开始搜索,默认为0
-
array.includes(valueToFind, fromIndex)
:-
对于数组,
includes
方法使用的是===
严格相等比较,因此在检查NaN
时也会返回true
-
valueToFind
: 要搜索的元素 -
fromIndex
(可选): 从哪个索引位置开始搜索,默认为0
-
const str = "hello world";
const names = ["qwe", "rty", "uio"];
console.log(str.includes("l")); // true
console.log(str.includes("r", 7)); // true
console.log(names.includes("rty")); // true
console.log(names.includes("rty", 2)); // false
console.log(names.indexOf("rty")); // 1
console.log(names.includes(NaN)); // false
console.log(names.indexOf(NaN)); // -1
指数运算符
在ES7之前,计算数字的乘方需要通过 Math.pow
方法来完成
在ES7中,增加了 **
运算符,可以对数字来计算乘方
console.log(Math.pow(2, 3)); // 8
console.log(2 ** 3); // 8
ES8
Object.values
返回一个包含对象自身可枚举属性值的数组
var person = {
name: "nihao",
age: 18,
};
console.log(Object.values(person)); // ['nihao', 18]
Object.entries
返回一个包含对象自身可枚举属性的键值对数组
var person = {
name: "nihao",
age: 18,
};
console.log(Object.entries(person)); // [['name', 'nihao'], ['age', 18]]
Object.getOwnPropertyDescriptors
获取其所有自身属性描述符的对象,如果没有属性,则可能为空对象
const obj = {
name: "小小",
age: 18,
};
console.log(Object.getOwnPropertyDescriptors(obj));
/* log: {
name: {value: '小小', writable: true, enumerable: true, configurable: true},
age: {value: 18, writable: true, enumerable: true, configurable: true}
}
*/
const obj1 = {};
console.log(Object.getOwnPropertyDescriptors(obj1)); // {}
String Padding
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8
中增加了 padStart
和 padEnd
方法,分别是对字符串的首尾进行填充的
string.padStart(targetLength, padString)/string.padEnd(targetLength, padString)
-
在字符串的开头填充指定的字符,直到达到目标长度。如果目标长度小于字符串的原始长度,则返回原字符串
-
targetLength
: 目标字符串的长度 -
padString
(可选): 用于填充的字符串。如果未提供,默认为空格字符" "
-
- 应用对手机号或身份证打码的场景
const str = "hello world";
console.log(str.padStart(14, "h")); // hhhhello world
console.log(str.padEnd(15, "d")); // hello worlddddd
const phone = "18868686611";
console.log(`${phone.slice(0, 3)}${phone.slice(-4).padStart(8, "*")}`); // 188****6611
Trailing Commas
在ES8
中,我们允许在函数定义和调用时多加一个逗号,开发中很少用
function foo(a, b,) {
return a + b;
}
console.log(foo(10, 20,));
async/await
async
async
关键字用于声明一个异步函数
async
是asynchronous
单词的缩写,异步、非同步sync
是synchronous
单词的缩写,同步、同时
async
异步函数可以有很多种写法:
async function getData() {}
const fn1 = async function () {};
const fn2 = async () => {};
class Person {
async running() {}
}
异步函数执行流程:
-
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行
-
异步函数有返回值时,和普通函数会有区别:
-
异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到
Promise.resolve
中 -
如果异步函数的返回值是
Promise
,状态由Promise
决定 -
如果异步函数的返回值是一个对象并且实现了
thenable
,那么由对象的then
方法来决定
async function getData() { return "hello"; // 相当于返回new Promise(resolve => { resolve('hello') }) // 也相当于返回Promise.resolve('hello') } console.log(getData()); // Promise {<fulfilled>: 'hello'} const fn1 = async function () { return new Promise((resolve, reject) => { reject("fail"); }); }; fn1().catch((err) => { console.log(err); // fail }); const fn2 = async () => { return { then() { return new Promise((resolve, reject) => { resolve("成功"); }); }, }; }; console.log(fn2()); // 返回新new的Promise由它决定状态
-
-
如果我们在
async
中抛出了异常,它不会像普通函数一样报错,而是会作为Promise
的reject
来传递async function getData() { throw "异常"; } console.log(getData()); // Promise {<rejected>: '异常'} getData().catch((err) => { console.log(err); // '异常' });
await
async
函数另外一个特殊之处是可以在它内部使用await
关键字,而普通函数中是不可以的
await
后面跟的可以是任意表达式,分类如下:
-
await
后面是一个普通的值,那么会直接返回这个值async function foo() { const result = await 42; console.log(result); // 输出: 42 } foo();
-
await
后面是一个thenable
的对象,会根据对象的then
方法调用来决定后续的值const thenableObj = { then: function (resolve, reject) { resolve("Resolved from thenable object"); }, }; async function foo() { const result = await thenableObj; console.log(result); // 输出: Resolved from thenable object } foo();
-
await
后面的表达式返回一个Promise
时:-
返回了一个
fulfilled
状态的Promise
,一旦Promise
已经fulfilled
,await
会获取到该Promise
的结果(即resolve
的值),并继续执行后面的代码async function test() { const result = await Promise.resolve(42); // 立即返回 console.log(result); // 42 } test();
-
返回了一个
reject
状态的Promise
,await
会将该Promise
的拒绝理由作为异常抛出,可以通过try...catch
语句来捕获这个异常async function foo1() { const result = await Promise.reject("Error occurred"); // try { // const result = await Promise.reject("Error occurred"); // } catch (err) { // console.log(err); // 输出: Error occurred // } } // foo1(); foo1().catch((err) => { // 两种方式捕获异常 console.log(err); // 输出: Error occurred });
-
await
关键字有什么特点呢?
-
等待异步操作的完成:如果
await
后的表达式返回一个Promise
时,await
会暂停函数的执行,直到其后面的Promise
为fulfilled
状态后继续执行异步函数。这使得异步代码看起来像同步代码,便于理解和编写 -
非阻塞:虽然
await
会暂停函数内部的执行,但它不会阻塞其他并行执行的代码 -
序列化执行:当多个异步操作需要按顺序执行时,使用
await
可以保证执行顺序
ES9
...
的使用(Object spread operators
)
-
对象浅拷贝:使用
...
可以轻松创建对象的浅拷贝const person = { name: "Alice", age: 25 }; const newPerson = { ...person }; console.log(newPerson); // 输出: { name: 'Alice', age: 25 }
-
对象合并:可以将多个对象合并为一个对象
const person = { name: "Alice", age: 25 }; const job = { role: "Developer", company: "Tech Co." }; const mergedObject = { ...person, ...job }; console.log(mergedObject); // 输出: { name: 'Alice', age: 25, role: 'Developer', company: 'Tech Co.' }
-
属性覆盖:可以覆盖对象中的属性
const person = { name: "Alice", age: 25 }; const updatedPerson = { ...person, age: 26 }; // 覆盖 age 属性 console.log(updatedPerson); // 输出: { name: 'Alice', age: 26 }
-
删除属性:通过解构赋值与扩展运算符结合,实现删除对象中的属性
const person = { name: "Alice", age: 25, role: "Developer" }; const { role, ...rest } = person; // 从 person 对象中移除 role 属性 console.log(rest); // 输出: { name: 'Alice', age: 25 }
Promise finally
表示无论Promise
对象无论变成fulfilled
还是rejected
状态,最终都会被执行,finally
方法不接收参数
const promise = new Promise((resolve, reject) => {
console.log("这个promiseLog会立即执行"); // 这个promiseLog会立即执行
// reject('失败')
resolve("成功");
});
promise
.then((res) => {
console.log(res) // 成功
})
.catch((err) => {
console.log(err); // 失败
})
.finally(() => {
console.log("finally"); // 成功失败都会执行打印
});
ES10
flat/flatMap
-
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回const names = ["aaa", ["bbb", ["ccc"]]]; console.log(names.flat(1)); // ['aaa', 'bbb', ["ccc"]] console.log(names.flat(2)); // ['aaa', 'bbb', 'ccc']
-
flatMap()
方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组flatMap
是先进行map
操作,再做深度为1
的flat
操作
const names = ["aaa", ["bbb", ["ccc"]]]; console.log( names.flatMap((f) => { console.log(f); return f; }) ); // ['aaa', 'bbb', ["ccc"]]
Object.fromEntries(iterable)
以前可以通过 Object.entries
将一个对象转换成 entries
,而Object.formEntries
可以将 entries
转换为一个对象
const obj = {
name: "obj",
age: 18,
height: 188,
};
console.log(Object.entries(obj)); // [['name', 'obj'], ['age', 18], ['height', 188]]
console.log(Object.fromEntries(Object.entries(obj))); // {name: 'obj', age: 18, height: 188}
// 应用
const paramsString =
"http://loaclhost:3000/loginOut?name=params&age=18&height=188";
const searchParams = new URLSearchParams(paramsString.split("?")[1]);
console.log(Object.fromEntries(searchParams)); // {name: 'params', age: '18', height: '188'}
trimStart/trimEnd
去除一个字符串首尾的空格,我们可以用 trim
方法,如果单独去除前面或者后面可以使用 trimStart
和 trimEnd
const str = " hello world ";
console.log(str.trim()); // hello world
console.log(str.trimStart()); // hello world
console.log(str.trimEnd()); // hello world
ES11
BigInt
在早期的JavaScript
中,不能正确的表示过大的数字(大于MAX_SAFE_INTEGER
的数值,表示的可能是不正确的),引入了新的数据类型BigInt
,在数值的后面加上 n
用于表示大的整数
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1000); // 9007199254741992
const bigInt = 9007199254740991n; // 不能混合使用BigInt和其他类型
console.log(bigInt + 1000n); // 9007199254741991n
空值合并操作符 ??
(Nullish Coalescing Operator
)
// ?? 只有前面值为 null 或者 undefined 时才会返回后面值
console.log(undefined ?? 2); // 2
console.log(null ?? 2); // 2
console.log(0 ?? 2); // 0
console.log("" ?? 2); // ""
console.log(true ?? 2); // true
console.log(false ?? 2); // false
// || 前面值会先转化为布尔值判断,为true时返回前面值 , false 返回后面值
console.log(undefined || 2); // 2
console.log(null || 2); // 2
console.log(0 || 2); // 2
console.log("" || 2); // 2
console.log(true || 2); // true
console.log(false || 2); // 2
可选链(Optional Chaining
)
可选链主要作用是让代码在进行null
和undefined
判断时更加清晰和简洁,可选链不能用于赋值,使用如下:
-
obj?.prop
:prop
对象上的属性 -
obj?.[expr]
:expr
对象的属性表达式 -
arr?.[index]
:index
数组的索引 -
func?.(args)
: 对函数使用,args
传入的参数
const info = {
name: "info",
friend: {
girl: {
name: "lucy",
},
},
};
if (info.friend && info.friend.girl) {
console.log(info.friend.girl.name); // lucy
}
console.log(info.friend?.girl?.name); // lucy
GlobalThis
在新的标准中的globalThis
,因为在浏览器中全局对象是window
,在node中全局对象是global
,所以在新的标准中就统一了一个globalThis
for…in
在ES11
之前,虽然很多浏览器支持for...in
来遍历对象类型,但是并没有被ECMA
标准化,在ES11
中,对其进行了标准化,for...in
是用于遍历对象的key
的
-
遍历对象: 得到对象的
key
const info = { name: "info", friend: { girl: { name: "lucy", }, }, }; for (const key in info) { console.log(key); // 1. name 2. friend }
-
遍历数组:
-
得到数组的索引为字符串型数字,不能直接进行几何运算
-
使用
for in
会遍历数组所有的可枚举属性,包括原型 -
如果不想遍历原型方法和属性的话,使用
hasOwnProperty()
方法可以判断某属性是不是该对象的实例属性
const namess = ["aaa", "bbb", "ccc"]; for (const index in namess) { console.log(index); // 0 1 2 }
-
-
for...of
: 适用遍历数/数组对象/字符串/map
/set
等拥有迭代器对象的集合,不能遍历对象const namess = ["aaa", "bbb", "ccc"]; for (const value of namess) { console.log(value); // aaa bbb ccc }
动态导入(Dynamic Import)
具体学习这篇文章:https://juejin.cn/post/7417474250595336230#heading-20
Promise.allSettled
具体学习这篇文章:https://juejin.cn/post/7403283321794232354#heading-19
import meta
import.meta
是⼀个给JavaScript
模块暴露特定上下⽂的元数据属性的对象,它包含了这个模块的信息,⽐如说这个模块的URL
-
url
:模块的完整URL
,包括查询参数和/或哈希(位于?
或之后#
)。在浏览器中,这是获取脚本的 URL(对于外部脚本)或包含文档的 URL(对于内联脚本)。在Node.js
中,这是文件路径(包括file://
协议) -
resolve
:使用当前模块的URL
作为基础,将模块说明符解析为URL
ES12
FinalizationRegistry
FinalizationRegistry
对象可以让你在对象被垃圾回收时请求一个回调
- 当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调(
finalizer
),可以通过register
方法 - 可以通过调用
register
方法,注册任何你想要清理回调的对象,传入该对象和所含的值
let obj = {
name: "obj",
};
const registry = new FinalizationRegistry((value) => {
console.log("对象被销毁了", value);
});
registry.register(obj, "obj");
obj = null;
WeakRefs
默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用,希望是一个弱引用的话可以使用WeakRef
let obj = { name: "obj" };
let info = new WeakRef(obj);
逻辑赋值运算符&&=
(logical assignment operators
)
let msg = "";
// msg = msg || "hello";
msg ||= "hello"; // 等价于上面代码
console.log(msg);
let obj = { name: "obj" };
// obj = obj && obj.name;
obj &&= obj.name;
console.log(obj); // 等价于上面代码
let bar = null;
// bar = bar ?? "默认值";
bar ??= "默认值";
console.log(bar); // 等价于上面代码
数字分隔符(Numeric Separator)
const num = 1_000_000_000;
console.log(num); // 1000000000
String.replaceAll
replaceAll()
方法返回一个新字符串,其中所有匹配的部分都被替换
const paragraph = "I think Ruth's dog is cuter than your dog!";
console.log(paragraph.replaceAll('dog', 'monkey'));
// "I think Ruth's monkey is cuter than your monkey!"
const regex = /Dog/gi;
console.log(paragraph.replaceAll(regex, 'ferret'));
// "I think Ruth's ferret is cuter than your ferret!"
ES13
method.at()
at()
方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数
const names = ["aaa", "bbb", "ccc"];
console.log(names.at(1)); // bbb
console.log(names.at(-1)); // ccc
const str = "hello";
console.log(str.at(1)); // e
console.log(str.at(-1)); // o
Object.hasOwn(obj, propKey)
如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn()
返回 true
,如果属性是继承的或者不存在,该方法返回 false
,在支持 hasOwn
的浏览器中,建议使用 hasOwn()
而非 hasOwnProperty()
let bar = Object.create(null)
bar.name = 'bar'
const object1 = {
prop: 'exists',
};
console.log(Object.hasOwn(bar, 'name')); // true
console.log(Object.hasOwn(object1, 'prop')); // true
console.log(Object.hasOwn(object1, 'toString')); // false
console.log(Object.hasOwn(object1, 'undeclaredPropertyValue')); // false
// hasOwnProperty()方法返回一个布尔值,表示对象自有属性(而不是继承来的属性)中是否具有指定的属性
const object2 = {};
object2.property1 = 42;
console.log(bar.hasOwnProperty('name')); // 报错
console.log(object2.hasOwnProperty('property1')); // true
console.log(object2.hasOwnProperty('toString')); // false
console.log(object2.hasOwnProperty('hasOwnProperty')); // false
New members of classes
新增了定义class
类中成员字段(field
)的其他方式:
-
实例公共字段
-
静态公共字段
-
实例私有字段
-
静态私有字段
-
静态块
class Person {
address = "中国";
static totalCount = "701";
// 只能类内部访问
#sex = "male";
static #maleCount = 101;
constructor(name, age) {
this.name = name;
this.age = age;
}
static {
console.log("static block execution");
}
printInfo() {
console.log(this.address, this.#sex, Person.#maleCount);
}
}