[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. 在不影响其它代码的情况下,保持代码的整清。例如常使用的:数组方法中的回调函数