一:定義函式的方式:
1、函式宣告;2、函式表示式
函式宣告的重要特徵:函式宣告提升,在執行程式碼之前會先讀取函式宣告。
sayHi();
function sayHi(){
console.log("Hi");
}
複製程式碼
函式表示式必須先賦值。
二:遞迴
function fact(num){
if(num <= 1){
return 1;
}else{
return num*fact(num-1);
}
}
複製程式碼
下面程式碼可能導致出錯
var another = fact;
fact = null;
console.log(another(4)); //出錯
複製程式碼
使用arguments.callee可以解決這個問題,arguments.callee是一個指向正在執行的函式的指標。
function fact(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
複製程式碼
在嚴格模式下,不能通過arguments.callee訪問,建立一個名為f()的函式,將它賦值給fact。
var fact = (function f(num){
if(num <= 1){
return 1;
}else{
return num * f(num-1);
}
});
複製程式碼
三、閉包
閉包是指有權訪問另一個函式作用域中的變數的函式。當某個函式被呼叫時,會建立一個執行環境及相應的作用域鏈。然後,使用arguments和其他命名引數的值來初始化函式的活動物件。
一般來說,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅儲存全域性作用域(全域性執行環境的變數物件)。但是,閉包的情況有所不同。
function create(name){
return function(){
return name;
}
}
複製程式碼
create()函式在執行完畢後,其活動物件不會被銷燬,因為匿名函式的作用域鏈仍然在引用這個活動物件。當create()函式返回後,其執行環境的作用域鏈會被銷燬,但它的活動物件仍然會留在記憶體中;直到匿名函式被銷燬後,create()活動物件才會被銷燬。
1、閉包和變數
作用域鏈的這種配置,使閉包只能取得包含函式中任何變數的最後一個值。閉包儲存的是整個變數物件,而不是某個特殊的變數。
function create(){
var result = new Array();
for(var i =0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
}
複製程式碼
陣列10個10,每個函式的作用域都儲存著create()函式的活動物件,所以引用的都是同一個變數i。
建立一個匿名函式強制讓閉包的行為符合預期:
function create(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
複製程式碼
這個函式中,沒有直接把閉包賦值給陣列,而是定義了一個匿名函式,並將立即執行該匿名函式的結果賦給陣列。這裡的匿名函式有一個引數num,也就是最終函式要返回的值。在呼叫每個匿名函式時,我們傳入了變數i。由於函式引數是按值傳遞的,所以就會將變數i的當前值複製給引數num。而在這個匿名函式內部,又建立並返回一個訪問num的閉包。這樣一來,result陣列中的每個函式都有num變數的一個副本,因此就可以返回各自不同的數值了。
2、this物件
this物件是在執行時基於函式的執行環境繫結的:在全域性函式中,this等於window,函式被當作某個物件的方法呼叫時,this等於那個物件。匿名函式的執行環境具有全域性性,因此this通常指向window。
建構函式當作普通函式呼叫時,this代表的是全域性window物件。和new使用建立物件,指向當前的物件。
var name = 'this window';
var object = {
name: 'my object',
getName: function(){
return function(){
return this.name;
};
}
};
alert(object.getName()()); //"this window"
複製程式碼
由於getName()返回一個函式,因此呼叫object.getName()()就會立即呼叫它返回的函式,結果就是返回一個字串。
3、記憶體洩漏
function assign(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
複製程式碼
以上程式碼建立一個作為element元素事件處理程式的閉包,而這個閉包又建立了一個迴圈引用。由於匿名函式儲存了一個對assign()活動物件的引用,因此會導致無法減少element的引用數。只要匿名函式存在,element的引用數至少也是1,因此它佔用的記憶體就永遠不會被回收。
可通過以下解決:
function assign(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
複製程式碼
閉包會引用包含函式的整個活動物件,而其中包含著element。即使不直接引用element,包含函式的活動物件中也仍然會儲存一個引用。
四、模仿塊級作用域
JS沒有塊級作用域的概念。在塊語句中定義的變數,實際是在包含函式中而非語句中建立的。
function output(count){
for(var i=0;i<count;i++){
alert(i);
}
var i; //重新宣告變數,視而不見
alert(i);
}
複製程式碼
在java、c++語言中,變數i只會在for迴圈的語句中有定義,迴圈一旦結束,變數i就會被銷燬。在JS中,變數i定義在output()的活動物件中,從它又定義開始就可以在函式內部訪問它。
用作塊級作用域的(私有作用域)的匿名函式的語法:
(function(){
//這裡是塊級作用域
})();
複製程式碼
重寫output()函式:
function output(count){
(function(){
for(var i=0;i<count;i++){
alert(i);
}
})();
alert(i); //導致一個錯誤
}
複製程式碼
五、私有變數