JavaScript之閉包與高階函數(一)

點擊此處訪問我的github了解更多詳情

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()被調用時的上下文環境(執行環境),局部變量即在其中,局部變量所處環境能被外界訪問,局部變量就不會被銷毀。

閉包的作用

  1. 封裝變量

閉包可以封裝形成‘私有變量‘,如:實現計算乘積:


    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));
  1. 模仿塊級作用域

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() {
        //塊級作用域
    })()
  1. 添加私有變量或函數

通過在私有作用域定義私有變量或函數,可以形成私有成員,如:


    (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中的高階函數。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 作用域和閉包是 JavaScript 最重要的概念之一,想要進一步學習 JavaScript,就必須理解 Java...
    劼哥stone閱讀 1,196評論 1 13
  • 閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。 一、變量...
    zock閱讀 1,084評論 2 6
  • 閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。 一、變量...
    zouCode閱讀 1,284評論 0 13
  • 我曾夢見獨自一人傲游城市與山谷, 也曾夢見三五成群譚天說地游戲人生 亦曾夢見奇形怪獸肆虐人間 現始終希望每晚有夢 ...
    生如夏花丶閱讀 202評論 1 0
  • 《親婆》的故事,就像開篇作者說的:親婆是個很普通的老人,記憶里的故事和場景,也都平平常常。我想,人間的親情,大概就...
    祺妙媽咪閱讀 3,751評論 0 6