<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

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

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看