JavaScript雖是一門面向對象的編程語言,但同時也有許多函數式編程的特性,如Lambda表達式,閉包,高階函數等。
函數式編程是種編程范式,它將電腦運算視為函數的計算。函數編程語言最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(參數)和輸出(返回值
閉包
何謂閉包?對于閉包眾位各有己見,今我試說之,閉包,常指有權訪問其外部作用域中變量和參數的函數。最常見的就是在某函數內部創建另一個函數。如:
var count = (function() {
var item = 0;
return {
add: function(num) {
item += typeof num === 'number' ? num : 1;
},
value: function() {
return item;
}
}
})();
此處把函數返回的結果賦值給count,該函數返回一個包含兩個方法的對象,對象中的方法均可訪問其包含函數中的變量及參數。count中保存的是該對象的一個引用,對象中的方法依然可以訪問自執行函數中的變量,而且訪問的是變量本身。
閉包 函數可以訪問它創建時所處的上下文環境中的變量以及參數,this以及arguments除外。
閉包其實并不是很好闡述,與我而言,自我理解與向他人闡述差別甚大,但也要試著去征服它。閉包的形成與變量息息相關,尤其是變量的作用以及變量生命周期,請看細說:
閉包與變量
閉包中所保存的是整個變量對象--執行環境(上下文環境)中的一個表示變量的對象的引用,訪問執行環境中變量即是訪問該變量對象中的變量。
變量對象 每個執行環境(上下文環境)中的一個表示所有變量的對象,全局環境的變量對象始終存在,而局部環境的變量對象只在其執行過程中存在。
典型案例如下:
function myNumber() {
var count = [];
for (var i = 0; i < 10; i ++) {
count[i] = function() {
return i;
}
}
return count;
}
這個函數會返回一個函數數組,這個數組會不會乖乖返回自己的數字呢?當然不會,事實上,每個函數都返回10。為什么呢?細細道來,因為每個函數的作用域鏈中都保存著myNumber()函數的活動對象(變量對象),他們都引用同一個變量對象,當然也引用同一個變量i,當myNumber()函數返回后i為10。
再看如下代碼:
function myNumber() {
var count = [];
for (var i = 0; i < 10; i ++) {
count[i] = (function(num) {
return function() {
return num;
};
})(i);
}
return count;
}
在此將自執行匿名函數結果賦值給數組,調用每個匿名函數時,傳入變量i,而函數參數是按值傳遞,即將變量值復制給參數num,在此匿名函數內部又創建并返回了一個訪問num參數的閉包,count數組中的函數均保存有自己的num變量的副本,于是,便返回各自的值了。
變量的作用域
變量分全局變量與局部變量,在函數中聲明變量時,以var關鍵字定義的變量即是局部變量,而不帶var關鍵字的就變成全局變量。
var c = 3
var func = function() {
var a = 1;
b = 2;
alert(b);//2
alert(c);//3
}
func();
alert(b);//2
alert(a);//Uncaught ReferenceError: b is not defined
我們知道,在函數中查找變量時,首先在當前函數執行環境作用域查找,若未找到,則隨當前執行環境創建的作用域鏈往外層查找,直到全局對象為止,這里的查找是從內向外查找的
變量的生命周期
上面說到變量作用域,這里談談變量生命周期:
- 全局變量,其生命周期在整個程序運行時間內永久存在,除非主動銷毀,否則可以隨時調用。
- 局部變量, 其在所屬作用域代碼執行過程中存在,當運行完成,且不存在外部調用此上下文環境中的變量時,即被銷毀,否則依然存在。
var func = function() {
var res = [1,2,3,4,5,6];
var a = 0;
return function() {
alert(res[a]);
a++;
}
};
var f = func();
func()();//1
func()();//1
f();//1
f();//2
f();//3
試比較執行fun()()與f()的彈出值,是不一樣的,貌似在f()中a一直存在。當執行var f = func();時,f函數返回的是一個匿名函數的引用,此匿名函數可以訪問func()被調用時的上下文環境(執行環境),局部變量即在其中,局部變量所處環境能被外界訪問,局部變量就不會被銷毀。
閉包的作用
- 封裝變量
閉包可以封裝形成‘私有變量‘,如:實現計算乘積:
var mult = function() {
var a = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
alert(mult(1,2,3,4));
- 模仿塊級作用域
JavaScript中是沒有塊級作用域的概念的,如:
function block() {
var res = [1,3,5,7,9];
for (var i = 0; i < res.length; i++) {
alert(res[i]);
}
var i;//重新聲明變量
alert(i);//5
}
如上代碼所見,i變量定義后在整個包含函數中均可訪問。JavaScript中for語句并不會形成塊級作用域,其整個作用域是包含函數創建的,而且對變量的后續聲明都將被忽略。
要達到塊級作用域效果,我們可以形成閉包來模仿之,如:
(function() {
//塊級作用域
})()
- 添加私有變量或函數
通過在私有作用域定義私有變量或函數,可以形成私有成員,如:
(function() {
var name = 'xjg';
function getName() {
reutn name;
}
Person = function(val) {
name = val;
}
Person.getName = function() {
return name;
}
})();
var p1 = new Person('Anagle');
alert(p1.getName());//Anagle
alert(getName())//ReferenceError: getName is not defined
此處,name就變成了一個靜態私有變量。
閉包與內存泄漏
局部變量本來在函數退出時被銷毀,然而閉包中不是這樣,局部變量生命周期被延長,閉包將使這些數據無法及時銷毀,會占用內存,容易造成內存泄漏。如:
function addHandle() {
var element = document.getElementById('myNode');
element.onclick = function() {
alert(element.id);
}
}
此處,onclick匿名函數保存了一個對包含函數活動對象(變量對象)的引用,其保存element的引用,element將不會被回收。
function addHandle() {
var element = document.getElementById('myNode');
var id = element.id;
element.onclick = function() {
alert(id);
}
element = null;
}
此處將element設為null,即解除對其的引用,垃圾回收器將回收其占用內存。
此篇對JavaScript閉包做了總結,闡述,限于篇幅,在下篇講述JavaScript中的高階函數。