[TOC]
* * * * *
> 定義:
函數是 JavaScript 中的基本組件之一。 一個函數是 JavaScript 過程 :一組執行任務的語句。
### 1. 定義函數
~~~
定義一個函數并不會自動的執行它。
定義了函數僅僅是賦予函數以名稱并明確函數被調用時該做些什么。
調用函數才會以給定的參數真正執行這些動作。
function f1(arg1,arg2) {}
var a1=function f1(arg1,arg2) {}
var a2=function (arg1,arg2) {}
f1.name=>'f1';
a1.name=>'f1';
a2.name=>'a2';
~~~
* * * * *
### 2. 參數
>[danger]原始值參數(比如一個具體的數字)被作為值傳遞給函數;值被傳遞給函數,如果被調用函數改變了這個參數的值,這樣的改變不會影響到全局或調用函數。
>[danger]如果你傳遞一個對象(即一個非原始值,例如Array或用戶自定義的對象)作為參數,而函數改變了這個對象的屬性,這樣的改變對函數外部是可見的。
~~~
在函數體內,使用arguments對象獲取類數組對象的參數列表, arguments[0] 就表示第一個傳進來的參數,如果在函數體內修改arguments[0],同步的表示第一個參數的變量值也會變,并不是說他們引用相同的內存,而是值會同步;
arguments變量只是類數組對象,并不是一個數組。
稱其為類數組對象是說它有一個索引編號和 length (得到參數的個數)屬性。
盡管如此,它并不擁有全部的Array對象的操作方法。
~~~
~~~
函數的參數,如果沒傳(由于可傳可不傳),或者不是需要的類型,
為了程序的健壯
在ES5中針對這種情況就要做判斷,專門給參數賦默認值:
function multiply(a, b) {
b = (typeof b !== 'undefined') ? b : 1;
return a*b;
}
multiply(5); // 5
在ES6中對這種操作做了規范,更容易:
function multiply(a, b = 1) {
return a*b;}
multiply(5); // 5
~~~
* * * * *
### 3. 函數作用域
一個函數可以訪問在其所在定義域內的所有變量和函數;
如果一個函數定義在全局定義域內,那么它就可以訪問全局作用域內的所有變量和函數;
如果一個函數定義在另外一個函數內,那么它就可以訪問父函數內的所有變量和函數;
函數中的 `this` 引用的是函數據以執行的環境對象
* * * * *
### 4. 返回值
函數總是有返回值得


* * * * *
### 5. 遞歸調用
~~~
一個函數可以指向并調用自身。有三種方法可以達到這個目的:
1. 函數名
2. arguments.callee
3. 作用域下的一個指向該函數的變量名
比如:
var a=function f3(x) {
/*
那么在函數體內,a(y), f3(y),arguments.callee(y) 是相等的,
都是以y為參數,執行函數
*/
}
~~~
當然,在函數體內調用自身的函數就是遞歸,為了避免無限循環,我們就需要設定執行條件。
分析下一個遞歸調用

~~~
將程序的執行過程寫出來,就像順序執行代碼:
if(3<0) return;
console.log('begin:', 3);
{
if(2<0) return;
console.log('begin:', 2);
{
if(1<0) return;
console.log('begin:', 1);
{
if(0<0) return;
console.log('begin:', 0);
{
if(-1<0) return; //這一條件符合,跳出遞歸
}
console.log('end:', 0);
}
console.log('end:', 1);
}
console.log('end:', 2);
}
console.log('end:', 3);
~~~
* * * * *
### 6. 函數的閉包
可以在一個函數里面嵌套另外一個函數。嵌套(內部)函數對其容器(外部)函數是私有的。它自身也形成了一個閉包。一個閉包是一個可以自己擁有獨立的環境與變量的的表達式(通常是函數)。
既然嵌套函數是一個閉包,就意味著一個嵌套函數可以”繼承“容器函數的參數和變量。換句話說,內部函數包含外部函數的作用域。
~~~
總結:
1. 內部函數只可以在外部函數中訪問。
2.內部函數形成了一個閉包:它可以訪問外部函數的參數和變量,但是外部函數卻不能使用它的參數和變量。
~~~
* * * * *
### 7. 箭頭函數:
箭頭函數表達式(也稱胖箭頭函數)相比函數表達式具有較短的語法并以詞法的方式綁定 this。箭頭函數總是匿名的。
優點:
有兩個因素會影響引入箭頭函數:更簡潔的函數和 this。
~~~
setTimeout/setInterval(function() {
//這里的this自動綁定到頂層作用域
});
function f1() {
setTimeout(function() {
console.log(this.name);
});
}
function f2() {
setTimeout(()=>{
console.log(this.age);
});
}
window.name='global';
window.age=25;
var o={
name: 'local',
age: 26,
showName: f1,
showAge: f2
};
o.showName()
'global'
o.showAge()
26
~~~
* * * * *
### 8. 參數和函數內部定義的函數名相同的情況

### 9. 嚴格模式
~~~
當函數的執行作用域為全局作用域:
1. 非嚴格模式下 , this 默認指向window;
2. 嚴格模式下,this 為 undefined;
~~~

* * * * *
### 10. 高階函數
> `Higher-order function`
> 兩個主要特征:
> 1.函數可以作為參數被傳遞
> 2.函數可以作為返回值輸出
>[info] 題 1:設計一個函數,輸入索引就可以得到值
|index|0|1|2|3|4|5|
| --- | --- | --- | --- | --- | --- | --- |
| value |1|1|2|3|5|8|
~~~
使用遞歸方法
function f1(n) {
if(n<2) return 1;
else {
return f1(n-2)+f1(n-1)
//這里為了減少耦合可用 arguments.callee來代替f1
}
}
但是這樣存在性能問題,所以我們需要緩存計算值
function f2(f,n) {
var result={};
if(result[n]===undefined) {
result[n]=f(n);
}
return result[n];
}
這里就把把函數當做參數傳遞了進去
~~~
>[info]題 2:構造一個簡單的閉包(返回值為函數)
~~~
function f3(arg1) {
var o={
name: arg1
};
return function() {
return o.name;
}
}
var a=f3('wdd'); //此時a是函數引用
a(); //輸出 'wdd'
~~~