簡體   English   中英

我不了解對象內的傳播語法

[英]I don't understand about spread syntax inside objects

我不了解對象內的傳播語法。

console.log(...false) // TypeError not iterable
console.log(...1) // TypeError not iterable
console.log(...null) // TypeError not iterable
console.log(...undefined) // TypeError not iterable

我理解上面的代碼由於非迭代器而發生錯誤。

但是這些代碼運行良好。

console.log({...false}) // {}
console.log({...1}) // {}
console.log({...null}) // {}
console.log({...undefined}) // {}

請讓我知道為什么上述代碼有效。

沒有傳播運算符!

這對於了解正在發生的事情非常重要,所以我必須從它開始。

語言中沒有定義擴展運算符 有擴展語法,但作為其他類型語法的子類別。 這聽起來只是語義,但它對如何以及為什么...工作有非常實際的影響。

操作員每次都以相同的方式行事。 如果您使用delete運算符作為delete obj.x ,那么無論上下文如何,您總是會得到相同的結果。 typeof或什- (減號)相同。 運算符定義將在代碼中完成的操作。 它總是相同的動作。 有時運算符可能會像+一樣重載:

 console.log("a" + "b"); //string concatenation console.log(1 + 2); //number addition

但它仍然不隨上下文而變化——你把這個表達放在什么地方

...語法是不同的-它不是在不同的地方相同的操作:

 const arr = [1, 2, 3]; const obj = { foo: "hello", bar: "world" }; console.log(Math.max(...arr)); //spread arguments in a function call function fn(first, ...others) {} //rest parameters in function definition console.log([...arr]); //spread into an array literal console.log({...obj}); //spread into an object literal

這些都是不同的語法,看起來相似,行為相似,但絕對不一樣。 如果...是運算符,您可以更改操作數並且仍然有效,但事實並非如此:

 const obj = { foo: "hello", bar: "world" }; console.log(Math.max(...obj)); //spread arguments in a function call //not valid with objects

 function fn(...first, others) {} //rest parameters in function definition //not valid for the first of multiple parameters

 const obj = { foo: "hello", bar: "world" }; console.log([...obj]); //spread into an array literal //not valid when spreading an arbitrary object into an array

所以,每次使用...有不同的規則和工作並不像任何其他用途。

原因很簡單: ...是不是一回事的。 該語言定義了不同事物的語法,例如函數調用、函數定義、數組文字和對象。 讓我們專注於最后兩個:

這是有效的語法:

 const arr = [1, 2, 3]; // ^^^^^^^^^ // | // +--- array literal syntax console.log(arr); const obj = { foo: "hello", bar: "world!" }; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // | // +--- object literal syntax console.log(obj);

但這些不是:

 const arr = [0: 1, 1: 2, 2: 3]; //invalid - you cannot have key-value pairs

 const obj = { 1, 2, 3 }; //invalid - you need key-value pairs

不足為奇 - 不同的語法有不同的規則。

同樣,這同樣適用於使用...[...arr]{...obj}只是您可以在 JavaScript 中使用的兩種不同類型的代碼,但...用法之間沒有重疊,只是如何您可以將1用作[1]{ 1: "one" }但這兩次的含義不同。

當您在函數調用中使用傳播並傳播到對象中時,實際會發生什么?

這是真正需要回答的問題。 畢竟,這些是不同的操作。

您的帶有console.log(...false)console.log({...false})示例演示了一個函數調用和一個對象字面量的用法,所以我將討論這兩個。 請注意,數組字面量擴展語法[...arr]在有效和無效方面的行為非常相似,但在這里並不是很相關。 重要的是為什么對象的行為不同,所以我們只需要一個例子來比較。

函數調用傳播fn(...args)

規范甚至沒有針對此構造的特殊名稱。 它只是ArgumentList的一種類型,在第12.3.8.1運行時語義:ArgumentListEvaluation (ECMAScript 語言規范鏈接)中,它本質上定義了“如果參數列表具有...那么像這樣評估代碼”。 我會為您省去規范中使用的無聊語言(如果您想查看該鏈接,請隨時訪問該鏈接)。

要采取的步驟的關鍵點是使用...args引擎將嘗試獲取args的迭代器。 本質上是由迭代協議(MDN鏈接)定義的。 為此,它將嘗試調用用@@iterator (或@@asyncIterator )定義的方法。 這就是你得到 TypeError 的地方——它發生在args沒有公開這樣的方法時。 沒有方法,意味着它不是可迭代的,因此引擎無法繼續調用該函數。

為了完整起見,如果args可迭代的,那么引擎將遍歷整個迭代器直到耗盡並從結果中創建參數。 這意味着我們可以在函數調用中使用任何具有擴展語法的任意迭代:

 const iterable = { [Symbol.iterator]() { //define an @@iterator method to be a valid iterable const arr = ["!", "world", "hello"]; let index = arr.length; return { next() { //define a `next` method to be a valid iterator return { //go through `arr` backwards value: arr[--index], done: index < 0 } } } } } console.log(...iterable);

對象傳播{...obj}

規范中仍然沒有此構造的特殊名稱。 它是對象文字的一種PropertyDefinition類型。 12.2.6.8運行時語義:PropertyDefinitionEvaluation (ECMAScript 語言規范鏈接)定義了如何處理它。 我將再次為您提供定義。

不同之處在於obj元素在擴展其屬性時是如何處理的。 為此,執行抽象操作CopyDataProperties ( target, source, excludedItems ) (ECMAScript 語言規范鏈接)。 這個可能值得一讀,以更好地了解到底發生了什么。 我將只關注重要的細節:

  1. 用表達式{...foo}

    • target將是新對象
    • source將是foo
    • excludedItems將是一個空列表,所以它無關緊要
  2. 如果source (提醒,這是代碼中的foo )為nullundefined則操作結束, targetCopyDataProperties操作返回。 否則,繼續。

  3. 下一個重要的事情是foo將變成一個對象。 這將使用ToObject ( argument )抽象操作,其定義如下(再次提醒您不會在此處獲得nullundefined ):

參數類型 結果
不明確的 拋出 TypeError 異常。
空值 拋出 TypeError 異常。
布爾值 返回一個新的布爾對象,其 [[BooleanData]] 內部插槽設置為參數。 有關布爾對象的描述,請參見 19.3。
數字 返回一個新的 Number 對象,其 [[NumberData]] 內部槽設置為參數。 有關 Number 對象的說明,請參見 20.1。
細繩 返回一個新的 String 對象,其 [[StringData]] 內部槽設置為參數。 有關 String 對象的描述,請參見 21.1。
象征 返回一個新的 Symbol 對象,其 [[SymbolData]] 內部槽設置為參數。 參見 19.4 了解 Symbol 對象的描述。
大整數 返回一個新的 BigInt 對象,其 [[BigIntData]] 內部槽設置為參數。 有關 BigInt 對象的描述,請參見 20.2。
目的 返回參數。

我們稱這個操作的結果from

  1. from中的所有可枚舉屬性都將以其值寫入target

  2. 展開操作完成, target是使用對象字面量語法定義的新對象。 完成的!

更概括地說,當您使用帶有對象字面量的擴展語法時,正在擴展的源將首先轉換為對象,然后實際上只有自己的可枚舉屬性會被復制到正在實例化的對象上。 在傳播nullundefined的情況下,傳播只是一個無操作:不會復制任何屬性並且操作正常完成(不拋出錯誤)。

這與函數調用中的傳播方式非常不同,因為不依賴於迭代協議。 您傳播的項目根本不必是可迭代的。

因為像NumberBoolean這樣的原始包裝器不產生任何自己的屬性,所以沒有什么可以復制的:

 const numberWrapper = new Number(1); console.log( Object.getOwnPropertyNames(numberWrapper), //nothing Object.getOwnPropertySymbols(numberWrapper), //nothing Object.getOwnPropertyDescriptors(numberWrapper), //nothing ); const booleanWrapper = new Boolean(false); console.log( Object.getOwnPropertyNames(booleanWrapper), //nothing Object.getOwnPropertySymbols(booleanWrapper), //nothing Object.getOwnPropertyDescriptors(booleanWrapper), //nothing );

然而,字符串對象確實有自己的屬性,其中一些是可枚舉的。 這意味着您可以將字符串擴展到對象中:

 const string = "hello"; const stringWrapper = new String(string); console.log( Object.getOwnPropertyNames(stringWrapper), //indexes 0-4 and `length` Object.getOwnPropertySymbols(stringWrapper), //nothing Object.getOwnPropertyDescriptors(stringWrapper), //indexes are enumerable, `length` is not ); console.log({...string}) // { "0": "h", "1": "e", "2": "l", "3": "l", "4": "o" }

這里更好地說明了值在傳播到對象中時的行為方式:

 function printProperties(source) { //convert to an object const from = Object(source); const descriptors = Object.getOwnPropertyDescriptors(from); const spreadObj = {...source}; console.log( `own property descriptors:`, descriptors, `\\nproduct when spread into an object:`, spreadObj ); } const boolean = false; const number = 1; const emptyObject = {}; const object1 = { foo: "hello" }; const object2 = Object.defineProperties({}, { //do a more fine-grained definition of properties foo: { value: "hello", enumerable: false }, bar: { value: "world", enumerable: true } }); console.log("--- boolean ---"); printProperties(boolean); console.log("--- number ---"); printProperties(number); console.log("--- emptyObject ---"); printProperties(emptyObject); console.log("--- object1 ---"); printProperties(object1); console.log("--- object2 ---"); printProperties(object2);

對象傳播是完全不同的。 它在內部映射到Object.assign()

所以const a = {...1}const a = Object.assign({}, 1)這里Object.assign({},1)1視為object而不是number 因此,您沒有收到任何拋出的異常。

此外,如果您對數組[...1]嘗試過相同的操作,它應該會拋出錯誤,因為它不會將1視為object並且您會得到與..1相同的行為。

總結一下:

console.log({...false}) => console.log(Object.assign({}, false))
console.log({...1}) => console.log(Object.assign({}, 1))
console.log({...null}) => console.log(Object.assign({}, null))
console.log({...undefined}) => console.log(Object.assign({}, undefined))

PS: Object.assign() 規范

嗯,這就是 JS 的美妙之處,這要歸功於可迭代協議 這意味着它意味着數組或映射。 默認情況下,這兩個都在語言構造中分配了行為,即它是一組我們可以一個接一個迭代的項目。 我們還可以根據需要統計和添加和刪除項目。

Example.JS 默認將它們理解為系列集合或集合或組。

 const array1 = [1, 4, 9, 16]; console.log(array1.length); array1.push(5); console.log(array1.length);

現在這些不是 JS 中唯一的可迭代對象類型,字符串也是。

 string = 'abc'; console.log(string.length) string = string+'d'; console.log(string.length) console.log(string[3])

然后有類似數組的對象也可以迭代

 let arrayLike = { 0: "Hello", 1: "World", }; console.log(arrayLike[1])
現在讓我們通過一個例子來理解你的第二個例子的實例,運行下面的代碼 console.log 並拋出錯誤。 因為默認情況下,對象不像數組和類似數組的對象那樣具有迭代行為。 由於擴展運算符聲明將三個點之后的任何點視為數組,如果它適合構造。 所以{...false}幾乎可以在下面的例子中對 b 發生什么。 它仍然是一個空對象,因為對象需要鍵值配對。

 a = [1,2,3]; b={1,2,3}; console.log(a[1]); console.log(b[1]);

a 不需要配對鍵值定義,默認情況下它自己這樣做,正如眾所周知的索引。

 a = [4,5,6]; b={1:4,2:5,3:6}; console.log(a[1]); console.log(b[1]);
在同一張紙條上閱讀這個例子

 a = [1,2,3]; b=[4,5,6]; c= [...a,...b]; d = [...a,b[1]]; console.log(c); console.log(d);

...(三個點)只告訴 Js 將其視為數組,如果它是可迭代的,否則只會拋出錯誤。 true false 不可迭代,大括號中的對象也不可迭代。 這就是為什么對象保持空白,因為......不會在非迭代項目上工作。 這有效

 a = [1,2,3]; b = {...a}; console.log(b)

這不 - kaboom

 a = [...false];

這也不起作用,只是保持沉默 - shshshs

 a = {...false};

我希望你明白這一點。 其他任何事情都會彈出后續問題。

For example
var array1 = [1, 2, 3, 4];
var array2 = [5, 6, 7, 8];
array2 = [ ...array1, ...array2 ] // [1, 2, 3, 4, 5, 6, 7, 8]

/** spread array example */
var str1 = "hello";
var result_ary = [...str1] // ["h", "e", "l", "l", "o"]

Spread 語法 (...) 允許在需要零個或多個參數(用於函數調用)或元素(用於數組文字)的地方擴展諸如數組表達式或字符串之類的可迭代對象,或者擴展對象表達式需要零個或多個鍵值對(用於對象文字)的地方。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM