[TOC]
# 基本概念
## 定義函數
在JavaScript中,定義函數的方式如下:
```js
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
```
上述`abs()`函數的定義如下:
* `function`指出這是一個函數定義;
* `abs`是函數的名稱;
* `(x)`括號內列出函數的參數,多個參數以`,`分隔;
* `{ ... }`之間的代碼是函數體,可以包含若干語句,甚至可以沒有任何語句。
請注意,函數體內部的語句在執行時,一旦執行到`return`時,函數就執行完畢,并將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有`return`語句,函數執行完畢后也會返回結果,只是結果為`undefined`。
由于JavaScript的函數也是一個對象,上述定義的`abs()`函數實際上是一個函數對象,而函數名`abs`可以視為指向該函數的變量。
因此,第二種定義函數的方式如下:
```js
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
```
在這種方式下,`function (x) { ... }`是一個匿名函數,它沒有函數名。但是,這個匿名函數賦值給了變量`abs`,所以,通過變量`abs`就可以調用該函數。
上述兩種定義完全等價,注意第二種方式按照完整語法需要在函數體末尾加一個`;`,表示賦值語句結束。
## 調用函數
調用函數時,按順序傳入參數即可:
~~~
abs(10); // 返回10
abs(-9); // 返回9
~~~
由于JavaScript允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,雖然函數內部并不需要這些參數:
~~~
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
~~~
傳入的參數比定義的少也沒有問題:
~~~
abs(); // 返回NaN
~~~
此時`abs(x)`函數的參數`x`將收到`undefined`,計算結果為`NaN`。
要避免收到`undefined`,可以對參數進行檢查:
~~~
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
~~~
# arguments對象
JavaScript還有一個免費贈送的關鍵字`arguments`,它只在函數內部起作用(箭頭函數除外),并且永遠指向當前函數的調用者傳入的所有參數。`arguments`類似`Array`但它不是一個`Array`:
~~~js
function sum() {
var sigma=0,
len = arguments.length;
for(var i=0; i<len; i++){
sigma+= arguments[i];
}
return sigma;
}
sum() // 0
sum(1) // 1
sum(1,2,3,4); // 10
~~~
> 利用`arguments`,你可以獲得調用者傳入的所有參數。也就是說,即使函數不定義任何參數,還是可以拿到參數的值。
實際上`arguments`最常用于判斷傳入參數的個數。你可能會看到這樣的寫法:
~~~js
// foo(a[, b], c)
// 接收2~3個參數,b是可選參數,如果只傳2個參數,b默認為null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 實際拿到的參數是a和b,c為undefined
c = b; // 把b賦給c
b = null; // b變為默認值
}
// ...
}
~~~
要把中間的參數`b`變為“可選”參數,就只能通過`arguments`判斷,然后重新調整參數并賦值。
# rest參數
由于JavaScript函數允許接收任意個參數,于是我們就不得不用`arguments`來獲取所有參數:
~~~js
function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
~~~
為了獲取除了已定義參數`a`、`b`之外的參數,我們不得不用`arguments`,并且循環要從索引`2`開始以便排除前兩個參數,這種寫法很別扭,只是為了獲得額外的`rest`參數,有沒有更好的方法?
ES6標準引入了rest參數,上面的函數可以改寫為:
~~~js
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 結果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 結果:
// a = 1
// b = undefined
// Array []
~~~
rest參數只能寫在最后,前面用`...`標識,從運行結果可知,傳入的參數先綁定`a`、`b`,多余的參數以數組形式交給變量`rest`,所以,不再需要`arguments`我們就獲取了全部參數。
如果傳入的參數連正常定義的參數都沒填滿,也不要緊,rest參數會接收一個空數組(注意不是`undefined`)。
因為rest參數是ES6新標準,所以你需要測試一下瀏覽器是否支持。
## 實踐
請用rest參數編寫一個`sum()`函數,接收任意個參數并返回它們的和:
~~~js
'use strict';
function sum(...rest) {
}
~~~
參考解決方案
```js
function sum(...rest) {
return rest.reduce((previous, current) => {
return previous + current;
});
}
console.log(sum(1, 2, 3));
// expected output: 6
console.log(sum(1, 2, 3, 4));
// expected output: 10
```
# 小心你的return語句
前面我們講到了JavaScript引擎有一個在行末自動添加分號的機制,這可能讓你栽到return語句的一個大坑:
~~~
function foo() {
return { name: 'foo' };
}
foo(); // { name: 'foo' }
~~~
如果把return語句拆成兩行:
~~~
function foo() {
return
{ name: 'foo' };
}
foo(); // undefined
~~~
> *要小心了*,由于JavaScript引擎在行末自動添加分號的機制,上面的代碼實際上變成了:
~~~
function foo() {
return; // 自動添加了分號,相當于return undefined;
{ name: 'foo' }; // 這行語句已經沒法執行到了
}
~~~
所以正確的多行寫法是:
~~~
function foo() {
return { // 這里不會自動加分號,因為{表示語句尚未結束
name: 'foo'
};
}
~~~
# 練習
定義一個計算圓面積的函數`area_of_circle()`,它有兩個參數:
* r: 表示圓的半徑;
* pi: 表示π的值,如果不傳,則默認3.14
``` js
function area_of_circle(r) {
return 0;
}
```
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- iView
- iView快速入門
- 課程講座
- 環境配置
- 第3周 Javascript快速入門