1. 字符串的扩展
1.1 字符的 Unicode 表示法
ES6加强了对Unicode的支持,但是,这种表示法只限于码点在\u0000
~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。\u20BB7
超过了\uFFFF
,所以是\u20BB
所表示的字符再加上7
"\u0061"
// "a"
"\uD842\uDFB7"
// "𠮷"
"\u00617"
// "a7"
使用大括号可以解决这个问题
"\u{20BB7}"
// "𠮷"
'\u{1F680}' === '\uD83D\uDE80'
// true
1.2 字符串的遍历接口
字符串可以使用for...of
来进行遍历,最大的优点是它相较于普通的for可以识别大于\uFFFF
的字符
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "𠮷"
1.3 模板字符串
在模板字符串中换行和空格会被保留,${}
可以使用变量或调用函数
function fn() {
let user="zzz";
let action="get";
return `User ${user} is not authorized
to do ${action}.`;
}
`foo ${fn()} bar`
// foo Hello World bar
2. 字符串的新增方法
2.1 String.fromCodePoint()
ES6 提供了String.fromCodePoint()
方法,可以识别大于0xFFFF
的字符。
String.fromCodePoint(0x20BB7)
// "𠮷"
2.2 includes(), startsWith(), endsWith()
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
这三个方法都支持第二个参数,前两个表示开始搜索的位置,最后一个表示结束的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.includes('Hello', 6) // false
s.endsWith('Hello', 5) // true
2.3 repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
输入Infinity
或小于等于-1
的数会报错,参数NaN
等同于 0。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
2.4 padStart(),padEnd()
padStart()
用于头部补全,padEnd()
用于尾部补全。padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
如果省略第二个参数,会用空格补全
2.5 trimStart(),trimEnd()
trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
2.6 replaceAll()
replaceAll()
方法,可以一次性替换所有匹配。
replace()
方法,只替换第一个匹配到字符
'aabbcc'.replaceAll('b', '_')
// 'aa__cc'
'aabbcc'.replaceA('b', '_')
// 'aa_bcc'
2.7 at()
at()
方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。如果参数位置超出了字符串范围,at()
返回undefined
。
const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"
str.at(10) // undefined
3. 数值的扩展
3.1 二进制和八进制表示法
S6 提供了二进制和八进制数值的新的写法,分别用前缀0b
(或0B
)和0o
(或0O
)表示。
0b111110111 === 503 // true
0o767 === 503 // true
Number('0b111') // 7
Number('0o10') // 8
3.2 Number.isFinite(), Number.isNaN()
Number.isFinite()
用来检查一个数值是否为有限的(finite),即不是Infinity
。
Number.isNaN()
用来检查一个值是否为NaN
。
注意字符串不会先转为数字类型
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
3.3 Number.parseInt(), Number.parseFloat()
将字符串转换为数字
// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
3.4 Number.isInteger()
判断是否为数字,但是存在误差
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
Number.isInteger(3.0000000000000002) // true
3.5 Number.EPSILON
用来表示一个极小值,它的大小是Math.pow(2, -52)
,一般用于设置能够接受的误差范围
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2); // 设置为误差为2的-50次方
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true
3.6 BigInt
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回
Infinity
。
BigInt
用来表示整数,没有位数限制
1234 // 普通整数
1234n // BigInt
// BigInt 的运算
1n + 2n // 3n
// 注意BigInt!=int
42n === 42 // false
BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n
运算方面和正数一样,但是不可以使用移位和一元+号
4. 函数的扩展
4.1 函数的默认值
4.1.1 基本用法
ES6后函数可以指定声明默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
4.1.2 和解构赋值结合使用
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
上面这个例子只使用了解构赋值而没有使用函数默认值,所以当不传入实参时,会报错,正确写法如下
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
当不传入参数时,优先使用函数默认值,无默认值再使用解构
当传入参数时,只使用解构
function f({ a='no', b = 'world' } = { a: 'hello' }) {
console.log(a);
}
f() // hello
// --------
function f({ a='no', b = 'world' } = {}) {
console.log(a);
}
f() // no
// --------
function f({a, b = 'world' } = {a:"hello"}) {
console.log(a);
}
f({}) // undefined
4.1.3 函数的length属性
返回没有默认值的属性个数,即最少传入几个参数
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
let x=1;
function bar(func = (x) => console.log("inner:"+x)) {
let x=2;
console.log("outer:"+func(x));
}
bar() // ReferenceError: foo is not defined
4.2 函数的作用域
注意在函数的形参声明处是一个单独的定义域
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
// --------
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar(); // outer
// 在函数参数声明处let foo="inner"还没有执行,所以指向全局变量
例子
let x = 1;
function foo(x, y = function() { x = 2; }) {
y();
console.log(x);
}
foo() // 2
x // 1
// 这种情况y中的x指向函数中的x
// --------
let x = 1;
function foo( y = function() { x = 2; }) {
let x = 3;
y();
console.log(x);
}
foo() // 3
x // 2
// 这种情况y中的x指向全局变量
4.3 rest 参数
即可变参数,注意可变参数只能位于最后一个位置
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
4.4 箭头函数
var sum = (num1, num2) => num1 + num2;
如果代码块多于一条语句,那么就需要加入大括号,并使用return
返回
var sum = (num1, num2) => {
console.log("sum: ");
return num1 + num2;
}
箭头函数还可以和解构一起使用
const full = ({ first, last }) => first + ' ' + last;
full({first:1,mid:2,last:3}); // '1 3'
箭头函数没有自己的this
,this
指向上层
5. 数组的扩展
5.1 扩展运算符
和rest
为互逆运算,能够将数组的元素解析出来
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
求数组中的最大值
let arr=[1,2,3];
Math.max(...arr);// 3
将arr2
加入arr1
中
let arr1=[1];
let arr2=[2];
arr1.push(...arr2);
arr1 // [1,2]
应用:
复制数组
let arr1=[1]; let arr2=arr1; arr2[1]=2; arr1; // [1,2] // 这种方法是会让arr2的指针直接指向arr1上,因此修改arr2,arr1也会改变,也就是浅拷贝 // -------- let arr1=[1]; arr2=[...arr1] arr2[1]=2; arr1;// [1] // 克隆了一份副本
合并数组
let a1=[1]; let a2=[2]; [...a1,...a2]; // [1,2] // 浅拷贝
和解构结合
let [f,...rest]=[1,2,3,4]; f; // [1] rest; // [2,3,4]
字符串
[..."arr"] // ['a','r','r']
5.2 Array.from()
Array.from()
可以将类数组对象和可遍历对象转化为数组
类数组对象:
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
'4':'e',
length: 5 // 必须存在
};
let arr2 = Array.from(arrayLike);
arr2; // ['a', 'b', 'c', undefined, 'e']
可遍历对象:
let namesSet = new Set(['a', 'b']);
let test=Array.from(namesSet);
[...namesSet]; // ['a','b']
test; // ['a','b']
Array.from()
还可以传入第二个参数,用来对每个元素进行处理
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
// --------
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]
// ----返回各种参数类型的函数----
function typesOf () {
return Array.from(arguments, value => typeof value)
}
typesOf(null, [], NaN)
// ['object', 'object', 'number']
5.3 Array.of()
主要是用来构造一个数组,代替原有的Array()
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
5.4 实例方法
5.4.1 copyWithin()
使用数组内部的数字进行覆盖
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
5.4.2 find(),findIndex(),findLast(),findLastIndex()
find()
函数接受一个回调函数,每个成员依次执行,找到第一个返回true
的元素,如果没有返回undefined
,回调函数有三个参数,值,下标,和本数组
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
findIndex()
和find()
类似,不过没有找到对应元素返回-1
它们两个还可以接收第二参数,用来绑定回调函数的this对象:
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
findLast()
,findLastIndex()
和上面两个相同,只不过是从末尾开始查找
5.4.3 fill()
一般用于空数组的初始化
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
注意,如果使用如果使用对象来填充,每个元素都指向同一块内存地址
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]
5.4.4 entries(),keys() 和 values()
用于数组遍历
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
5.4.5 includes()
检测是否包含某个元素
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
5.4.6 flat()
用于将多维数组拉平,默认拉平一层,传入Infinity
永远拉为一维数组
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
5.4.7 at()
用于定位,支持负索引,越界返回undefined
const sentence = 'This is a sample sentence';
sentence.at(0); // 'T'
sentence.at(-1); // 'e'
sentence.at(-100) // undefined
sentence.at(100) // undefined
5.4.8 toReversed(),toSorted(),toSpliced(),with()
用法没变,只是不再原数组上操作,会返回一个新数组
5.4.9 group(),groupToMap()
对数组内的元素进行分组,接受参数和find()
相同
const array = [1, 2, 3, 4, 5];
array.group((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// { odd: [1, 3, 5], even: [2, 4] }
groupToMap()
更加直接,无论返回什么都直接作为key:
const array = [1, 2, 3, 4, 5];
const odd = { odd: true };
const even = { even: true };
array.groupToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
6. 对象扩展
6.1 属性的简介表示
在大括号可以直接写变量和函数
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
6.2 属性表达式
可以通过以下两种方式定义对象的属性
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
表达式也可以定义方法
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
如果表达式是一个对象,会被自动转为[object object]
,所以第二个对象表达式,会覆盖第一个
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
6.3 方法的name
方法的name会返回方法的方法名
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
对于使用set
、get
定义的方法,name
不是在方法上,而是在该方法的描述对对象上
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
6.4 属性的可枚举性和遍历
6.4.1 可枚举性
当使用Object.getOwnPropertyDescriptor
获取描述对象中enumerable
的值为true
时,表示当前的属性是可遍历的,以下四种操作会忽略不可枚举的属性
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
6.4.2 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for...in
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上面代码中,Reflect.ownKeys
方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2
和10
,其次是字符串属性b
和a
,最后是 Symbol 属性。
6.5 super
super
,指向当前对象的原型对象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
// 设置obj的原型对象为proto
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
super
只可以用在方法中,以下用法都会报错
// 报错
const obj = {
foo: super.foo
}
// 下面两种是对象中的函数
// 报错
const obj = {
foo: () => super.foo
}
// 报错
const obj = {
foo: function () {
return super.foo
}
}
注意:
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上面代码中,super.foo
指向原型对象proto
的foo
方法,但是绑定的this
却还是当前对象obj
,因此输出的就是world
。
7. 对象扩展方法
7.1 Object.is()
在使用Object.is()
时,它不会将两边的值进行类型转换,这一点类似于===
,但是对于+0
和-0
来说,Object.is()
会把它认为是false
,会把两个NaN
判断相等
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
7.2 Object.assign()
Object.assign()
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。后面的同名属性会覆盖前面的属性,注意它只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false
)。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
对于只有一个参数,会直接返回该参数。如果不是对象,会先转成对象,然后返回该对象
注意:
这里的拷贝是浅拷贝,当
source
改变时,target
同样也会改变const obj1 = {a: {b: 1}}; const obj2 = Object.assign({}, obj1); obj1.a.b = 2; obj2.a.b // 2
同名属性的替换
const target = { a: { b: 'c', d: 'e' } } const source = { a: { b: 'hello' } } Object.assign(target, source) // { a: { b: 'hello' } }
数组的 处理,按下标来进行覆盖
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
取值函数的处理:
Object.assign()
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。const source = { get foo() { return 1 } }; const target = {}; Object.assign(target, source) // { foo: 1 }
7.3 Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
方法,返回指定对象所有自身属性(非继承属性)的描述对象
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
7.4 Object.setPrototypeOf(),Object.getPrototypeOf()
Object.setPrototypeOf(obj,prototype)
接受两个参数,用来将obj的原型对象设置为prototype
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
Object.getPrototypeOf(obj)
用来获取对象的原型对象
7.5 Object.keys(),Object.values(),Object.entries()
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
7.6 Object.fromEntries()
可以将键值对数组、map转化为对象
// 例一
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
7.7 Object.hasOwn()
用于判断属性是继承的还是自身的
const foo = Object.create({ a: 123 });
foo.b = 456;
Object.hasOwn(foo, 'a') // false
Object.hasOwn(foo, 'b') // true
8. 运算符的扩展
8.1 指数运算符
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
let a = 1.5;
a **= 2;
// 等同于 a = a * a;
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
8.2 链判断运算符
对于一个对象的链式的判空,常常需要写很长的if判断,如
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
ES6引入链判断运算符,可将上面的代码改造为
const firstName = message?.body?.user?.firstName || 'default';
在链式调用的时候判断,左侧的对象是否为null
或undefined
。如果是的,就不再往下运算,而是返回undefined
。
对于那些可能没有实现的方法,这个运算符尤其有用。
if (myForm.checkValidity?.() === false) {
// 表单校验失败
return;
}
下面是?.
运算符常见形式,以及不使用该运算符时的等价形式。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
8.3 Null 判断运算符
??
主要是和连判断运算符联用,当为undefined
或null
时返回一个默认值
let a=null;
console.log(a?.b?.c??10); // 10
a={}
console.log(a?.b?.c??10); // 10
a={
b:{
c:{
}
}
}
console.log(a?.b?.c??10); // {}
当任意一级为空,都会返回默认值
8.4 逻辑赋值运算符
// 或赋值运算符
x ||= y
// 等同于
x || (x = y)
// 与赋值运算符
x &&= y
// 等同于
x && (x = y)
// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)
主要使用或赋值运算符来设置默认值
function example(opts) {
opts.foo ??= 'bar';
opts.baz ??= 'qux';
}