[TOC]
## 3.7 函數
函數就是一段可以反復調用的**代碼塊**。
通過函數可以封裝任意多條語句,而且可以在任何地方、任何時候調用。
ECMAScript中的函數使用`function`關鍵字來聲明,后跟一組參數以及函數體,這些參數在函數體內像局部變量一樣工作。
~~~
function functionName(arg0, arg1....argN) {
statements
}
~~~
函數調用會為形參提供實參的值。函數使用它們實參的值來計算返回值,稱為該函數調用表達式的值。
~~~
function test(name){
return name;
}
test('tg');
~~~
在上面的例子中,name就是形參,調用時的'tg'就是實參。
還可以通過在函數內添加`return`語句來實現返回值。
注意:遇到return語句時,會立即退出函數,即return語句后面的語句不再執行。
~~~
function test(){
return 1;
alert(1); //永遠不會被執行
}
~~~
一個函數中可以包含多個return語句,而且return語句可以不帶有任何返回值,最終將返回undefined。
如果函數掛載在一個對象上,將作為對象的一個屬性,就稱它為對象的方法。
~~~
var o = {
test: function(){}
}
~~~
test()就是對象o的方法。
### 3.7.1 函數定義
JavaScript有**三種**方法,可以定義一個函數。
**1.function命令**
~~~
function name() {}
~~~
name是函數名稱標識符。函數名稱是函數聲明語句必需的部分。不過對于函數表達式來說,名稱是可選的:如果存在,該名字只存在于函數體內,并指向該函數對象本身。
圓括號:圓括號內可放置0個或多個用逗號隔開的標識符組成的列表,這些標識符就是函數的參數名稱。 花括號:可包含0條或多條JavaScript語句。這些語句構成了函數體。一旦調用函數,就會執行這些語句。
**2.函數表達式**
~~~
var f = function(x){
console.log(x);
}
~~~
采用函數表達式聲明函數時,function命令后面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。
**3.Function()**
Function()函數定義還可以通過Function()構造函數來定義
~~~
var f=new Function('x','y','return x+y');
//等價于
var f=function(x,y){
return x+y;
}
~~~
除了最后一個參數是函數體外,前面的其他參數都是函數的形參。如果函數不包含任何參數,只須給構造函數簡單的傳入一個字符串---函數體---即可。 不過,Function()構造函數在實際編程中很少會用到。
### 3.7.2 理解參數
ECMAScript函數不介意傳遞進來多少個參數,也不在乎傳進來參數的類型。原因是ECMAScript中的參數在內部是用**一個數組**來表示的。
**1.可選形參**
在ECMAScript中的函數在調用時,傳遞的參數可少于函數中的參數,沒有傳入參數的命名參數的值是**undefined**。
為了保持好的適應性,一般應當給參數賦予一個合理的默認值。
~~~
function go(x,y){
x = x || 1;
y = y || 2;
}
~~~
注意:當用這種可選實參來實現函數時,需要將可選實參放在實參列表的最后。那些調用你的函數的程序員是沒法省略第一個參數并傳入第二個實參的。
**2.實參對象**
當調用函數時,傳入的實參個數超過函數定義時的形參個數時,是沒有辦法直接獲得未命名值的引用。可以利用標識符`arguments`,其指向實參對象的引用,**實參對象是一個類數組對象**,可以通過數字下標來訪問傳入函數的實參值,而不用非要通過名字來得到實參。
~~~
function go(x){
console.log(arguments[0]);
console.log(arguments[1]);
}
go(1,2);
//1
//2
~~~
arguments有一個**length屬性**,用以標識其所包含**元素的個數**。
~~~
function f(x){
console.log(arguments.length);
}
f(1,2) // 2
~~~
通過實參名字來修改實參值的話,通過arguments[]數組也可以獲取到更改后的值。
arguments類數組中每一個元素的值會與對應的命名參數的值保持同步,這種影響是單向的,也可以這樣說,如果是修改arguments中的值,會影響到命名參數的值,但是修改命名參數的值,并不會改變arguments中對應的值。
~~~
function f(x){
console.log(x); // 1
arguments[0]=null;
console.log(x); // null
}
f(1);
~~~
在上面的例子中,arguments[0]和x指代同一個值,修改其中一個的值會影響到另一個。 注意:如果有同名的參數,則取最后出現的那個值。
**3.按值傳參**
ECMAScript中所有函數的參數都是**按值傳遞**的。也就是說,把函數外部的值復制給函數內部的參數,就和把值從一個變量復制到另一個變量一樣。
在向參數傳遞基本類型的值時,被傳遞的值會被復制給一個局部變量(即命名參數,或者用ECMAScript的概念來說,就是arguments對象中的一個元素。)
例子:
~~~
var num = 1;
function test(count){
count += 10;
return count;
}
var result = test(num);
console.log(num); // 1
console.log(result); // 11
~~~
在上面的例子中,我們將num作為參數傳給了test()函數,即count的值也是1,然后在函數內將count加10,但是由于傳遞的只是num的值的一個副本,并不會影響num,count和num是獨立的,所以最后num的值依舊是1.
在向參數傳遞引用類型的值時,會先把這個值在內存中的地址**復制給一個局部變量**,若局部變量變化,則局部變量和復制給局部變量路徑的全局變量也會發生改變。
~~~
function test(obj){
obj.name = 'tg';
}
var person = new Object();
test(person);
console.log(person.name); // "tg"
~~~
但是,如果局部變量指向了一個新的堆內地址,再改變局部變量的屬性時,不會影響全局變量。
看下面的例子:
~~~
function setName(obj) {
//相當于省略了var obj = person;
obj.name = ‘Nicholas’;
obj = new Object(); //重寫了obj的值,這時候obj與person引用的對象無關了。
obj.name = ‘Greg’;
}
var person = new Object();
setName(person);
alert(person.name); //‘Nicholas’
~~~
在上面的例子中,全局的person和函數內局部的obj在初始傳遞時,兩者指向的是內存中的同一個地址,但是,當在函數內創建了一個新的對象,并賦值給obj(賦值的是新對象的地址)。這個時候,obj指向的就不在是全局對象person,而是指向了新對象的地址,所以給obj添加屬性name時,全局對象person的屬性不會被改變。
對于上面的例子中的obj,也可以這樣說,一旦obj的值發生了變化,那么它就不再指向person在內存中的地址了。
另一個面試中常見的陷阱:
~~~
var foo = {
bar: function(){return this.baz;},
baz: 1};
(function(){return typeof arguments[0]();})(foo.bar); //undefined
重寫1
(function(){
return typeof arguments[0]();
})(function()return this.baz;);
重寫2
var a=function(){
return this.baz;
}
(function(){
return typeof arguments[0]();
}
)(a);
即
return typeof a();
~~~
arguments在函數內部,作為函數內部一個變量的屬性,調用了a,a沒有屬性baz,故結果為undefined
**4.將對象屬性用作實參**
當一個函數包含超過三個形參時,要記住調用函數中實參的正確順序是件讓人頭疼的事。不過,我們可以通過**名/值對**的形式傳入參數,這樣就不需要管參數的順序了。
~~~
function f(params){
console.log(params.name);
}
f({name:'a'})
~~~
### 3.7.3 沒有重載
* ECMAScript函數沒有重載的定義。
* 重載是指為一個函數編寫兩個定義,只要這兩個定義的簽名(接受的參數的類型和數量)不同即可。
* 如果在ECMAScript中定義了兩個名字相同的函數,則該名字只屬于后定義的函數。
- 前言
- 第一章 JavaScript簡介
- 第三章 基本概念
- 3.1-3.3 語法、關鍵字和變量
- 3.4 數據類型
- 3.5-3.6 操作符、流控制語句(暫略)
- 3.7函數
- 第四章 變量的值、作用域與內存問題
- 第五章 引用類型
- 5.1 Object類型
- 5.2 Array類型
- 5.3 Date類型
- 5.4 基本包裝類型
- 5.5 單體內置對象
- 第六章 面向對象的程序設計
- 6.1 理解對象
- 6.2 創建對象
- 6.3 繼承
- 第七章 函數
- 7.1 函數概述
- 7.2 閉包
- 7.3 私有變量
- 第八章 BOM
- 8.1 window對象
- 8.2 location對象
- 8.3 navigator、screen與history對象
- 第九章 DOM
- 9.1 節點層次
- 9.2 DOM操作技術
- 9.3 DOM擴展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件處理程序
- 10.3 事件對象
- 10.4 事件類型
- 第十一章 JSON
- 11.1-11.2 語法與序列化選項
- 第十二章 正則表達式
- 12.1 創建正則表達式
- 12.2-12.3 模式匹配與RegExp對象
- 第十三章 Ajax
- 13.1 XMLHttpRequest對象
- 你不知道的JavaScript
- 一、作用域與閉包
- 1.1 作用域
- 1.2 詞法作用域
- 1.3 函數作用域與塊作用域
- 1.4 提升
- 1.5 作用域閉包
- 二、this與對象原型
- 2.1 關于this
- 2.2 全面解析this
- 2.3 對象
- 2.4 混合對象“類”
- 2.5 原型
- 2.6 行為委托
- 三、類型與語法
- 3.1 類型
- 3.2 值
- 3.3 原生函數