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
- 用处:判断数组中是否存在某个成员。
- 参数:
- 需要查找的成员值;
- 从第 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中增加了 padStart
和 padEnd
方法,可对字符串首位进行填充,来实现某种格式化效果。
- 参数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
- 常用,给一个未定义的空值,附一个默认值。
- 未定义:
undefined
和null
。
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
- 常用,和上面的一样,也是用来判断当前变量是否为未定义:
undefined
和null
。
// 如果后端返回这样一个结果,我们想读取 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 环境的全局对象,不同的环境获取的方式是不一样的:
- 在浏览器中通过
this
、window
来获取; - 在 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 是null
或undefined
时对其赋值,否则 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"