JavaScript展開運算子和剩餘運算子的區別詳解
目錄
- 什麼是剩餘運算子?
- 剩餘運算子在函式中是如何工作的?
- 注意!不能在包含剩餘引數的函式體中使用"use strict"
- 剩餘運算子在引數解構中是如何工作的?
- Script arguments和剩餘引數之間有哪些區別?
- 什麼是展開運算子以及它在JavaScript中是如何工作的?
- 有關展開運算子我們需要知道的
- 1. 展開運算子不能展開物件字面量的值
- 2. 展開運算子不克隆相同的屬性
- 3. 注意展開運算子在包含非原語的物件中是何如工作的
- 如果myName陣列包含非原語項呢?
- 結語
JavaScript使用符號三個點(...)作為剩餘運算子和展開運算子,不過這兩個運算子是有區別的。
最主要的區別就是,剩餘運算子將使用者提供的某些特定值的其餘部分放入JavaScript陣列中,而展開運算子則將可迭代的物件展開為單個元素。
例如下面這段程式碼,其中使用了剩餘運算子將部分值存放到陣列中:
// Use rest to enclose the rest of specific user-supplied values into an array: function myBio(firstName,lastName,...otherInfo) { return otherInfo; } // Invoke myBio function while passing five arguments to its parameters: myBio("Oluwatobi","Sofela","CodeSweetly","Web Developer","Male"); // The invocation above will return: ["CodeSweetly","Male"]
檢視執行結果
上面的程式碼中,我們使用...otherInfo將傳入函式myBio()引數中的剩餘部分"CodeSweetly","We Developer"和"Male"存放到陣列中。
然後我們再來看下面這個例子,其中使用了展開運算子:
// Define a function with three parameters:
function myBio(firstName,company) {
return `${firstName} ${cCIkYAuclastName} runs ${company}`;
}
// Use spread to expand an array's items into individual arguments:
myBio(...["Oluwatobi","CodeSweetly"]);
// The invocation above will return:
“Oluwatobi Sofela runs CodeSweetly”
檢視執行結果
上面的程式碼中,我們使用展開運算子(...)將陣列["Oluwatobi","Sofela","CodeSweetly"]的內容逐一展開並傳遞給函式myBio()的引數。
如果你對剩餘運算子和展開運算子不是很熟悉,不用太擔心,本文接下來會對它們進行介紹。
在接下來的章節中,我們將詳細討論剩餘運算子和展開運算子在JavaScript中是如何工作的。
什麼是剩餘運算子?
正如上面所提到的,剩餘運算子將使用者提供的某些特定值的其餘部分放入JavaScript陣列中。語法如下:
...yourValues
上面程式碼中的三個點(...)用來表示剩餘運算子。
剩餘運算子之後的內容用來表示你希望填充到陣列中的值。注意,只能在函式定義的最後一個引數中使用剩餘運算子。
剩餘運算子在JavaScript函式中是如何工作的?
在JavaScript函式中,剩餘運算子被用在函式最後一個引數的前面。如下面的程式碼:
// Define a function with two regular parameters and one rest parameter: function myBio(firstName,...otherInfo) { return otherInfo; }
這裡,剩餘運算子告訴JavaScript程式將使用者提供給引數otherInfo的任何值都新增到一個數組中。然後,將該陣列賦值給otherInfo引數。
因此,我們將...otherInfo稱之為剩餘引數。
需要注意的是,呼叫時傳遞給函式的實參中剩餘引數是可選的。
另一個例子:
// Define a function with two regular parameters and one rest parameter: function myBio(firstName,"Male"]
檢視執行結果
上面的程式碼中,呼叫函式myBio()時傳入了5個引數,而函式myBio()的實參只有3個。也就是說,引數"Oluwatobi"和"Sofela"分別傳遞給了firstName和lastName,其它的引數("CodeSweetly","Web Developer"和"Male")都傳遞給了剩餘引數otherInfo。所以,函式myBio()返回的結果是["CodeSweetly","Web Developer","Male"],它們都是剩餘引數otherInfo的內容。
注意!不能在包含剩餘引數的函式體中使用"use strict"
記住,不能在任何包含剩餘引數,預設引數,或引數解構的函式體中使用"use strict"指令。否則http://www.cppcns.com,JavaScript將報語法錯誤。
考慮下面的程式碼:
// Define a function with one rest parameter: function printMyName(...value) { "use strict"; return value; } // The definition above will return: "Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list"
注意:只有當整個或封閉作用域處於strict模式時,才可以將"use strict"放到函式體外。
現在我們知道了剩餘運算子在函式中的作用,接下來我們來看看它在引數解構中是如何工作的。
剩餘運算子在引數解構中是如何工作的?
剩餘運算子在引數解構賦值時通常被用在最後一個變數的前面。下面是一個例子:
// Define a destructuring array wicCIkYAucth two regular variables and one rest variable:
const [firstName,...otherInfo] = [
"Oluwatobi","Male"
];
// Invoke the otherInfo variable:
console.log(otherInfo);
// The invocation above will return:
["CodeSweetly","Male"]
檢視執行結果
這裡的剩餘運算子(...)告訴Jahttp://www.cppcns.comvaScript程式將使用者提供的其它值都新增到一個數組中。然後,將該陣列賦值給otherInfo變數。
因此,我們將這裡的...otherInfo稱之為剩餘變數。
另一個例子:
// Define a destructuring object with two regular variables and one rest variable: const { firstName,...otherInfo } = { firstName: "Oluwatobi",lastName: "Sofela",companyName: "CodeSweetly",profession: "Web Developer",gender: "Male" } // Invoke the otherInfo variable: console.log(otherInfo); // The invocation above will return: {companyName: "CodeSweetly",gender: "Male"}
檢視執行結果
注意上面的程式碼中,剩餘運算子將一個屬性物件(而不是陣列)賦值給otherInfo變數。也就是說,當你在解構一個物件時使用剩餘運算子,它將生成一個屬性物件而非陣列。
但是,如果你在解構陣列或函式時使用剩餘運算子,它將生成陣列字面量。
你應該已經注意到了,在JavaScript arguments和剩餘引數之間存在一些區別,下面我們來看看這些區別都有哪些。
JavaScript arguments和剩餘引數之間有哪些區別?
下面我列出了JavaScript arguments和剩餘引數之間的一些區別:
區別1:arguments物件是一個類似於陣列的物件,但它並非真正的陣列!
請記住這一點,JavaScript arguments物件不是真正的陣列。它是一個類似於陣列的物件,不具備陣列所擁有的任何特性。
而剩餘引數是一個真正的陣列,你可以在它上面使用陣列所擁有的任何方法。例如,你可以對一個剩餘引數使用sort()、map()、forEach()或pop()方法。但你不能對arguments物件使用這些方法。
區別2:不能在箭頭函式中使用arguments物件
arguments物件在箭頭函式中不可用,而剩餘引數在所有的函式中都是可用的,也包括箭頭函式。
區別3:優先使用剩餘引數
優先使用剩餘引數而不是arguments物件,特別是在編寫ES6相容程式碼時。
我們已經瞭解了剩餘運算子是如何工作的,下面我們來討論下展開運算子,並看看它和剩餘運算子之間的區別。
什麼是展開運算子以及它在JavaScript中是如何工作的?
展開運算子(...)將一個可迭代的物件展開為單個元素。
展開運算子可以應用在陣列字面量,函式呼叫,以及被初始化的屬性物件中,它將可迭代物件的值逐一展開到單獨的元素中。實際上,它和剩餘操作符正好相反。
注意,只有在陣列字面量,函式呼叫,或被初始化的屬性物件中使用展開運算子時才有效。
下面我們來看一些實際的例子。
示例1:展開運算子在陣列字面量中如何工作
const myName = ["Sofela","is","my"]; const aboutMe = ["Oluwatobi",...myName,"name."]; console.log(aboutMe); // The invocation above will return: [ "Oluwatobi","my","name." ]
檢視執行結果
上面的程式碼中,展開運算子(...)將陣列myName拷貝到aboutMe中。
注意:
- 對myName的任何修改不會反映到aboutMe中。因為myName陣列中的所有值都是原語。擴充套件操作符只是簡單地將myName陣列的內容複製並貼上到aboutMe中,而不建立任何對原始陣列元素的引用。
- 展開運算子只做淺拷貝。所以,當myName陣列中包含任何非原語值時,JavaScript將在myName和aboutMe之間建立一個引用。有關展開運算子如何處理原語值和非原語值的更多資訊,可以檢視這裡。
- 假設我們沒有使用展開運算子來複制陣列myName的內容,例如我們編寫這行程式碼const aboutMe = [ "Oluwatobi",myName,"name." ] 這種情況下JavaScript將給myName分配一個引用,這樣,所有對myName陣列所做的修改就都會反映到aboutMe陣列中。
示例2:如何使用展開運算子將字串轉換為陣列
const myName = "Oluwatobi Sofela"; console.log([...myName]); // The invocation above will return: [ "O","l","u","w","a","t","o","b","i"," ","S","f","e","a" ]
檢視執行結果
上面的程式碼中,我們在陣列字面量中使用展開運算子([...])將myName字串的值展開為一個數組。這樣,字www.cppcns.com符串"Oluwatobi Sofela"的內容被展開到陣列[ "O","l","u","w","a","t","o","b","i"," ","S","f","e","a" ]中。
示例3:展開運算子如何在函式呼叫中工作
const numbers = [1,3,5,7]; function addNumbers(a,b,c,d) { return a + b + c + d; } console.log(addNumbers(...numbers)); // The invocation above will return: 16
檢視執行結果
上面的程式碼中,我們使用展開運算子將陣列numbers的內容展開並傳遞給函式addNumbers()的引數。如果陣列numbers的元素多於4個,JavaScript只會將前4個元素作為引數傳遞給函式addNumbers()而忽略其餘的元素。
下面是一些其它的例子:
const numbers = [1,7,10,200,90,59]; function addNumbers(a,d) { return a + b + c + d; } console.log(addNumbers(...numbers)); // The invocation above will return: 16
檢視執行結果
const myName = "Oluwatobi Sofela"; function spellName(a,c) { return a + b + c; } console.log(spellName(...myName)); // returns: "Olu" console.log(spellName(...myName[3])); // returns: "wundefinedundefined" console.log(spellName([...myName])); // returns: "O,l,u,w,a,t,o,i,S,f,e,aundefinedundefined" console.log(spellName({...myName})); // returns: "[object Object]undefinedundefined"
檢視執行結果
示例4:展開運算子在物件字面量中如何工作
const myNames = ["Oluwatobi","Sofela"]; const bio = { ...myNames,runs: "codesweetly.com" }; console.log(bio); // The invocation above will return: { 0: "Oluwatobi",1: "Sofela",runs: "codesweetly.com" }
檢視執行結果
上面的程式碼中,我們在bio物件內部使用展開運算子將陣列myNames的值展開為各個屬性。
有關展開運算子我們需要知道的
當使用展開運算子時,請記住以下三個基本資訊。
1. 展開運算子不能展開物件字面量的值
由於屬性物件是非可迭代物件,所以不能使用展開運算子將它的值進行展開。但是,你可以使用展開運算子將一個物件的屬性克隆到另一個物件中。
看下面這個例子:
const myName = { firstName: "Oluwatobi",lastName: "Sofela" }; const bio = { ...myName,website: "codesweetly.com" }; console.log(bio); // The invocation above will return: { firstName: "Oluwatobi",website: "codesweetly.com" };
檢視執行結果
上面的程式碼中,我們使用展開運算子將myName物件的內容克隆到了bio物件中。
注意:
- 展開運算子只能展開可迭代物件的值。
- 只有當一個物件包含一個帶有@@iterator key的屬性時,才是一個可迭代的物件。
- Array,TypedArray,String,Map,Set都是內建的可迭代型別,因為它們預設都帶有@@iterator屬性。
- 屬性物件是非可迭代陣列型別,因為預設情況下它沒有@@iterator屬性。
- 可以在屬性物件上新增@@iterator使其成為可迭代物件。
2. 展開運算子不克隆相同的屬性
假設我們使用展開運算子將物件A的屬性克隆到物件B中,如果物件B包含與物件A中相同的屬性,那麼物件B的屬性將覆蓋物件A的屬性。換句話說,在這個過程中,物件A中那些與物件B相同的屬性不會被克隆。
看下面這個例子:
const myName = { firstName: "Tobi",firstName: "Oluwatobi",website: "codesweetly.com" };
檢視執行結果
注意,展開運算子沒有將myName物件的firstName屬性的值複製到bio物件中,因為物件bio中已經包含firstName屬性了。
3. 注意展開運算子在包含非原語的物件中是何如工作的
如果在只包含原語值的物件(或陣列)上使用展開運算子,JavaScript不會在原物件和複製物件之間建立任何引用。
看下面這段程式碼:
const myName = ["Sofela","name."]; console.log(aboutMe); // The invocation above will return: ["Oluwatobi","name."]
檢視執行結果
注意,myName中的每一個元素都是一個原語值,因此,當我們使用展開運算子將myName克隆到aboutMe時,JavaScript不會在兩個陣列之間建立任何引用。所以,對myName陣列所做的任何修改都不會反映到aboutMe陣列中,反之亦然。
讓我們給myName陣列新增一個元素:
myName.push("real");
現在我們來檢查一下myName和aboutMe的值:
console.log(myName); // ["Sofela","real"] console.log(aboutMe); // ["Oluwatobi","name."]
請注意,我們對myName陣列的修改並沒有反映到aboutMe陣列中,因為展開運算子並沒有在原始陣列和複製陣列之間建立任何引用。
如果myName陣列包含非原語項呢?
假設myName包含非原語項,這種情況下,展開運算子將在原陣列的非原語項和克隆項之間建立一個引用。
看下面的例子:
const myName = [["Sofela","my"]]; const aboutMe = ["Oluwatobi",["Sofela","my"],"name." ]
檢視執行結果
注意,這裡的myName陣列包含一個非原語項。
因此,當使用展開運算子將myName的內容克隆到aboutMe時,JavaScript將在兩個陣列之間建立一個引用。這樣,任何對myName陣列的修改都會反映到aboutMe陣列中,反之亦然。
作為例子,我們同樣給myName陣列新增一個元素:
myName[0].push("real");
現在我們來檢視myName和aboutMe的值:
console.log(myName); // [["Sofela","real"]] console.log(aboutMe); // ["Oluwatobi","real"],"name."]
檢視執行結果
注意,對myName陣列的修改內容反映到了aboutMe陣列中,因為展開運算子在原始陣列和複製陣列之間建立了一個引用。
另外一個例子:
const myName = { firstName: "Oluwatobi",lastName: "Sofela" }; const bio = { ...myName }; myName.firstName = "Tobi"; console.log(myName); // { firstName: "Tobi",lastName: "Sofela" } console.log(bio); // { firstName: "Oluwatobi",lastName: "Sofela" }
檢視執行結果
上面的程式碼中,myName的更新內容沒有反映到bio物件中,因為我們在只包含原語值的物件上使用展開運算子。
注意,開發人員通常將這裡的myName稱之為淺物件,因為它只包含原語項。
另外一個例子:
const myName = { fullName: { firstName: "Oluwatobi",lastName: "Sofela" } }; const bio = { ...myName }; myName.fullName.firstName = "Tobi"; console.log(myName); // { fullName: { firstName: "Tobi",lastName: "Sofela" } } console.log(bio); // { fullName: { firstName: "Tobi",lastName: "Sofela" } }
檢視執行結果
上面的程式碼中,myName的更新內容被反映到bio物件中,因為我們在包含非原語值的物件上使用了展開運算子。
注意:
- 我們稱這裡的myName為深物件,因為它包含非原語項。
- 將一個物件克隆到另一個物件時,如果建立了引用,則進行的是淺拷貝。例如,...myName產生了myName物件的一個淺拷貝,因為你對其中一個物件所做的任何修改都會反映到另一個物件中。
- 將一個物件克隆到另一個物件時,如果沒有建立引用,則進行的時深拷貝。例如,我可以通過const bio = ON.parse(JSON.stringify(myName))將myName物件深拷貝到bio物件中。如此一來,JavaScript將把myName克隆到bio中而不建立任何引用。
- 我們可以通過用一個新物件來替換myName或bio中的fullName子物件,從而切斷myName和bio之間的引用。例如,使用myName.fullName = { firstName: "Tobi",lastName: "Sofela" }來斷開myName和bio之間的指標。
結語
本文討論了剩餘操作符和展開操作符之間的區別,並通過示例說明了它們在JavaScript中是如何工作的。
到此這篇關於JavaScript展開運算子和剩餘運算子的區別詳解的文章就介紹到這了,更多相關JavaScript展開運算子和剩餘運算子 內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!