跳到主要内容

10. ES6+ 基础

1 ES6 基础

1.1 字面量增强

属性的简写、方法的简写、计算属性名

const name = "why";
const age = 18;

const obj = {
// 1.property shorthand(属性的简写)
name,
age,

// 2.method shorthand(方法的简写)
foo: function() {
console.log(this);
},
bar() {
console.log(this);
},
baz: () => {
console.log(this);
},

// 3.computed property name(计算属性名)
[name + 123]: 'hehe'
}

obj.baz(); // window
obj.bar(); // obj: {name: 'why', age: 18, foo: ƒ, bar: ƒ, …}
obj.foo(); // obj: {name: 'why', age: 18, foo: ƒ, bar: ƒ, …}

// obj[name + 123] = "hehe"
console.log(obj) // obj: {name:'why', age:18, why123:"hehe", …}

1.2 解构 Destructuring

数组的解构、对象的解构。

数组的解构:

const names = ["abc", "cba", "nba"]

// 对数组的解构: []
const [item1, item2, item3] = names;
console.log(item1, item2, item3);

// 解构后面的元素
const [, , itemz] = names;
console.log(itemz);

// 解构出一个元素,后面的元素放到一个新数组中
const [itemx, ...newNames] = names;
console.log(itemx, newNames);

// 解构的默认值
const [itema, itemb, itemc, itemd = "aaa"] = names;
console.log(itemd);

对象的解构:

var obj = {
name: "why",
age: 18,
height: 1.88
}

// 对象的解构: {}
const { name, age, height } = obj;
console.log(name, age, height);

// 只解构一个
const { age } = obj
console.log(age);

// 解构后重命名
const { name: newName } = obj;
console.log(newName);

// 重命名 + 赋默认值
const { address: newAddress = "广州市" } = obj;
console.log(newAddress);


// 在函数入参时解构
function bar({name, age}) {
console.log(name, age);
}

bar(obj);

1.3 标签模板字符串

标签模板字符串(Tagged Template Literals)

  • 第一个参数:数组
  • 之后的参数:传入的入参

如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:

  • 模板字符串被拆分了;
  • 第一个元素是数组,是被模块字符串拆分的字符串组合;
  • 后面的元素是一个个模块字符串传入的内容;
标签模版字符串:foo``

// 第一个参数依然是模块字符串中整个字符串, 只是被切成多块,放到了一个数组中
// 第二个参数是模块字符串中, 第一个 ${}
function foo(m, n, x) {
console.log(m, n, x, '----');
}

foo("Hello", "World", "!");
// Hello World ! ----

// ❗️标签模块字符串
foo``;
// [''] undefined undefined '----'

foo`Hello World !` ;
// ['Hello World !'] undefined undefined '----'


const name = "why";
const age = 18;
foo`Hello${name}Wo${age}rld`
// ['Hello', 'Wo', 'rld'] 'why' 18 '----'

使用: styled-components 库

export const BannerWrapper = styled.div`
background : url(${props => props.bgImages}) center center;

.banner {
height: 270px;
display: flex;
position: relative;
}
`
  • styled.div 中,div 其实是一个函数
  • 这里的 div 就使用了标签模板字符串,反引号内的 ${} 传递了变量,styled-components 库就通过这个断点进行切分,然后转译为 css 文件。

1.4 函数的默认参数

默认参数:

// es5 的参数默认值:
// 如果传入:0 或 "" 空字符串,会被认为 false,
// 需要完善边界问题,用 arguments 判断是否有足够的入参
function sum(m, n) {
m = m || 10;
n = n || 20;
return m + n;
}

// es6 的参数默认值:
function sum(m = 10, n = 20) {
return m + n;
}

// 有默认值的形参最好放到最后(影响length的统计)
function bar(x, y, z = 30) {
console.log(x, y, z)
}

默认参数 + 解构:解构 = 默认值

  • 等号右边是默认值

  • 等号左边是解构

function printInfo({name, age} = {name: "why", age: 18}) {
console.log(name, age);
}

printInfo(); // why 18
printInfo({name: "kobe", age: 40}); // kobe 40

// 另外一种写法
function printInfo1({name = "why", age = 18} = {}) {
console.log(name, age);
}

printInfo1(); // why 18
printInfo1({name: "kobe", age: 40}); // kobe 40

有默认值的参数,函数的 length 属性不做统计。

  • 有默认值的参数,其后面位置的其余参数都不做统计。

function baz(x, y, z, m, n = 30) {
console.log(x, y, z, m, n)
}

console.log(baz.length); // 4

// 默认值在第2个位置,length只有1。
function bar(x, y = 10, z, m, n) {
console.log(x, y, z, m, n)
}

console.log(bar.length); // 1

1.5 函数的剩余参数

ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:

  • 如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
  • rest paramaters 必须放到最后;
function foo(m, n, ...args) {
console.log(m, n); // 20 30
console.log(args); // [40, 50, 60]
console.log(arguments);
// Arguments: [20, 30, 40, 50, 60, callee: (...), Symbol(Symbol.iterator): ƒ]
}

foo(20, 30, 40, 50, 60);

剩余参数 和 arguments 有什么区别呢?

  • 包含范围。rest 参数 只包含那些没有对应形参的实参, arguments 对象包含了传给函数的所有实参;
  • 数组/类数组。rest 参数是真正的数组,可以进行数组的所有操作, arguments 对象是类数组(不是数组);
  • ES6 版本。rest 参数是 ES6中 提供,且替代 arguments 的数组, arguments 是 js 早期版本中的数据结构,为了方便去获取所有的参数。

1.6 展开语法

展开语法(Spread syntax):

  • 可以在 调用函数 / 创建数组 时,将 数组表达式string 在语法层面展开;

  • 还可以在 创建对象 时, 将 对象表达式 按 key-value 的方式展开;

展开语法的场景:

  • 在调用函数时使用;
  • 在创建数组时使用;
  • 在创建对象时使用,是 ES2018(ES9)中添加的新特性;

注意:展开运算符其实是一种 浅拷贝

const names = ["abc", "cba", "nba"];
const name = "ninjee";
const info = {name: "ninjee", age: 18};

function foo(x, y, z) {
console.log(x, y, z);
}
// 1.函数调用时
// abc cba nba
foo.apply(null, names); // (es5)利用apply的第2个参数自动展开
foo(...names); // abc cba nba:展开names数组并传入
foo(...name); // n i n:展开name字符串并传入


// 2.构造数组时
const newNames = [...names, ...name];
console.log(newNames);
// ['abc', 'cba', 'nba', 'n', 'i', 'n', 'j', 'e', 'e'];


// 3.构建对象字面量时 ES2018(ES9)
const obj = { ...info, address: "广州市", ...names };
console.log(obj);
// {0: 'abc', 1: 'cba', 2: 'nba', name: 'ninjee', age: 18, address: '广州市'}

1.7 数值的表示

在ES6中规范了二进制和八进制的写法:

const num1 = 100 // 十进制

// b -> binary
const num2 = 0b100; // 二进制
// o -> octal
const num3 = 0o100; // 八进制
// x -> hexadecimal
const num4 = 0x100; // 十六进制

console.log(num1, num2, num3, num4); // 100 4 64 256

ES2021新特性:数字过长时,可以使用 _ 作为连接符,观看更清晰。

// 大的数值的连接符(ES2021 ES12)
const num = 10_000_000_000_000_000;
console.log(num); // 10000000000000000

2 ES7 基础

2.1 Array.includes

  • 用处:判断数组中是否存在某个成员。
  • 参数:
    1. 需要查找的成员值;
    2. 从第 x 个索引处(含)开始查找。如果为负数,从 array.length + 负数 开始找。
      • 比如 -3 就是从倒数第三个开始(含),从倒数第 1 个开始数;
      • 比如 2 就是从第二个开始(含)从第 0 个开始数。
  • 返回:boolean

es5 通过 IndexOf 寻找下标,如果返回 -1 表明不存在,否则就是存在。

// es5
const names = ["abc", "cba", "nba", "mba", NaN];

if (names.indexOf("cba") !== -1) {
console.log("包含abc元素");
}

// ES7 ES2016
if (names.includes("cba", 2)) {
console.log("包含abc元素");
}

if (names.indexOf(NaN) !== -1) {
console.log("包含NaN");
}

if (names.includes(NaN)) {
console.log("包含NaN");
}

2.2 指数(乘方)

exponentiation运算符

  • 在ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成。
  • 在ES7中,增加了 ** 运算符,可以对数字来计算乘方。
// es5
const result1 = Math.pow(3, 3)

// ES7: **
const result2 = 3 ** 3
console.log(result1, result2); // 9 9

3 ES8 基础

3.1 Object API

  • ES5: Object.keys 获取一个对象所有的 key;
  • ES8:Object.values 来获取所有的 value 值;
  • ES8:Object.entries 获取 [K, V] 对;
const obj = {
name: "why",
age: 18
}

Object.keys(obj); // [ 'name', 'age' ]
Object.values(obj); // [ 'why', 18 ]

// 用的非常少
Object.values(["abc", "cba", "nba"]); // [ 'abc', 'cba', 'nba' ]
Object.values("abc"); // 展开:[ 'a', 'b', 'c' ]


Object.entries(obj); // [['name', 'why'], ['age', 18]]
const objEntries = Object.entries(obj);

// 可迭代
objEntries.forEach(item => {
console.log(item[0], item[1]);
// name why
// age 18
})

Object.entries(["abc", "cba", "nba"]); // [['0', 'abc'], ['1', 'cba'], ['2', 'nba']]
Object.entries("abc"); // [['0', 'a'], ['1', 'b'], ['2', 'c']]

3.2 String Padding

ES8中增加了 padStartpadEnd 方法,可对字符串首位进行填充,来实现某种格式化效果。

  • 参数1,填充的个数
  • 参数2,填充的字符
  • 返回:填充好的新字符串
const message = "Hello World";

const newMessage = message.padStart(15, "*").padEnd(20, "-");
console.log(newMessage); // ****Hello World-----

比如,对传入的身份证数字进行加密:

// 案例
const cardNumber = "321324234242342342341312";
const lastFourCard = cardNumber.slice(-4);
const finalCard = lastFourCard.padStart(cardNumber.length, "*");
console.log(finalCard); // ********************1312

4 ES10 基础

4.1 flat, flatMap

实现了数组展开,也就是数组的 扁平化(非原地展开)。

flat()

  • 将一个数组按照入参的深度展开
  • 参数:number,表明展开的深度,默认展开 1 层。
  • 返回:展开后的新数组。

flatMap()

  • 相当于:先 map,后 flat 展开,只展开 1 层。
  • 参数:回调函数,相当于是一个 map 回调函数。
  • 返回:展开后的新数组。
//【1】 flat
const arr = [1, 2, [3, 4, [5, 6]]];
arr.flat(); // [1, 2, 3, 4, [5, 6]] 默认1层
arr.flat(2); // [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
var arr2 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr2.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


//【2】flatMap
// map() 与 flatMap()
const arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]); // [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]); // [2, 4, 6, 8] 进行了一层展开

// 应用: 对一个数组中的单词进行拆分,然后组成新数组。
const arr2 = ["it's Sunny in", "", "California"];
arr2.map(x => x.split(" ")); // [["it's","Sunny","in"],[""],["California"]]
arr2.flatMap(x => x.split(" ")); // ["it's","Sunny","in", "", "California"]

4.2 Object.fromEntries

Object.fromEntries()

  • 把键值对列表(entries)转换为一个对象。

这个方法和 Object.entries 正好相反。

// 1.基本使用
const obj = {
name: "why",
age: 18,
height: 1.88,
};

// object 转化为 entries
const entries = Object.entries(obj); // [['name', 'why'], ['age', 18], ['height', 1.88]]
// entries 转化为 object
const obj2 = Object.fromEntries(entries); // {name: 'why', age: 18, height: 1.88}

// 2.Polyfill
const newObj = {};
for (const [key, value] of entries) {
newObj[key] = value;
}
// {name: 'why', age: 18, height: 1.88}

应用场景:

当 web 拿到地址栏的参数时,可以快速的解析(URLSearchParams),并生成对象(fromEntries)。

URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串。

  • 默认可迭代,可以把符合 URL 格式的字符串拆分成 entries(键值对列表)。
  • 更多知识:MDN
const queryString = "name=why&age=18&height=1.88";
const queryParams = new URLSearchParams(queryString);
for (const param of queryParams) {
console.log(param);
// ['name', 'why']
// ['age', '18']
}

const paramObj = Object.fromEntries(queryParams);
console.log(paramObj);
// 生成了一个方便使用的对象:{name: 'why', age: '18', height: '1.88'}

4.3 trimStart, trimEnd

  • trim:去除字符串的首尾空格,一次性把开头结尾全部去除;
  • trimStart:只去除开头
  • trimEnd:只去除结尾

返回:原地去除,返回取除后的字符串。

const message = "    Hello World    ";

console.log(message.trim()); // "Hello World"
console.log(message.trimStart()); // "Hello World "
console.log(message.trimEnd()); // " Hello World"

5 ES11 基础

5.1 BigInt

在早期的 JavaScript 中,我们不能正确的表示过大的数字:

  • 大于 MAX_SAFE_INTEGER 的数值,存在精度问题,表示的可能是不正确的。
  • 在数字后添加一个 n 则修改为 BigInt 类型。
  • BigInt(num) 可以将一个 number 转化为 BigInt。
    • 反之,Number(bigint) 可把 BigInt 转化为 number。
// ES11之前 max_safe_integer
const maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt); // 9007199254740991
console.log(maxInt + 1); // 9007199254740992
console.log(maxInt + 2); // 9007199254740992 错误

// ES11之后: BigInt
const bigInt = 900719925474099100n;
console.log(bigInt + 10n); // 900719925474099110n

const num = 100;
console.log(bigInt + BigInt(num)); // 900719925474099200n

const smallNum = Number(bigInt);
console.log(smallNum);

5.2 ??

空值合并操作符:Nullish Coalescing Operator

  • 常用,给一个未定义的空值,附一个默认值。
  • 未定义:undefinednull
const code = undefined;
const res = code ?? "1008";
console.log(res); // 1008

为什么不用 ||

因为 ?? 限定了只能是 undefined / null 的值才可以添加默认值,而 || 是规定只要为 false 就会赋第二个值:

const code = 0;
const res1 = code ?? "1008"; // 0
const res2 = code || "1008"; // 1008

// polyfill
const res = code !== undefined || null ? code : "1008"; // 0

可以看到,这里 res1 没有被添加默认值,因为它本身存在有意义的值 0

  • es5 时,常用三元运算符来判断。

5.3 ?.

可选链:Optional Chaining

  • 常用,和上面的一样,也是用来判断当前变量是否为未定义: undefinednull
// 如果后端返回这样一个结果,我们想读取 description 中的内容,需要用到可选链防止报错
const res = {
code: 200,
ans: {
codeName: {
name: "ninjee",
id: "10086",
description : undefined
},
},
};

// 没有报错,因为在读取 description 时就中断后续的读取了。
const age = res?.ans?.description?.age // undefined

// 如果不判断是否存在,就会报错:
const age = res.ans.description.age;
// Uncaught TypeError: Cannot read properties of undefined (reading 'age')

// es5时的判断:
const age = res && res.ans && res.ans.description && res.ans.description.age;
// undefined

5.4 Global This

在之前我们希望获取 JavaScript 环境的全局对象,不同的环境获取的方式是不一样的:

  • 在浏览器中通过 thiswindow 来获取;
  • 在 Node 中通过 global 来获取;

在 ES11 中对获取全局对象进行了统一的规范:globalThis

// 获取某一个环境下的全局对象(Global Object)

// 在浏览器下
console.log(window); // 在浏览器下获取全局对象,在node下报错(未定义)
console.log(this); // 在浏览器下获取全局对象,在node下返回空对象: {}

// 在node下
console.log(global); // 在浏览器下报错(未定义),在node下获取全局对象

// ES11
console.log(globalThis); // 获取全局对象

5.5 for..in 标准化

在 ES11 之前,虽然很多浏览器支持 for...in 来遍历对象类型,但是并没有被 ECMA 标准化。

在 ES11 中,对其进行了标准化,for...in 是用于遍历对象的 key 的:

  • 遍历所有可枚举属性,包括原型链上的属性,不包括 Symbol。
    • 如果只想看自身属性:使用hasOwnProperty()方法可以判断某属性是不是该对象的实例属性
  • 不按序遍历。

for of:

  • 通常用来遍历数组,或其他可迭代对象。
  • 如果要遍历对象,必须自定义一个 iterator 迭代器。
// for...in 标准化: ECMA
const obj = {
name: "why",
age: 18
}

for (const item in obj) {
console.log(item) // name age
}

如果是数组:

  • for in 遍历数组的索引(index),for of 遍历数组的下标(value)
  • for in 通常用来遍历对象,不要遍历数组。

6 ES12+ 基础

6.1 FinalizationRegistry

FinalizationRegistry 对象

  • 当一个在注册表中注册的对象被回收时,会触发这个回调函数。

用一个清理回调。(清理回调有时被称为 finalizer

  • 调用 register(),注册任何你想要清理回调的对象,传入该对象和所含的值。
// ES12: FinalizationRegistry类
const finalRegistry = new FinalizationRegistry((message) => {
console.log("注册在finalRegistry的对象, 某一个被销毁", message);
});

let obj = { name: "why" };
let info = { age: 18 };

finalRegistry.register(obj, "obj object");
finalRegistry.register(info, "info message");

obj = null;
info = null;

// 一段时间后
// 注册在finalRegistry的对象, 某一个被销毁 info message
// 注册在finalRegistry的对象, 某一个被销毁 obj object

6.2 WeakRefs

如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用

  • 如果我们希望是一个弱引用的话,可以使用WeakRef;
  • deref() 返回WeakRef 实例的目标对象,如果目标对象已被垃圾收集,则返回 undefined
// ES12: WeakRef类
// WeakRef.prototype.deref():
// --> 如果原对象没有销毁, 那么可以获取到原对象
// --> 如果原对象已经销毁, 那么获取到的是undefined
const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象, 某一个被销毁", value);
});

let obj = { name: "why" };
const info = new WeakRef(obj);
// console.log(info, info.deref()); // WeakRef对象,{name: 'why'}
finalRegistry.register(obj, "obj");

obj = null;

setTimeout(() => {
console.log(info.deref()?.name); // undefined
console.log(info.deref() && info.deref().name); // undefined
}, 10000);

// 一段时间后 ...
// 注册在finalRegistry的对象, 某一个被销毁 obj

// 一段时间后 ...
// undefined
// undefined

6.3 逻辑运算符

logical assignment operators 逻辑运算符的简便写法:

||= 逻辑或赋值运算符

  • x ||= y 仅:当 x 是 false 时对其进行赋值(y),否则 x 保持原值不变;
  • 是一个完整的表达式,x 完成了赋值运算。

??= 逻辑空赋值运算符

  • x ??= y: 仅在 x 是 nullundefined 时对其赋值,否则 x 保持原值不变;
  • 是一个完整的表达式,x 完成了赋值运算。

x ?? y :当 x 为 null 或者 undefined 时,返回 y,否则返回左侧操作数。

  • 是一个赋值操作,左侧还需有一个变量接受赋值:const code = x ?? y
// 1.||= 逻辑或赋值运算
// 可以便捷的设置默认值(message为false的情况下),如果是给undefined或null设默认值,用 `??=`
let message = "hello world";
message ||= "default value";
// message = message || "default value";
console.log(message); // hello world

// 2.&&= 逻辑与赋值运算
// &&=
let info = { name: "why" };
// 1.判断info
// 2.有值(true)的情况下, 取出info.name,否则值不变
info &&= info.name;
// info = info && info.name;s
console.log(info); // why

// 3.??= 逻辑空赋值运算
let message = 0;
message ??= "default value";

console.log(message); // 0

6.4 replaceAll

replace() string 替换内容,替换一次 / 全部替换(regex 设置全局模式):

replaceAll() string 全局替换 ( regex 必须为全局模式):

  • 参数1:string 中要替换的字符,支持 string 或 regex。
  • 参数2:替换后的内容
  • 不会修改原数组,而是返回替换后的新数组。

需要注意:

  • replace 字符串作为参数1,只发生一次匹配和一次替换; relaceAll 字符串作为参数1,会发生全局匹配,并全部替换。
  • replace 正则作为参数1,可以匹配一次;也可以 /g 全局匹配,这相当于使用 replaceAll; relaceAll 必须使用 /g 全局匹配。
'aabbcc'.replace('b', '.');  // 'aa.bcc'
'aabbcc'.replaceAll('b', '.'); // 'aa..cc'

// 全局的regex
'aabbcc'.replace(/b/, '.'); // 'aa.bcc'
'aabbcc'.replace(/b/g, '.'); // "aa..cc"
'aabbcc'.replaceAll(/b/g, '.'); // "aa..cc"