An easy guide to object rest/spread
變一個魔術,將這隻貓變成一隻狗,注意 .sound
屬性值如何變化。
const dog = { ...cat, ...{ sound: 'woof' // `` { sound: 'woof', legs: 4 }
後面宣告的 ·woof·
屬性值覆蓋了前面的在 cat
物件宣告的屬性值 'meow'
, 符合之前所說的規則: 對於同名屬性,後宣告的值覆蓋先宣告的值。
這個規則同樣適用於物件的初始化
const anotherDog = { ...cat, sound: 'woof' // `` { sound: 'woof', legs: 4 }
上面程式碼裡,sound: 'woof'
同樣覆蓋了之前宣告的 ' meow'
值。
現在,交換一下擴充套件物件的位置,輸出了不同的結果。
const stillCat = { ...{ sound: 'woof' // `` { sound: 'meow', legs: 4 }
cat
物件仍然是 cat
物件。雖然第一個源物件內的 .sound
屬性值是 'woof'
,但是被之後 cat
物件的 'meow'
覆蓋。
普通屬性和物件擴充套件的相對位置非常重要,這將直接影響到物件克隆,物件合併,以及填充預設屬性的結果。
下面分別詳細介紹。
2.2 克隆物件
用物件擴充套件符克隆一個物件非常簡潔,下面的程式碼克隆了一個 bird
物件。
const bird = { type: 'pigeon', color: 'white'}; const birdClone = { ...bird }; console.log(birdClone); // => { type: 'pigeon', color: 'white' } console.log(bird === birdClone); // => false
...bird
將 bird
物件的自有和可列舉屬性複製到目標物件 birdClone
內。
雖然克隆看起來很簡單,但仍然要注意其中的幾個細微之處。
淺複製
物件擴充套件只是對物件進行了 淺複製, 只有物件自身被複制,而巢狀的物件結構 沒有被複制。
laptop
物件有一個巢狀物件 laptop.screen
。現在我們來克隆 laptop
物件來看看其內部的巢狀物件怎麼變化。
const laptop = { name: 'MacBook Pro', screen: { size: 17, isRetina: true }};const laptopClone = { ...laptop};console.log(laptop === laptopClone); // => false console.log(laptop.screen === laptopClone.screen); // => true
第一個比較語句 laptop === laptopClone
的值為 false
, 說明主物件被正確克隆。
然而 laptop.screen === laptopClone.screen
的計算結果為 true
,說明 laptopClone.screen
沒有被複制,而是 laptop.screen
和 laptopClone.screen
引用了同一個巢狀物件。
好的一點是,你可以在物件的任何一層使用物件擴充套件符,只需要再多做一點工作就同樣可以克隆一個巢狀物件。
const laptopDeepClone = { ...laptop, screen: { ...laptop.screen } }; console.log(laptop === laptopDeepClone); // => false console.log(laptop.screen === laptopDeepClone.screen); // => false
使用 ...laptop.screen
使巢狀物件也被克隆,現在 laptopDeepClone
完全克隆了 laptop
。
原型失去了
下面的程式碼宣告瞭一個 Game
類,並創造了一個 doom
例項。
class Game { constructor(name) { this.name = name; } getMessage() { return `I like ${this.name}!`; } }const doom = new Game('Doom'); console.log(doom instanceof Game); // => true console.log(doom.name); // => "Doom" console.log(doom.getMessage()); // => "I like Doom!"
現在我們克隆一個透過建構函式建立的 doom
例項,結果可能與你想的不同。
const doomClone = { ...doom };console.log(doomClone instanceof Game); // => false console.log(doomClone.name); // => "Doom" console.log(doomClone.getMessage()); // TypeError: doomClone.getMessage is not a function
...doom
將自有屬性 .name
屬性複製到 doomClone
內。
doomClone
現在只是一個普通的 JavaScript 物件,它的原型是 Object.prototype
而不是預想中的Game.prototype
。物件擴充套件不保留源物件的原型。
因此呼叫 doomClone.getMessage()
方法會丟擲一個 TypeError
錯誤,因此 doomClone
沒有繼承 getMessage()
方法。
當然我們可以手動在克隆物件上加上 __proto__
屬性來結局這個問題。
const doomFullClone = { ...doom, __proto__: Game.prototype };console.log(doomFullClone instanceof Game); // => true console.log(doomFullClone.name); // => "Doom" console.log(doomFullClone.getMessage()); // => "I like Doom!"
物件字面量內部的 __proto__
屬性確保了 doomFullClone
的原型為 Game.prototype
。
儘量不要嘗試這種方法。__proto__
屬性已經廢棄,這裡使用它只是為了論證前面的觀點。
物件擴充套件的目的是以淺複製的方式擴充套件自有和可列舉屬性,因此不保留源物件的原型似乎也說得過去。
例外,這裡用 Object.assign()
來克隆 doom
更加合理。
const doomFullClone = Object.assign(new Game(), doom);console.log(doomFullClone instanceof Game); // => true console.log(doomFullClone.name); // => "Doom" console.log(doomFullClone.getMessage()); // => "I like Doom!"
這樣,就保留了原型。
2.3 不可變物件更新
在一個應用裡,同一個物件可能會用於多個地方,直接修改這個物件會帶來意想不到的副作用,並且追蹤這個修改及其困難。
一個好的方式是使操作不可變。不可變性使修改物件更為可控,更有利於書寫。。即時是在複雜的應用場景,由於單向資料流,更容易確定物件的來源和改變的原因。
使用物件擴充套件能更方便的以不可變方式來修改一個物件。假設現在你有一個物件來描述一本書的資訊。
const book = { name: 'JavaScript: The Definitive Guide', author: 'David Flanagan', edition: 5, year: 2008 };
現在,書第六版即將出版,我們用物件擴充套件的處理這個場景。
const newerBook = { ...book, edition: 6, //
newerBook
物件內的...book
擴充套件了book
物件的屬性。手動建立的可列舉屬性editon: 6
和year: 2011
更新了原有的同名屬性。重要的屬性一般在末尾來指定,以便覆蓋前面已經建立的同名屬性。
newerBook
是一個更新了某些屬性的新的物件,並且我們沒有改變原有的book
物件,滿足了不可變性的要求。2.4 合併物件
使用物件擴充套件符合並多個物件非常簡單。
現在我們合併3個物件來建立一個“合成物件”。
const part1 = { color: 'white'};const part2 = { model: 'Honda'};const part3 = { year: 2005};const car = { ...part1, ...part2, ...part3 }; console.log(car); // { color: 'white', model: 'Honda', year: 2005 }上面的例子中,我們使用
part1
、part2
、part3
3個物件合併成了一個car
物件。另外,不要忘了之前講的規則,
後面的屬性值會覆蓋前面的同名屬性值
。這是我們合併有同名屬性物件的計算依據。現在我們稍微改變一下之前的程式碼。給
part1
和part3
增加一個.configuration
屬性。const part1 = { color: 'white', configuration: 'sedan'};const part2 = { model: 'Honda'};const part3 = { year: 2005, configuration: 'hatchback'};const car = { ...part1, ...part2, ...part3 //
...part1
將configuration
屬性設定成了'sedan'
。然而之後的擴充套件符...part3
覆蓋了之前的同名.configuration
,最終生成的物件值為'hatchback'
。2.5 給物件設定預設值
一個物件在程式執行時可能會有多套不同的屬性值,有些屬性可能會被設定,有些則可能被忽略。
這種情況通常發生在一個配置物件上。使用者可以指定一個重要的屬性值,不重要的屬性則使用預設值。
現在我們來實現一個
multline(str, config)
方法,將str
按照給定的長度分割成多行。
config
物件接受下面3個可選的引數。
width
: 分割的字元長度,預設是10
。newLine
: 新增到每一行結尾的的字元, 預設是n
。indent
: 每一行開頭的縮排符,預設是空字串''
。
下面是一些 multline()
執行的例子。
multiline('Hello World!'); // =>` 'Hello Worlnd!'multiline('Hello World!', { width: 6 }); // => 'Hello nWorld!'multiline('Hello World!', { width: 6, newLine: '*' }); // => 'Hello *World!'multiline('Hello World!', { width: 6, newLine: '*', indent: '_' }); // => '_Hello *_World!'
config
引數接受幾套不同的屬性值:你可以指定1,2或者3個屬性值,甚至不指定任何一個屬性。
使用物件擴充套件語法來填充配置物件非常簡單,在物件字面量裡,首先擴充套件預設值物件,然後是配置物件,如下所示:
function multiline(str, config = {}) { const defaultConfig = { width: 10, newLine: 'n', indent: '' }; const safeConfig = { ...defaultConfig, ...config }; let result = ''; // Implementation of multiline() using // safeConfig.width, safeConfig.newLine, safeConfig.indent // ... return result; }
我們來仔細瞭解一下 safeConfig
物件。
...defaultConfig
首先將預設物件的屬性複製,隨後,...config
裡使用者自定義的值覆蓋了之前的預設屬性值。
這樣 safeConfig
值就擁有了所有 multiline()
需要的配置引數。無論呼叫 multiline()
函式時,輸入的 config
是否缺失了某些屬性,都可以保證 safeConfig
擁有所有的必備引數。
顯而易見,物件擴充套件實現了我們想要的 給物件設定預設值。
2.6 更加深入
物件擴充套件更有用的一點是用於巢狀物件,當更新一個複雜物件時,更具有可讀性,比 Object.assign()
更值得推薦。
下面的 box
物件定義一個盒子及盒子內的物品。
const box = { color: 'red', size: { width: 200, height: 100 }, items: ['pencil', 'notebook'] };
box.size
描述了這個盒子的尺寸,box.items
列舉了盒子內的物品。
為了使盒子看起來更高,我們增大 box.size.height
的值,只需要在巢狀物件上使用 物件擴充套件符
。
const biggerBox = { ...box, size: { ...box.size, height: 200 } }; console.log(biggerBox); /* { color: 'red', size: { width: 200, height: 200
...box
確保了biggerBox
獲得了 源物件box
上的全部屬性。更新
box.size
的 height 值需要額外一個{...box.size, height: 200}
物件,該物件接收box.size
的全部屬性,並將 height 值更新至200
。只需要一個語句就能更新物件的多處屬性。
現在如果我們還想把顏色改成
black
,增加盒子的寬度到400
, 並且再放一把尺子到盒子內,應該怎麼辦?同樣很簡單。const blackBox = { ...box, color: 'black', size: { ...box.size, width: 400 }, items: [ ...box.items, 'ruler' ] }; console.log(blackBox); /* { color: 'black',2.7 擴充套件
undefined
、null
和原始型別值
如果在
undefined
、null
和原始型別值
上使用原始型別的值,不會複製任何屬性,也不會丟擲錯誤,只是簡單的返回一個空物件。const nothing = undefined; const missingObject = null; const two = 2;console.log({ ...nothing }); // => { } console.log({ ...missingObject }); // => { } console.log({ ...two }); // => { }如上所示:從
nothing
,missingObject
和two
不會複製任何屬性。當然,這只是一個演示,畢竟根本沒有理由在一個原始型別的值上面使用物件擴充套件符。
3. 剩餘屬性
當使用解構賦值將物件的屬性值賦值給變數後,剩餘的屬性值將會被集合進一個剩餘物件內。
下面的程式碼演示了怎麼使用 rest 屬性。
const style = { width: 300, marginLeft: 10, marginRight: 30};const { width, ...margin } = style; console.log(width); // => 300 console.log(margin); // => { marginLeft: 10, marginRight: 30 }透過解構賦值,我們定義了一個新的變數
width
,並將它的值設定為style.width
。而解構賦值宣告內的...margin
則獲得了style
物件的其餘屬性,margin
物件獲取了marginLeft
和marginRight
屬性。rest 運算子同樣只會獲取自有屬性和可列舉屬性。
注意,在解構賦值內,rest 運算子只能放到最後,因此
const { ...margin , width } = style
無效,並會丟擲一個SyntaxError: Rest element must be last element
錯誤。4. 結論
物件擴充套件需要以下幾點:
它只會提取物件的自有屬性和可列舉屬性
後定義的屬性值會覆蓋之前定義過的同名屬性值
同時,物件擴充套件使用上方便簡潔,能更好的處理巢狀物件,保持不可變性,在實現物件克隆和填充預設屬性值上也使用方便。
而 rest
運算子在解構賦值時可以收集剩餘的屬性。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2618/viewspace-2800791/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 理解spread運算子與rest引數REST
- [譯] Object.assign 和 Object Spread 之爭, 用誰?Object
- JS: Object.assign() Vs Spread OperatorJSObject
- [譯]使用 JavaScript 物件 Rest 和 Spread 的7個技巧JavaScript物件REST
- Object Pascal Style Guide (轉)ObjectGUIIDE
- 【譯】ES2018 新特性:Rest/Spread 特性REST
- 必須要懂的JS之(rest引數與spread語法)JSREST
- 【Rest】PUT Vs Post in RestREST
- REST is not enabled. use -rest to turn onREST
- 字串魔法(easy)字串
- ACM A problem is easyACM
- 【BUUCTF】easy calc
- 【BUUCTF】Easy JavaJava
- 你要知道的 - Spread Operator for objects 技巧Object
- 你要知道的 – Spread Operator for objects 技巧Object
- Spread for ASP.NET技術白皮書ASP.NET
- REST APIsRESTAPI
- rest apiRESTAPI
- The REST ObjectionRESTObject
- REST真相REST
- Easy-Admin
- vue-easy-rendererVue
- type challenge(easy 部分)
- Prefix Flip (Easy Version)
- Catch the Mole(Easy Version)
- 到底什麼樣的 REST 才是最佳 REST?REST
- SOA之(5)——REST的SOA(SOA with REST)概念REST
- j-easy/easy-rules: Java簡單的規則引擎Java
- Uncaught TypeError: Object [object Object] has no method 'xxx'ErrorObject
- 開源的Spread表格控制元件介紹控制元件
- A guide to this in JavaScriptGUIIDEJavaScript
- REST : rest_framework.decorators.api_view 實現PATCHRESTFrameworkAPIView
- REST StreamingREST
- Rest-AssuredREST
- WCF Rest ServiceREST
- 淺談RESTREST
- 關於RESTREST
- Easy-locust Web 版本Web