# 函數定義和調用
## 定義函數
在JavaScript中,定義函數的方式如下:
```
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
```
上述`abs()`函數的定義如下:
* `function`指出這是一個函數定義;
* `abs`是函數的名稱;
* `(x)`括號內列出函數的參數,多個參數以`,`分隔;
* `{ ... }`之間的代碼是函數體,可以包含若干語句,甚至可以沒有任何語句。
請注意,函數體內部的語句在執行時,一旦執行到`return`時,函數就執行完畢,并將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有`return`語句,函數執行完畢后也會返回結果,只是結果為`undefined`。
由于JavaScript的函數也是一個對象,上述定義的`abs()`函數實際上是一個函數對象,而函數名`abs`可以視為指向該函數的變量。
因此,第二種定義函數的方式如下:
```
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`:
```
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
```
利用`arguments`,你可以獲得調用者傳入的所有參數。也就是說,即使函數不定義任何參數,還是可以拿到參數的值:
```
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
```
實際上`arguments`最常用于判斷傳入參數的個數。你可能會看到這樣的寫法:
```
// 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`來獲取所有參數:
```
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參數,上面的函數可以改寫為:
```
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()`函數,接收任意個參數并返回它們的和:
```
'use strict';
function sum(...rest) {
???
}
// 測試:
var i, args = [];
for (i=1; i<=100; i++) {
args.push(i);
}
if (sum() !== 0) {
alert('測試失敗: sum() = ' + sum());
} else if (sum(1) !== 1) {
alert('測試失敗: sum(1) = ' + sum(1));
} else if (sum(2, 3) !== 5) {
alert('測試失敗: sum(2, 3) = ' + sum(2, 3));
} else if (sum.apply(null, args) !== 5050) {
alert('測試失敗: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
} else {
alert('測試通過!');
}
```
## 小心你的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
```
'use strict';
function area_of_circle(r, pi) {
return 0;
}
// 測試:
if (area_of_circle(2) === 12.56 && area_of_circle(2, 3.1416) === 12.5664) {
alert('測試通過');
} else {
alert('測試失敗');
}
```
Max是一個JavaScript新手,他寫了一個`max()`函數,返回兩個數中較大的那個:
```
'use strict';
function max(a, b) {
if (a > b) {
return
a;
} else {
return
b;
}
}
alert(max(15, 20));
```
但是Max抱怨他的瀏覽器出問題了,無論傳入什么數,`max()`函數總是返回`undefined`。請幫他指出問題并修復。
- JavaScript教程
- JavaScript簡介
- 快速入門
- 基本語法
- 數據類型和變量
- 字符串
- 數組
- 對象
- 條件判斷
- 循環
- Map和Set
- iterable
- 函數
- 函數定義和調用
- 變量作用域
- 方法
- 高階函數
- map/reduce
- filter
- sort
- 閉包
- 箭頭函數
- generator
- 標準對象
- Date
- RegExp
- JSON
- 面向對象編程
- 創建對象
- 原型繼承
- 瀏覽器
- 瀏覽器對象
- 操作DOM
- 更新DOM
- 插入DOM
- 刪除DOM
- 操作表單
- 操作文件
- AJAX
- Promise
- Canvas
- jQuery
- 選擇器
- 層級選擇器
- 查找和過濾
- 操作DOM
- 修改DOM結構
- 事件
- 動畫
- 擴展
- underscore
- Collections
- Arrays
- Functions
- Objects
- Chaining
- Node.js
- 安裝Node.js和npm
- 第一個Node程序
- 模塊
- 基本模塊
- fs
- stream
- http
- buffer
- Web開發
- koa
- mysql
- swig
- 自動化工具
- 期末總結
- Python 2.7教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷和循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 使用__future__
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- 常用第三方模塊
- PIL
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 協程
- gevent
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫數據庫模塊
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 添加配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- 期末總結
- Python3教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- Python代碼運行助手
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷
- 循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 實例屬性和類屬性
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用枚舉類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- StringIO和BytesIO
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模塊
- PIL
- virtualenv
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 異步IO
- 協程
- asyncio
- async/await
- aiohttp
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫Web App骨架
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 編寫配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- FAQ
- 期末總結
- Git教程
- Git簡介
- Git的誕生
- 集中式vs分布式
- 安裝Git
- 創建版本庫
- 時光機穿梭
- 版本回退
- 工作區和暫存區
- 管理修改
- 撤銷修改
- 刪除文件
- 遠程倉庫
- 添加遠程庫
- 從遠程庫克隆
- 分支管理
- 創建與合并分支
- 解決沖突
- 分支管理策略
- Bug分支
- Feature分支
- 多人協作
- 標簽管理
- 創建標簽
- 操作標簽
- 使用GitHub
- 自定義Git
- 忽略特殊文件
- 配置別名
- 搭建Git服務器
- 期末總結