总结ES6—ES13新特性

发布于:2024-10-17 ⋅ 阅读:(10) ⋅ 点赞:(0)

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 functionblock 作用域)进行实例化时,在此作用域中用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
形成块级作用域

块级作用域的形成条件主要是基于代码块(如 {})的存在。在任何包围变量声明的代码块(包括控制结构、循环、函数等)中,使用 letconst 声明的变量都会有自己的块级作用域。这意味着这些变量只在当前块级作用域中有效,外部无法访问,有块级作用域就会形成新的块级词法环境

  • ifwhileswitch 等控制流结构中使用let/const声明,{} 代表一个新的块级作用域

  • for 循环、for...offor...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 引擎会做以下操作:

  1. 创建新块级作用域: 对于每次循环迭代,都会创建一个新的块级作用域

  2. 复制变量值: 新的块级作用域中的变量i会被初始化为前一次迭代时该变量的值,即使变量名相同都叫 i,它们在不同的块级作用域中指向不同的内存空间

  3. 在新的作用域中执行代码: 这意味着如果你在循环中使用闭包(如 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`` 调用,在reactcss-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

SymbolES6中新增的一个基本数据类型,翻译为符号
为什么需要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(); // 抛出错误
    

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有什么区别呢?

  • 区别一:WeakMapkey只能使用对象,不接受其他的类型作为key

  • 区别二:WeakMapkey对象的引用是弱引用,如果没有其他强引用引用这个对象,那么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中增加了 padStartpadEnd 方法,分别是对字符串的首尾进行填充的

  • 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关键字用于声明一个异步函数

  • asyncasynchronous单词的缩写,异步、非同步
  • syncsynchronous单词的缩写,同步、同时

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中抛出了异常,它不会像普通函数一样报错,而是会作为Promisereject来传递

    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 已经 fulfilledawait 会获取到该 Promise 的结果(即 resolve 的值),并继续执行后面的代码

      async function test() {
        const result = await Promise.resolve(42); // 立即返回
        console.log(result); // 42
      }
      test();
      
    • 返回了一个reject状态的 Promiseawait 会将该 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 会暂停函数的执行,直到其后面的 Promisefulfilled状态后继续执行异步函数。这使得异步代码看起来像同步代码,便于理解和编写

  • 非阻塞:虽然 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操作,再做深度为1flat操作
    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 方法,如果单独去除前面或者后面可以使用 trimStarttrimEnd

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

可选链主要作用是让代码在进行nullundefined判断时更加清晰和简洁可选链不能用于赋值,使用如下:

  • obj?.propprop对象上的属性

  • 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);
  }
}