[toc]
### 參數默認值
#### 使用方法
在書寫參數時,直接給形參賦值,賦的值即為該參數的默認值。當調用函數時,如果沒有給對應的參數賦值(給的值是undefined),則會自動使用默認值。
```js
function sum?(n1,?n2 = 10,?n3 = 5){
return n1+n2+n3;
}
sum(10, 20, 30); //三個參數都給的話按正常參數計算,返回60
sum(20); //只給第一個參數,后兩個參數取默認值,返回35
sum(10,?undefined,?10); //第一個和第三個參數有值,第二個沒賦值取默認值,返回30
sum(); //一個參數都沒給,因為n1沒有默認值,相當于undefined+10+5,返回NaN
```
>參數默認值可以為各類型的值和表達式,例如
```js
function getEle?(name,?container = document.getElementById('mydiv'),?content){
//執行代碼塊
}
```
#### [擴展\]對arguments的影響
在正常模式下,arguments和形參是同步的,即在函數體中,改變形參的值,arguments對應的值也會改變。
在嚴格模式下(use?strict),arguments和形參是脫離的,即在函數體中,改變形參的值,arguments對應的值是不變的。
無論在哪種模式下,只要給參數加了默認值,那么該函數就會自動按照嚴格模式的規則來執行,即arguments與形參脫離關系。
#### [擴展\]留意暫時性死區
形參和ES6中的let和const聲明一樣,具有作用域,并且根據參數的聲明順序,存在暫時性死區。
```js
function test?(a,?b = a){
console.log(a,?b);
}
test(1); //執行該代碼可以正常輸出1?1,因為b取默認值的時候a已經有值了。
function test?(a = b?,?b){
console.log(a,?b);
}
test(undefined,?3); //執行該代碼會報錯,因為a的默認值為b,此時b還沒有被賦值,還在暫時性死區內,所以會報錯。
```
>在ES6模式下,函數體內不能再聲明與參數同名的變量,這樣也會報錯。其原因都是因為參數作用域的關系。
### 剩余參數
在日常編寫函數的過程中,調用函數時往往形參個數不固定,無論調用者設置幾個參數都可以執行。此時通常使用arguments來獲取用戶輸入的所有參數。
但arguments存在一些缺陷:
1.?如果跟形參配合使用,根據是否為嚴格模式及參數值被改變的關系,容易造成混亂。
2.?從語義上來說,使用arguments獲取參數,由于形參缺失,無法從函數定義上理解函數的真實意圖。
為解決上面的問題,ES6制定了剩余參數的概念。專門用于收集末尾的所有參數,并將其放到一個形參數組中。
剩余參數的用法是在參數名前面加上'...',用法如下:
```js
function test(...value){ //value即是定義的剩余參數
console.log(value)
}
test(1); //輸出結果[1]
test(1,3); //輸出結果[1,3]
test(1,3,4,5); //輸出結果[1,?3,?4,?5]
```
>使用剩余參數,無論輸入多少個參數,都會將其整合為一個數組。是解決參數不固定,替代arguments的一個完美方案。
剩余參數的使用注意事項:
1.?一個函數,在形參中只能定義一個剩余參數。
2.?剩余參數必須是該函數最后一個參數。
### 展開運算符
在日常開發中,由于參數的數量不確定,我們會使用剩余參數來解決這個問題。但如果給的參數本身就是一個數組或對象,剩余參數就會出現數組套數組或數組套對象的情況。這種情況在函數體中是很難操作的。我們想要的是數組或對象中的每一項分別做為參數給這個函數。從ES6開始為我們提供了展開運算符的方法來解決這個問題
使用方法,在要展開的參數名前面加'...',例如:
```js
let arr=?[1,2,3,4,5,6];
test(...arr); //此時將arr數組中的每一個值分別做為參數。相當于test(1,2,3,4,5,6)
```
>加了展開運算符的參數,可以在函數參數列表的任何位置出現。
#### 針對數組展開(ES6中提供的方法)
展開運算符可以實現只有一層的數組深拷貝功能。
```js
let arr1=?[1,2,3,4];
let arr2=?[...arr1]; //一行代碼就完成了arr2深度克隆arr1,兩個數組值一樣,但互相不關聯。
let arr3=?[1,3,...arr1,4,6]; //也可以任何位置插入其它的值。
```
#### 針對對象展開(ES7中提供的方法)
```js
let obj1 =?{name:'wang',?age:30};
let obj2 =?{...obj1}; //同數組一樣,也可以深拷貝只有一層的對像,兩個對像值一樣,但互相不影響。
let obj3 =?{...obj1,?age:28}; //利用該方法可以實現復制原有對像的值再將需要改變的值重新賦值。
```
**注意:**
>展開運算符實際來講只能算是淺拷貝,只能支持只有一層數據的數組或對象深拷貝。如果對象中還包含一個對象,那么被包含的對象是無法被深拷貝的。
### 函數的雙重用途
當創建一個函數后,調用該函數有兩種方式,一種是使用new來創建一個新的函數,另一種是直接調用該函數。
在ES6之前,函數體內部是無法判斷該函數是否采用new的方式來調用的。有時就會因為this的指向問題導致報錯。
在ES6中為我們提供了一個特殊的API(new.target),可以使用該API在函數內部,判斷該函數是否使用了new來調用。
new.target使用該表達式返回的結果是:如果沒有使用new來調用函數,則返回undefined。如果使用new來調用,則返回的是new后面的函數體。
利用該API,就可以在函數體內判定該函數是否采用new的方式來調用的:
```js
//定義一個構造函數Test
function Test(...args){
if?(new.target === undefined){
//返回報錯信息并退出函數
}
//正常執行的函數體
}
//使用該方法就可以限制該函數只能使用new來調用。
```
### 箭頭函數
#### 回顧this指向問題
ES6中箭頭函數的出現,可以解決一部份this指向的問題。
首先,回顧一下this指向的幾種情況:
1.?通過對象調用函數,this指向對象
2.?直接調用函數,this指向全局對象
3.?如果通過new調用函數,this指向新創建的對象
4.?如果通過apply、call、bind調用的函數,this指向該方法指向的數據
5.?如果是DOM事件函數,this指向事件源
#### 箭頭函數的使用語法
ES6中箭頭函數的出現,可以非常好的解決this指向的問題。
1.?箭頭函數是一個函數表達式,理論上,任何使用函數表達式的場景都可以使用箭頭函數。
```js
//什么是函數表達式?
const test = function(a,?b){
//函數體內容
};
//上面的例子中,等號后面的所有內容叫做函數表達式。
function abc(){
//函數體內容
}
//函數abc叫做函數聲明,不是函數表達式
```
2.?完整的箭頭函數語法
```js
????(參數1,?參數2,?...args) => {
//函數體內容
????}
```
3.?如果聲明的箭頭函數參數只有一個,可以省略小括號
```js
參數 => {
//函數體
}
```
>該寫法只適用于只有一個參數的情況,如果有多個參數和沒有參數均不能用這種寫法。
4.?如果箭頭函數的函數體,只有一條返回語句,可以省略大括號和return關鍵字。
```js
參數 => 返回結果的表達式;
```
5.?如果箭頭函數只有一條返回語句,但返回的是對象,需在返回對象的大括號外面加一個()包起來。否則系統會認為對象的{}是函數體,會報語法錯誤。
```js
let sum =?(a,?b)?=>?({
????????a:a,
????????b:b,
????????sum:a+b
????})
//用()包裹起來,就代表{}中是一個表達式,而不是函數體。
```
#### 箭頭函數的注意細節
1.?箭頭函數函數體中的this,取決于箭頭函數定義的位置的this指向,而與如何調用無關。
2.?箭頭函數中,不存在this、arguments、new.target。如果使用了,則使用的是函數外層對應的this、arguments、new.target。
3.?箭頭函數沒有原型,因此箭頭函數不能作為構造函數使用。
#### 箭頭函數的適用場景
1.?臨時性使用的函數,并不會刻意調用它,例如:
????1.?事件處理函數
????2.?異步處理函數
????3.?其它臨時性函數
2.?為了綁定外層this的函數
3.?在不影響其它代碼的情況下,保持代碼的整清。例如常使用的:數組方法中的回調函數