【笔记】ES5及以后的新特性

前言

ES全称是ECAMScript,它是由ECAM国际标准化组织制定的一项脚本语言的标准化规范
从ES5之后的更新,从2015年6月开始每1年更新1次,并增加1次版本号
如2015年6月的版本为ES2015,又被称为ES6

数组通过forEach()函数进行遍历

ES5.js
1
2
3
4
5
var arr = [];

var result = arr.forEach(function (currentValue, index, array, thisArg) {
console.log(currentValue, index, array, thisArg);
});

数组通过filter()函数进行筛选

ES5.js
1
2
3
4
5
const arr = [];

var result = arr.filter(function (currentValue, index, array, thisArg) {
return currentValue;
});

数组通过some()函数进行查找

  • 回调函数返回true时会立即终止循环
ES5.js
1
2
3
4
5
const arr = [];

var exist = arr.some(function (currentValue, index, array, thisArg) {
return true;
});

字符串去除首尾空白字符

ES5.js
1
2
3
const str = "  hello world  ";

const result = str.trim(); // "hello world"

对象的属性描述符

ES5.js
1
2
3
4
5
6
7
8
var obj = { key: "value" };

Object.defineProperty(obj, "key", {
configurable: false, // 不可被再次修改描述符
enumerable: false, // 不可被枚举
writable: false, // 不可被修改
value: "", // 定义属性的值
});
ES5.js
1
2
3
4
5
6
7
8
9
10
11
12
var obj = { _key: "value" };

Object.defineProperty(obj, "key", {
configurable: false, // 不可被再次修改描述符
enumerable: false, // 不可被枚举
get: function () { // 被读取时触发的回调函数
return obj._key;
},
set: function (value) { // 被修改时触发的回调函数
return obj._key = value;
},
});

具有块级作用于的变量

  • var定义的变量在全局作用域都有效,let和const定义的变量是块级变量,只有在当前的块级作用域有效

  • 存在作用域的变量:let定义的变量、const定义的变量、function定义的函数、class定义的类

ES5.js
1
2
3
4
var a = null;
{
console.log(a); // null
}
ES6.js
1
2
3
4
const b = null;
{
console.log(b); // undefined
}
  • var定义的变量可以再次被定义,let和const定义的变量不能再次定义
ES5.js
1
2
var a = null;
var a = "";
ES6.js
1
2
const a = null;
// const a = "";
  • var在全局作用域定义的变量会作为window对象的属性,let和const在全局作用域定义的变量不会作为window对象的属性
ES5.js
1
2
var a = null;
console.log(window.a); // null
ES6.js
1
2
const a = null;
console.log(window.a); // undefined

let关键字

  • let可以先声明变量再赋值
ES6.js
1
2
let a;
a = null;
  • let也可以直接定义变量
ES6.js
1
let a = null;

const关键字

  • const定义的变量是只读变量,不能被修改
  • const必须直接定义变量,不能先声明变量再赋值
ES6.js
1
const a = null;
  • const定义的变量不能再次赋值
ES6.js
1
2
const a = null;
// a = 0;

对象增强

对象属性的增强

  • 将一个变量值作为对象的一个属性值时,如果变量名与属性名相同,可以简写
ES5.js
1
2
3
4
5
var key = "value";

var obj = {
key: key
}
ES6.js
1
2
3
4
5
const key = "value";

const obj = {
key
}

对象方法的增强

  • 在对象中定义方法时,可以简写
ES5.js
1
2
3
var obj = {
fn: function () {}
}
ES6.js
1
2
3
const obj = {
fn() {}
}

计算属性名

  • 在对象中定义一个属性时,如果这个属性名是可能会变化的,可以将变量值作为属性名
ES6.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const key = "filed-name";

// 定义对象并定义属性
const obj = {
[key]: "value"
};

// 添加或修改属性
obj[key] = "value";

// 删除属性
delete obj[key];

// 属性修饰符
Object.defineProperty(obj, key, {})

Symbol

定义Symbol

  • 通过Symbol()可以得到一个绝对唯一的标识符
ES6.js
1
const key = Symbol();

Symbol的应用

ES6.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const symbol = Symbol();

// 定义对象并定义属性
const obj = {
[symbol]: "value"
};

// 添加或修改属性
obj[symbol] = "value";

// 删除属性
delete obj[symbol];

// 属性修饰符
Object.defineProperty(obj, symbol, {})
获取对象的属性
  • 获取除了Symbol定义的属性
ES5.js
1
var keys = Object.keys(obj);
  • 获取Symbol定义的属性
ES6.js
1
const symbols = Object.getOwnPropertySymbols(obj);

Symbol的描述

定义具有描述的Symbol

ES10.js
1
const symbol = Symbol("description");

获取Symbol的描述

ES10.js
1
console.log(symbol.description); // "description"

定义相同的Symbol

  • Symbol默认无论如何定义都是不相同的
ES10.js
1
2
3
const symbol1 = Symbol("description");
const symbol2 = Symbol("description");
console.log(symbol1 === symbol2); // false
  • 如果使用for()函数定义Symbol,如果描述相同,则Symbol相同
ES10.js
1
2
3
const symbol1 = Symbol.for("description");
const symbol2 = Symbol.for("description");
console.log(symbol1 === symbol2); // true

解构(Destructuring)

  • 一些编程语言,…,允许多个变量被并行的赋值,…,如果赋值的右手侧是一个单一变量(比如一个数组或结构),这个特征就叫做解包(unpacking)或解构(维基百科

数组的解构

  • 解构数组时,映射到等号左面的变量名有顺序要求
ES6.js
1
2
const arr = ["value", "value"];
const [ key1, key2 ] = arr;
  • 解构数组时,如果数值对应索引的值被抛弃,则对应索引位置的变量需留空
ES6.js
1
2
3
const arr = ["value1", "value2", "value3"];
const [ key1, , key3 ] = arr;
console.log(key3); // "value3"
  • 解构数组时,映射到等号左面的变量个数超出,则多出来的变量值为undefined
ES6.js
1
2
3
const arr = ["value", "value"];
const [ key1, key2, key3 ] = arr;
console.log(key3); // undefined
  • 解构数组时,映射到等号左边的变量期望是数组时,需要使用...扩展运算符,得到剩余参数
ES6.js
1
2
3
4
const key2 = []
const arr = ["value1", "value2", "value3"];
const [ key1, ...keyOther ] = arr;
console.log(keyOther); // ["value2", "value3"]
  • 解构数组时,可以为等号左边的变量定义缺省值
ES6.js
1
2
3
4
const arr = ["value1", undefined];
const [ key1, key2 = "default2", key3 = "default3" ] = arr;
console.log(key2); // "default2"
console.log(key3); // "default3"

对象的结构

  • 通过与对象相同的属性名的变量进行解构匹配
  • 解构对象时,映射到等号左面的变量名没有顺序要求
ES6.js
1
2
const obj = { key1: "value", key2: "value" };
const { key1, key2 } = obj;
  • 如果变量名与解构的属性名不相同,需要手动指定变量
    • 通过:定义实际映射的变量名
ES6.js
1
2
3
4
const obj = { key1: "value1", key2: "value2" };
const { key1: key3, key2 } = obj;
console.log(key3); // "value1"
console.log(key2); // "value2"
  • 解构对象时,映射到等号左边的变量期望是对象时,需要使用...扩展运算符,得到剩余参数
ES6.js
1
2
3
const obj = { key1: "value1", key2: "value2", key3: "value3" };
const { key1, ...keyOther } = obj;
console.log(keyOther); // {key2: "value2", key3: "value3"}
  • 解构对象时,可以为等号左边的变量定义缺省值
ES6.js
1
2
3
4
const obj = { key1: undefined, key2: undefined };
const { key1: key3 = "default1", key2 = "default2" } = obj;
console.log(key3); // "default1"
console.log(key2); // "default2"

展开语法(Spread Syntax)

可展开的变量

数组

  • 将一个数组转换成一组用逗号分隔的参数序列
ES6.js
1
2
const arr = [];
console.log(...arr);

字符串

  • 将一个字符串转换成一组用逗号分隔的字符序列
ES6.js
1
2
const str = "";
console.log(...str);

对象

  • 将一个对象转换成一组用逗号分隔的键值对序列
ES9.js
1
2
3
4
5
6
7
8
9
10
const obj1 = {
key1: "value",
};

const obj2 = {
...obj1,
key2: "value",
};

console.log(obj2); // { key1: "value", key2: "value" }

展开语法的使用场景

调用函数时

ES6.js
1
2
3
4
5
6
7
const arr = [ "value1", "value2" ];
function fn(arg1, arg2) {
console.log(arg1); // "value1"
console.log(arg2); // "value2"
}

fn(...arr);

定义数组时

ES6.js
1
2
3
const arr1 = [ "value1", "value2" ];

const arr2 = [ ...arr1, "value3" ];

创建对象时

  • 将一个对象在另一个对象中展开,这个对象的所有属性键值对会赋值给另一个对象
ES9.js
1
2
3
4
5
6
7
8
9
10
const obj1 = {
key1: "value",
};

const obj2 = {
...obj1,
key2: "value",
};

console.log(obj2); // { key1: "value", key2: "value" }

模板字符串

ES5.js
1
2
const a = "";
const b = "文本内容" + a;
ES6.js
1
2
const a = "";
const b = `文本内容${a}`;

通过模板字符串调用函数并传递参数

  • 将模板字符串中的字符串作为第一个参数传递给函数,如果字符串中插入了变量,会根据变量位置拆分字符串,以字符串数组的形式作为第一个参数,其他参数依次是插入的变量值
ES5.js
1
2
3
function fn(...args) {}

fn(["front", "middle", "back"], 1, 2);
ES6.js
1
2
3
4
function fn(...args) {}

// fn(["front", "middle", "back"], 1, 2);
fn`front{1}middle${2}back`;

变量默认值

通过空值合并操作符为变量定义默认值

  • 为变量赋值时,如果变量值为nullundefined,则实际使用默认值赋值
ES11.js
1
2
const a = null;
const b = a ?? "default";

为函数形参定义默认值

  • 为函数形参赋值时,如果实参值为undefined,则实际使用默认值赋值
ES6.js
1
2
3
function fn(a = "default") {}

fn();

形参具有默认值时的形参书写顺序(推荐)

  • 具有多个形参时,具有默认值的形参最好写在最后
ES6.js
1
2
3
function fn(a, b = "default") {}

fn("value")
  • 具有多个形参时,具有默认值的形参与剩余参数最好都写在最后,剩余参数最好写在具有默认值的形参之后
ES6.js
1
2
3
function fn(a, b = "default", ...args) {}

fn("value")

形参具有默认值时的参数个数统计

  • 函数的形参具有默认值时,统计参数个数时,不会被算在内
ES6.js
1
2
3
function fn(a = "default") {}

console.log(fn.length); // 0
  • 函数的形参具有默认值时,统计arguments的长度时,会根据实参决定arguments的长度
ES6.js
1
2
3
4
5
function fn(a = "default") {
console.log(arguments.length); // 0
}

fn();
ES6.js
1
2
3
4
5
function fn(a = "default") {
console.log(arguments.length); // 1
}

fn(null);

函数的默认值定义为对象

ES6.js
1
2
3
function fn(obj = { key: "value" }) {}

fn();
  • 使用结构赋值得到形参默认值对象的属性
ES6.js
1
2
3
function fn({ key } = { key: "value" }) {}

fn();
  • 结构对象时为属性定义默认值
ES6.js
1
2
3
function fn({ key = "value" } = {}) {}

fn();

箭头函数

  • 箭头函数不能作为构造函数使用
  • 箭头函数没有super
  • 箭头函数不绑定this指向,其this指向是上层this指向
  • 箭头函数没有arguments,无法通过arguments获取参数
ES6.js
1
const fn = (arg1, arg2) => {console.log()};
  • 只有一个形参,可以省略小括号
ES6.js
1
const fn = arg => {console.log()};
  • 代码块内只有一句代码,且为返回语句,可以省略大括号和return关键字
ES6.js
1
const fn = (arg1, arg2) => "value";
  • 代码块内只有一句代码,且为返回语句,且返回的是对象,虽然可以省略大括号和return关键字,但要使用()包裹返回的对象
ES6.js
1
const fn = (arg1, arg2) => ({ key: "value" });

不同进制数的字面量表示

十进制数

ES5.js
1
const num = 10;

二进制数

ES6.js
1
const num = 0b1010;

八进制数

ES5.js
1
const num = 012;
ES6.js
1
const num = 0o12;

十六进制数

ES5.js
1
const num = 0xA;

Set集合

  • Set集合中的值不会重复,如果添加重复数据,会自动去重

Set集合

  • Set集合可以存储任意类型数据

定义Set集合

ES6.js
1
const set = new Set();

获取元素总数

ES6.js
1
const result = set.size;

添加元素

  • 元素会自动去重
ES6.js
1
set.add("value");

删除元素

ES6.js
1
const success = set.delete("value");

删除所有元素

ES6.js
1
set.clear();

判断是否包含指定元素

ES6.js
1
const exist = set.has("value");

遍历Set集合

ES6.js
1
2
3
for (const item of set) {
console.log(item);
}
ES6.js
1
2
3
set.forEach(item => {
console.log(item);
});

Set集合与数组互转

数组转换为Set集合
ES6.js
1
2
3
const arr = [];

const set = new Set(arr);
Set集合转换为数组
ES6.js
1
const arr = Array.from(set);

WeakSet集合

  • WeakSet集合只能存储引用类型数据,不能存储基本类型数据
  • WeakSet集合存储的引用类型数据是弱引用,如果加入到集合的数据没有被其他引用,则会被GC清理
  • WeakSet集合不能被遍历,因为元素可能不存在,所以没有size属性和clear()方法

定义WeakSet集合

ES6.js
1
const weakSet = new WeakSet();

添加元素

  • 元素会自动去重
ES6.js
1
2
3
const key = "value";

weakSet.add(key);

删除元素

ES6.js
1
2
3
const key = "value";

weakSet.delete(key);

判断是否包含指定元素

ES6.js
1
2
3
const key = "value";

const exist = weakSet.has(key);

Map映射

Map映射

  • Map对象在toString()操作时会完整返回结构,而Object对象在toString()操作时只会输出[Object object]
  • Map不仅可以将任意类型数据作为值,还可以将任意类型数据作为键,而Object对象只能将字符串或Symbol作为键

定义Map映射

ES6.js
1
const map = new Map();

获取键值对总数

ES6.js
1
const result = map.size();

添加或修改键值对

ES6.js
1
map.set("key", "value");

根据指定键获取值

ES6.js
1
const value = map.get("key");

根据指定键删除键值对

ES6.js
1
const success = map.delete("key");

删除所有键值对

ES6.js
1
map.clear();

根据指定键判定键值对是否存在

ES6.js
1
const exist = map.has("key");

遍历键值对

ES6.js
1
2
3
4
5
6
for (const item of map) {
const [key, value] = item;
console.log(item);
console.log(key);
console.log(value);
}
ES6.js
1
2
3
map.forEach(value => {
console.log(value);
});

WakeMap映射

  • WeakMap映射的键只能存储引用类型数据,不能存储基本类型数据
  • WeakMap映射存储的键值对的键是弱引用,如果加入到映射的键没有被其他引用,则会被GC清理
  • WeakMap映射不能被遍历,因为键值对可能不存在,所以没有size属性和clear()方法

定义WeakMap映射

ES6.js
1
const weakMap = new WeakMap();

添加或修改键值对

ES6.js
1
weakMap.set("key", "value");

根据指定键获取值

ES6.js
1
const value = weakMap.get("key");

根据指定键删除键值对

ES6.js
1
const success = weakMap.delete("key");

根据指定键判定键值对是否存在

ES6.js
1
const exist = weakMap.has("key");

判断数组中是否存在指定元素

ES7.js
1
2
3
const arr = [];

const exist = arr.includes("value");

指定开始查找位置

0:缺省值,从头查找

ES7.js
1
2
3
const arr = [];

const exist = arr.includes("value", 0);

计算乘方

ES5.js
1
const result = Math.pow(2, 3);
ES7.js
1
const result = 2 ** 3;

获取对象的所有键和所有值

ES5.js
1
2
3
const obj = {};

const keys = Object.keys(obj);
ES8.js
1
2
3
const obj = {};

const values = Object.values(obj);

将对象转换为二维数组

ES8.js
1
2
3
const obj = { key1: "value", key2: "value" };

const arr = Object.entries(obj); // [["key1", "value"], ["key2", "value"]]

将字符串转换为二维数组

ES8.js
1
2
3
const str = "value";

const arr = Object.entries(str); // [["0", "v"], ["1", "a"], ["2", "l"], ["3", "u"], ["4", "e"]]

字符串根据长度补齐空白

从字符串首部补齐空白

2:字符串最短长度
"0":用于填充空白字符的字符串

ES8.js
1
2
3
const str = "1";

const result = str.padStart(2, "0");

从字符串尾部补齐空白

ES8.js
1
2
3
const str = "1";

const result = str.padEnd(2, "0");

允许参数列表末尾添加逗号

ES8.js
1
2
3
4
5
function fn(arg, ) {

}

fn("", )

获取对象的属性描述符

ES8.js
1
2
3
const obj = {}

const result = Object.getOwnPropertyDescriptors(obj);

对象上使用展开运算符

ES9.js
1
2
3
4
5
6
7
8
9
10
const obj1 = {
key1: "value",
};

const obj2 = {
...obj1,
key2: "value",
};

console.log(obj2); // { key1: "value", key2: "value" }

将多维数组数组扁平化

仅进行扁平化

1:指定层级

Infinity:无穷大,展开所有层级

ES10.js
1
2
3
const arr = [1, [2]];

const result = arr.flat(1); // [1, 2]

先处理数据再进行扁平化

ES10.js
1
2
3
4
5
6
const arr = [
"key1 value1 value2",
"key2 value",
];

const result = arr.map((item) => item.split(" ")).flat(1); // ["key1", "value1", "value2", "key2", "value"]
1
2
3
4
5
6
const arr = [
"key1 value1 value2",
"key2 value",
];

const result = arr.flatMap((item) => item.split(" ")); // ["key1", "value1", "value2", "key2", "value"]

将二维数组转换为对象

ES10.js
1
2
3
const arr = [["key1", "value"], ["key2", "value"]];

const obj = Object.fromEntries(arr);

字符串去除首尾空白字符

去除首尾空白字符

ES5.js
1
2
3
const str = "  hello world  ";

const result = str.trim(); // "hello world"

去除首部空白字符

ES10.js
1
2
3
const str = "  hello world  ";

const result = str.trimStart(); // "hello world "

去除尾部空白字符

ES10.js
1
2
3
const str = "  hello world  ";

const result = str.trimEnd(); // " hello world"

Symbol的描述

BigInt

  • 通过在整数末位添加n来表示大整数
ES11.js
1
const bigInt = 9007199254740993n;

空值合并操作符

可选链操作符

安全的访问属性

通过短路与

ES5.js
1
2
3
const obj = { key: "value" }

const value = obj && obj.key;

通过可选链操作符

ES11.js
1
2
3
const obj = { key: "value" }

const value = obj?.key;

安全的执行函数

通过短路与

ES5.js
1
2
3
const obj = { method: function () {} }

obj && obj.method();

通过可选链操作符

ES11.js
1
2
3
const obj = { method: function () {} }

obj?.method?.();

全局对象

ES5.js
1
console.log(window);
ES11.js
1
console.log(globalThis);

for…in标准化

  • for…in从ES3开始就被定义,但直到ES11才被标准化,定义for…in遍历对象时,只会遍历对象的键
ES11.js
1
2
3
4
5
const obj = { key: "value" }

for (const key in obj) {
console.log(key);
}

对象被GC回收后的回调函数

ES12.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义对象
let obj1 = {}
let obj2 = {}

// 定义回调函数
const finalizationRegistry = new FinalizationRegistry((value) => {
console.log(value, "callback success")
});

// 绑定需要监听的对象
finalizationRegistry.register(obj1);
finalizationRegistry.register(obj2, "obj2");

// 解除对象引用,等待GC回收
obj1 = null;
obj2 = null;

弱引用对象

ES12.js
1
2
3
4
5
6
7
8
9
10
11
// 定义对象
let obj = { key: "value", fn: function () {} }

// 定义弱引用对象
let weakRef = new WeakRef(obj);

// 从弱引用对象中获取属性,如果对象被GC回收则会获取失败
console.log(weakRef.deref().key); // value

// 从弱引用对象中调用方法,如果对象被GC回收则会调用失败
weakRef.deref().fn();

逻辑赋值运算符

  • 如果传递了零值,则使用默认值为变量赋值
ES12.js
1
2
3
function fn(key) {
key ||= "default";
}
  • 如果传递了undefined或null,则使用默认值为变量赋值
ES12.js
1
2
3
function fn(key) {
key ??= "default";
}
  • 如果对象中存在指定属性,就将指定属性的值作为对象的值
ES12.js
1
2
3
const obj = { obj: { key: "value" } };

obj &&= obj.obj;

长数字分隔符

  • 利用_分隔数字,可以提高代码的可读性,不限制_书写的位置

英式数字分隔法

ES12.js
1
const oneBillion = 1_000_000_000;

中式数字分隔法

ES12.js
1
const oneBillion = 10_0000_0000;

字符串替换子串

  • 替换一次
ES5.js
1
2
3
const str = "old";

str.replace("old", "new");
  • 替换全部
ES12.js
1
2
3
const str = "old";

str.replaceAll("old", "new");

元素访问方法

  • ES5通过[]只能正向访问(从0开始),不能逆向访问
ES5
1
2
3
const arr = [""]

arr[0];
  • ES13通过at()可以正向访问(从0开始),也可以逆向访问(从-1开始)
ES13.js
1
2
3
4
const arr = [""]

arr.at(0);
arr.at(-1);

判断属性是否在当前对象上而非原型对象上

ES3.js
1
2
3
const obj = { key: "value" };

const exist = obj.hasOwnProperty("key");
ES13.js
1
2
3
const obj = { key: "value" };

const exist = Object.hasOwn(obj, "key");

类中的新成员

  • 直接在类中定义实例属性,而不是在构造方法中定义实例属性
ES13.js
1
2
3
class Cls {
key = "value";
}

公共属性和私有属性

公共属性

  • 没有使用修饰符作为属性名前缀的属性为公共属性(public)
ES13.js
1
2
3
4
5
6
class Cls {
key = "value";
}

const cls = new Cls();
console.log(cls.key); // "value"

约定的私有属性

  • 约定的私有属性使用_作为属性名前缀

使用_作为属性名前缀的属性仍然可以被在外部被访问

ES13.js
1
2
3
4
5
6
class Cls {
_key = "value";
}

const cls = new Cls();
console.log(cls._key); // "value"

私有属性

  • 真正的私有属性(private)使用#作为属性名前缀

使用#作为属性名前缀的属性不可以在外部被访问,只能在类内部被访问

ES13.js
1
2
3
4
5
6
class Cls {
#key = "value";
}

const cls = new Cls();
// console.log(cls.#key);

实例属性和静态属性

实例属性

  • 没有使用修饰符修饰的属性为实例属性,实例属性只能通过对象访问
ES13.js
1
2
3
4
5
6
class Cls {
key = "value";
}

const cls = new Cls();
console.log(cls.key); // "value"

静态属性

  • 使用static作为修饰符修饰的属性为静态属性,静态属性只能通过类访问
ES13.js
1
2
3
4
5
class Cls {
static key = "value";
}

console.log(Cls.key); // "value"
静态私有属性
ES13.js
1
2
3
4
5
class Cls {
static #key = "value";
}

// console.log(Cls.#key);

静态代码块

  • 静态代码块会在加载解析类时立即执行(创建对象之前)且只会执行一次
ES13.js
1
2
3
4
5
class Cls {
static {
...
}
}

完成