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'

箭头函数没有自己的thisthis指向上层

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]

应用:

  1. 复制数组

    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]
    // 克隆了一份副本
    
  2. 合并数组

    let a1=[1];
    let a2=[2];
    [...a1,...a2]; // [1,2]
    // 浅拷贝
    
  3. 和解构结合

    let [f,...rest]=[1,2,3,4];
    f; // [1]
    rest; // [2,3,4]
    
  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"

对于使用setget定义的方法,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(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

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方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性210,其次是字符串属性ba,最后是 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指向原型对象protofoo方法,但是绑定的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}

对于只有一个参数,会直接返回该参数。如果不是对象,会先转成对象,然后返回该对象

注意:

  1. 这里的拷贝是浅拷贝,当source改变时,target同样也会改变

    const obj1 = {a: {b: 1}};
    const obj2 = Object.assign({}, obj1);
    
    obj1.a.b = 2;
    obj2.a.b // 2
    
  2. 同名属性的替换

    const target = { a: { b: 'c', d: 'e' } }
    const source = { a: { b: 'hello' } }
    Object.assign(target, source)
    // { a: { b: 'hello' } }
    
  3. 数组的 处理,按下标来进行覆盖

    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
    
  4. 取值函数的处理: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';

在链式调用的时候判断,左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回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 判断运算符

??主要是和连判断运算符联用,当为undefinednull时返回一个默认值

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';
}
上次更新:
Contributors: YangZhang