ES6 允許在大括號里面,直接寫入變量和函數(shù),作為對象的屬性和方法。這樣的書寫更加簡潔。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
上面代碼中,變量 foo 直接寫在大括號里面。這時,屬性名就是變量名, 屬性值就是變量值。下面是另一個例子。
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!";
}
};
下面是一個實際的例子。
let birth = '2000/01/01';
const Person = {
name: '張三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
這種寫法用于函數(shù)的返回值,將會非常方便。
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint()
// {x:1, y:10}
CommonJS 模塊輸出一組變量,就非常合適使用簡潔寫法。
let ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
屬性的賦值器
(setter)和取值器
(getter),事實上也是采用這種寫法。
const cart = {
_wheels: 4,
get wheels () {
return this._wheels;
},
set wheels (value) {
if (value < this._wheels) {
throw new Error('數(shù)值太小了!');
}
this._wheels = value;
}
}
簡潔寫法在打印對象時也很有用。
let user = {
name: 'test'
};
let foo = {
bar: 'baz'
};
console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}
上面代碼中, console.log 直接輸出 user 和 foo 兩個對象時,就是兩組鍵值對,可能會混淆。把它們放在大括號里面輸出,就變成了對象的簡潔表示法,每組鍵值對前面會打印對象名,這樣就比較清晰了。
注意,簡寫的對象方法不能用作構(gòu)造函數(shù),會報錯。
const obj = {
f() {
this.foo = 'bar';
}
};
new obj.f() // 報錯
上面代碼中, f 是一個簡寫的對象方法,所以 obj.f 不能當(dāng)作構(gòu)造函數(shù)使用。
JavaScript 定義對象的屬性,有兩種方法。
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
上面代碼的方法一是直接用標(biāo)識符作為屬性名,方法二是用表達(dá)式作為屬性名,這時要將表達(dá)式放在方括號之內(nèi)。
但是,如果使用字面量方式定義對象(使用大括號),在 ES5 中只能使用方法一(標(biāo)識符)定義屬性。
var obj = {
foo: true,
abc: 123
};
ES6 允許字面量定義對象時,用方法二(表達(dá)式)作為對象的屬性名,即把表達(dá)式放在方括號內(nèi)。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
下面是另一個例子。
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
表達(dá)式還可以用于定義方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
注意,屬性名表達(dá)式與簡潔表示法,不能同時使用,會報錯。
// 報錯
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
// 正確
const foo = 'bar';
const baz = { [foo]: 'abc'};
注意,屬性名表達(dá)式如果是一個對象,默認(rèn)情況下會自動將對象轉(zhuǎn)為字符串 [object Object] ,這一點要特別小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
上面代碼中, [keyA] 和 [keyB] 得到的都是 [object Object] ,所以 [keyB] 會把 [keyA] 覆蓋掉,而 myObject 最后只有一個 [object Object] 屬性。
函數(shù)的name
屬性,返回函數(shù)名
。對象方法也是函數(shù),因此也有 name 屬性。
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
上面代碼中,方法的 name 屬性返回函數(shù)名(即方法名)。
如果對象的方法使用了取值函數(shù)( getter )和存值函數(shù)( setter ),則 name 屬性不是在該方法上面,而是該方法的屬性的描述對象的 get 和 set 屬性上面,返回值是方法名前加上 get 和 set 。
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"
有兩種特殊情況: bind 方法創(chuàng)造的函數(shù), name 屬性返回 bound 加上原函數(shù)的名字; Function 構(gòu)造函數(shù)創(chuàng)造的函數(shù), name 屬性返回 anonymous 。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
如果對象的方法是一個 Symbol 值,那么 name 屬性返回的是這個 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
上面代碼中, key1 對應(yīng)的 Symbol 值有描述, key2 沒有。
對象的每個屬性都有一個描述對象
(Descriptor),用來控制該屬性的行為。 Object.getOwnPropertyDescriptor
方法可以獲取該屬性的描述對象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述對象的 enumerable 屬性,稱為“可枚舉性”,如果該屬性為 false ,就表示某些操作會忽略當(dāng)前屬性。
目前,有四個操作會忽略 enumerable 為 false 的屬性。
這四個操作之中,前三個是 ES5 就有的,最后一個 Object.assign()
是 ES6 新增的。其中,只有 for...in 會返回繼承的屬性,其他三個方法都會忽略繼承的屬性,只處理對象自身的屬性。實際上,引入“可枚舉”
( enumerable )這個概念的最初目的,就是讓某些屬性可以規(guī)避掉 for...in 操作,不然所有內(nèi)部屬性和方法都會被遍歷到。比如,對象原型的 toString 方法,以及數(shù)組的 length 屬性,就通過“可枚舉性”
,從而避免被 for...in 遍歷到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
上面代碼中, toString 和 length 屬性的 enumerable 都是 false ,因此 for...in 不會遍歷到這兩個繼承自原型的屬性。
另外,ES6 規(guī)定,所有 Class 的原型的方法都是不可枚舉的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
總的來說,操作中引入繼承的屬性會讓問題復(fù)雜化,大多數(shù)時候,我們只關(guān)心對象自身的屬性。所以,盡量不要用 for...in 循環(huán),而用 Object.keys() 代替。
ES6 一共有5 種
方法可以遍歷對象的屬性。
(1)for...in
for...in 循環(huán)遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
(2)Object.keys(obj)
Object.keys 返回一個數(shù)組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames 返回一個數(shù)組,包含對象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一個數(shù)組,包含對象自身的所有 Symbol 屬性的鍵名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys 返回一個數(shù)組,包含對象自身的(不含繼承的)所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。
以上的 5 種方法遍歷對象的鍵名
,都遵守同樣的屬性遍歷的次序規(guī)則。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上面代碼中, Reflect.ownKeys 方法返回一個數(shù)組,包含了參數(shù)對象的所有屬性。這個數(shù)組的屬性次序是這樣的,首先是數(shù)值屬性 2 和 10 ,其次是字符串屬性 b 和 a ,最后是 Symbol 屬性。
我們知道, this
關(guān)鍵字總是指向函數(shù)所在的當(dāng)前對象
,ES6 又新增了另一個類似的關(guān)鍵字 super
,指向當(dāng)前對象的原型對象
。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
上面代碼中,對象 obj.find()
方法之中,通過 super.foo
引用了原型對象proto
的 foo
屬性。
注意, super 關(guān)鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。
// 報錯
const obj = {
foo: super.foo
}
// 報錯
const obj = {
foo: () => super.foo
}
// 報錯
const obj = {
foo: function () {
return super.foo
}
}
上面三種 super 的用法都會報錯,因為對于 JavaScript 引擎來說,這里的 super 都沒有用在對象的方法之中。第一種寫法是 super 用在屬性里面,第二種和第三種寫法是 super 用在一個函數(shù)里面,然后賦值給 foo 屬性。目前,只有對象方法的簡寫法可以讓 JavaScript 引擎確認(rèn),定義的是對象的方法。
JavaScript 引擎內(nèi)部, super.foo
等同于 Object.getPrototypeOf(this).foo (屬性)或 Object.getPrototypeOf(this).foo.call(this) (方法)。
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 卻還是當(dāng)前對象 obj ,因此輸出的就是 world 。
《數(shù)組的擴(kuò)展》一章中,已經(jīng)介紹過擴(kuò)展運(yùn)算符( ... )。ES2018 將這個運(yùn)算符引入了對象。
對象的解構(gòu)賦值
用于從一個對象取值
,相當(dāng)于將目標(biāo)對象自身的所有可遍歷的(enumerable)
、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
上面代碼中,變量 z 是解構(gòu)賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵( a 和 b ),將它們連同值一起拷貝過來。
由于解構(gòu)賦值要求等號右邊是一個對象,所以如果等號右邊是 undefined 或 null ,就會報錯,因為它們無法轉(zhuǎn)為對象。
let { ...z } = null; // 運(yùn)行時錯誤
let { ...z } = undefined; // 運(yùn)行時錯誤
解構(gòu)賦值必須是最后一個參數(shù),否則會報錯。
let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤
上面代碼中,解構(gòu)賦值不是最后一個參數(shù),所以會報錯。
注意,解構(gòu)賦值
的拷貝是淺拷貝
,即如果一個鍵的值是復(fù)合類型
的值(數(shù)組、對象、函數(shù))、那么解構(gòu)賦值拷貝的是這個值的引用
,而不是這個值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
上面代碼中, x 是解構(gòu)賦值所在的對象,拷貝了對象 obj 的 a 屬性。 a 屬性引用了一個對象,修改這個對象的值,會影響到解構(gòu)賦值對它的引用。
另外,擴(kuò)展運(yùn)算符的解構(gòu)賦值,不能復(fù)制繼承自原型對象的屬性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
上面代碼中,對象 o3 復(fù)制了 o2 ,但是只復(fù)制了 o2 自身的屬性,沒有復(fù)制它的原型對象 o1 的屬性。
下面是另一個例子。
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
上面代碼中,變量 x 是單純的解構(gòu)賦值,所以可以讀取對象 o 繼承的屬性;變量 y 和 z 是擴(kuò)展運(yùn)算符的解構(gòu)賦值,只能讀取對象 o 自身的屬性,所以變量 z 可以賦值成功,變量 y 取不到值。ES6 規(guī)定,變量聲明語句之中,如果使用解構(gòu)賦值,擴(kuò)展運(yùn)算符后面必須是一個變量名,而不能是一個解構(gòu)賦值表達(dá)式,所以上面代碼引入了中間變量 newObj ,如果寫成下面這樣會報錯。
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
解構(gòu)賦值的一個用處,是擴(kuò)展某個函數(shù)的參數(shù),引入其他操作。
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用 x 和 y 參數(shù)進(jìn)行操作
// 其余參數(shù)傳給原始函數(shù)
return baseFunction(restConfig);
}
上面代碼中,原始函數(shù) baseFunction 接受 a 和 b 作為參數(shù),函數(shù) wrapperFunction 在 baseFunction 的基礎(chǔ)上進(jìn)行了擴(kuò)展,能夠接受多余的參數(shù),并且保留原始函數(shù)的行為。
對象的擴(kuò)展運(yùn)算符
( ... )用于取出參數(shù)對象的所有可遍歷
屬性,拷貝到當(dāng)前對象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
由于數(shù)組是特殊的對象,所以對象的擴(kuò)展運(yùn)算符也可以用于數(shù)組。
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
如果擴(kuò)展運(yùn)算符后面是一個空對象,則沒有任何效果。
{...{}, a: 1}
// { a: 1 }
如果擴(kuò)展運(yùn)算符后面不是對象,則會自動將其轉(zhuǎn)為對象。
// 等同于 {...Object(1)}
{...1} // {}
上面代碼中,擴(kuò)展運(yùn)算符后面是整數(shù) 1 ,會自動轉(zhuǎn)為數(shù)值的包裝對象 Number{1} 。由于該對象沒有自身屬性,所以返回一個空對象。
下面的例子都是類似的道理。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
但是,如果擴(kuò)展運(yùn)算符后面是字符串,它會自動轉(zhuǎn)成一個類似數(shù)組的對象,因此返回的不是空對象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
對象的擴(kuò)展運(yùn)算符等同于使用 Object.assign()
方法。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
上面的例子只是拷貝了對象實例的屬性,如果想完整克隆一個對象,還拷貝對象原型的屬性,可以采用下面的寫法。
// 寫法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 寫法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 寫法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
上面代碼中,寫法一的 proto 屬性在非瀏覽器的環(huán)境不一定部署,因此推薦使用寫法二和寫法三。
擴(kuò)展運(yùn)算符可以用于合并兩個對象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
如果用戶自定義的屬性,放在擴(kuò)展運(yùn)算符后面,則擴(kuò)展運(yùn)算符內(nèi)部的同名屬性會被覆蓋掉。
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
上面代碼中, a 對象的 x 屬性和 y 屬性,拷貝到新對象后會被覆蓋掉。
這用來修改現(xiàn)有對象部分的屬性就很方便了。
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};
上面代碼中, newVersion 對象自定義了 name 屬性,其他屬性全部復(fù)制自 previousVersion 對象。
如果把自定義屬性放在擴(kuò)展運(yùn)算符前面,就變成了設(shè)置新對象的默認(rèn)屬性值。
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
與數(shù)組的擴(kuò)展運(yùn)算符一樣,對象的擴(kuò)展運(yùn)算符后面可以跟表達(dá)式。
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
};
擴(kuò)展運(yùn)算符的參數(shù)對象之中,如果有取值函數(shù) get ,這個函數(shù)是會執(zhí)行的。
let a = {
get x() {
throw new Error('not throw yet');
}
}
let aWithXGetter = { ...a }; // 報錯
上面例子中,取值函數(shù) get 在擴(kuò)展 a 對象時會自動執(zhí)行,導(dǎo)致報錯。
在實際編程中,如果讀取對象內(nèi)部的某個屬性,往往需要判斷一下該對象是否存在。比如,要讀取 message.body.user.firstName ,安全的寫法是寫成下面這樣。
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
或者使用三元運(yùn)算符 ?: ,判斷一個對象是否存在。
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
這樣的層層判斷非常麻煩,因此 ES2020 引入了“鏈判斷運(yùn)算符”(optional chaining operator) ?. ,簡化上面的寫法。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
上面代碼使用了 ?. 運(yùn)算符,直接在鏈?zhǔn)秸{(diào)用的時候判斷,左側(cè)的對象是否為 null 或 undefined 。如果是的,就不再往下運(yùn)算,而是返回 undefined 。
鏈判斷運(yùn)算符有三種用法。
下面是判斷對象方法是否存在,如果存在就立即執(zhí)行的例子。
iterator.return?.()
上面代碼中,iterator.return
如果有定義,就會調(diào)用該方法,否則直接返回 undefined 。
對于那些可能沒有實現(xiàn)的方法,這個運(yùn)算符尤其有用。
if (myForm.checkValidity?.() === false) {
// 表單校驗失敗
return;
}
上面代碼中,老式瀏覽器的表單可能沒有 checkValidity
這個方法,這時 ?. 運(yùn)算符就會返回 undefined ,判斷語句就變成了 undefined === false ,所以就會跳過下面的代碼。
下面是這個運(yùn)算符常見的使用形式,以及不使用該運(yùn)算符時的等價形式。
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()
上面代碼中,特別注意后兩種形式,如果 a?.b() 里面的 a.b 不是函數(shù),不可調(diào)用,那么 a?.b() 是會報錯的。 a?.() 也是如此,如果 a 不是 null 或 undefined ,但也不是函數(shù),那么 a?.() 會報錯。
使用這個運(yùn)算符,有幾個注意點。
(1)短路機(jī)制
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
上面代碼中,如果 a 是 undefined 或 null ,那么 x 不會進(jìn)行遞增運(yùn)算。也就是說,鏈判斷運(yùn)算符一旦為真,右側(cè)的表達(dá)式就不再求值。
(2)delete 運(yùn)算符
delete a?.b
// 等同于
a == null ? undefined : delete a.b
上面代碼中,如果 a 是 undefined 或 null ,會直接返回 undefined ,而不會進(jìn)行 delete 運(yùn)算。
(3)括號的影響
如果屬性鏈有圓括號,鏈判斷運(yùn)算符對圓括號外部沒有影響,只對圓括號內(nèi)部有影響。
(a?.b).c
// 等價于
(a == null ? undefined : a.b).c
上面代碼中, ?. 對圓括號外部沒有影響,不管 a 對象是否存在,圓括號后面的 .c 總是會執(zhí)行。
一般來說,使用 ?. 運(yùn)算符的場合,不應(yīng)該使用圓括號。
(4)報錯場合
以下寫法是禁止的,會報錯。
// 構(gòu)造函數(shù)
new a?.()
new a?.b()
// 鏈判斷運(yùn)算符的右側(cè)有模板字符串
a?.``
a?.b`{c}`
// 鏈判斷運(yùn)算符的左側(cè)是 super
super?.()
super?.foo
// 鏈運(yùn)算符用于賦值運(yùn)算符左側(cè)
a?.b = c
(5)右側(cè)不得為十進(jìn)制數(shù)值
為了保證兼容以前的代碼,允許 foo?.3:0 被解析成 foo ? .3 : 0 ,因此規(guī)定如果 ?. 后面緊跟一個十進(jìn)制數(shù)字,那么 ?. 不再被看成是一個完整的運(yùn)算符,而會按照三元運(yùn)算符進(jìn)行處理,也就是說,那個小數(shù)點會歸屬于后面的十進(jìn)制數(shù)字,形成一個小數(shù)。
讀取對象屬性的時候,如果某個屬性的值是 null 或 undefined ,有時候需要為它們指定默認(rèn)值。常見做法是通過 || 運(yùn)算符指定默認(rèn)值。
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
上面的三行代碼都通過 || 運(yùn)算符指定默認(rèn)值,但是這樣寫是錯的。開發(fā)者的原意是,只要屬性的值為 null 或 undefined ,默認(rèn)值就會生效,但是屬性的值如果為空字符串或 false 或 0 ,默認(rèn)值也會生效。
為了避免這種情況,ES2020 引入了一個新的 Null 判斷運(yùn)算符 ?? 。它的行為類似 || ,但是只有運(yùn)算符左側(cè)的值為 null 或 undefined 時,才會返回右側(cè)的值。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
上面代碼中,默認(rèn)值只有在屬性值為 null 或 undefined 時,才會生效。
這個運(yùn)算符的一個目的,就是跟鏈判斷運(yùn)算符 ?. 配合使用,為 null 或 undefined 的值設(shè)置默認(rèn)值。
const animationDuration = response.settings?.animationDuration ?? 300;
上面代碼中, response.settings 如果是 null 或 undefined ,就會返回默認(rèn)值300。
這個運(yùn)算符很適合判斷函數(shù)參數(shù)是否賦值。
function Component(props) {
const enable = props.enabled ?? true;
// …
}
上面代碼判斷 props 參數(shù)的 enabled 屬性是否賦值,等同于下面的寫法。
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
?? 有一個運(yùn)算優(yōu)先級問題,它與 && 和 || 的優(yōu)先級孰高孰低?,F(xiàn)在的規(guī)則是,如果多個邏輯運(yùn)算符一起使用,必須用括號表明優(yōu)先級,否則會報錯。
// 報錯
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
上面四個表達(dá)式都會報錯,必須加入表明優(yōu)先級的括號。
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
```ES6 允許在大括號里面,直接寫入變量和函數(shù),作為對象的屬性和方法。這樣的書寫更加簡潔。
更多建議: