(三)閉包和高階函數

来源:http://www.cnblogs.com/ahthw/archive/2016/01/09/5117570.html
-Advertisement-
Play Games

雖然javascript是一門面向對象的編程語言,但這門語言同時也同時擁有許多函數式語言的特性。函數式語言的鼻祖是LISP,javascript設計之初參考了LISP兩大方言之一的Schenme,引入了Lambda表達式,閉包,高階函數等特性。使用這些特性,我們就可以靈活的編寫javascript代...


雖然javascript是一門面向對象的編程語言,但這門語言同時也同時擁有許多函數式語言的特性。

函數式語言的鼻祖是LISP,javascript設計之初參考了LISP兩大方言之一的Schenme,引入了Lambda表達式,閉包,高階函數等特性。使用這些特性,我們就可以靈活的編寫javascript代碼。

一:閉包

對於javascript程式員來說,閉包(closure)是一個難懂又必須征服的概念。閉包的形成與變數作用域以及變數的聲明周期密切相關。

1.變數作用域

變數的作用域就是指變數的有效範圍,我們最常談到的是在函數中聲明的變數作用域。

當在函數中聲明一個變數時,如果沒有使用var關鍵字,這個變數就會變成全局變數(當然這是一種容易造成命名衝突的做法。)

另外一種情況是用var關鍵字在函數中聲明變數,這時候的變數即局部變數,只有在函數內部才能訪問到這變數,在函數外面是訪問不到的,代碼如下:

var func = function() {
    var a = 1;
    console.log(a)
}
func()
console.log(a);//Uncaught ReferenceError: a is not defined

下麵這段包含了嵌套函數的代碼,也許能幫助我們加深對遍歷搜索過程中的理解

var a = 1;
var func = function() {
    var b = 2;
    var func2 = function(){
        var c = 3;
        console.log(b);
        console.log(a)
    }
    func2()
    console.log(c) //Uncaught ReferenceError: c is not defined
}
func()

2.變數的生成周期

var func = function(){
    var a =1;
    console.log(a) //退出函數後局部變數a將銷毀
}
func()
var func2 = function(){
    var a = 2;
    return function() {
        a++;
        console.log(a)
    }
}
var f = func2();
f() //3
f() //4
f() //5
f() //6

func2根我們之前的推論相反,當退出函數後,局部變數a並沒有消失,而是停留在某個地方。這是因為,當執行 var f = func2()時,f返回了一個匿名函數的引用,它可以訪問到func()被調用時的所產生的環境,而局部變數a一直處在這個環境里。既然局部變數所在的環境還能被外界訪問,這個局部的變數就有了不被銷毀的理由。在這裡產生了一個閉包環境,局部變數看起來被延續了。

利用閉包我們可以完成很多奇妙的工作,下麵介紹一個閉包的經典應用。

假設頁面上有5個div節點,我們通過迴圈給div綁定onclick,按照索引順序,點擊第一個時彈出0,第二個輸出2,依次類推。

<div>div1</div>
<div>div2</div>
<div>div3</div>
<div>div4</div>
<div>div5</div>
<div>div6</div>

<script type="text/javascript">
var nodes = document.getElementsByTagName('div')
console.log(nodes.length)
for (var i = 0; i < nodes.length; i++) {
    nodes[i].onclick = function() {
        console.log(i)
    }
}
</script>

在這種情況下,發現無論點擊那個div都輸出6,這是因為div節點的onclick是被非同步觸發的,當事件被觸發的時候,for迴圈早已經結束,此時的變數i已經是6。

解決的辦法是,在閉包的幫助下,把每次迴圈的i都封閉起來,當事件函數順著作用域鏈中從內到外查找變數i時,會先找到被封閉在閉包環境中的i,如果有6個div,這裡的i就是0,1,2,3,4,5

var nodes = document.getElementsByTagName('div')
for (var i = 0; i < nodes.length; i++) {
    (function(i){
        nodes[i].onclick = function(){
            console.log(i+1)
        }
    })(i)
}

根據同樣的道理,我們還可以編寫如下一段代碼

var Type = {};

for (var i = 0 , type; type = ['String','Array','Number'][i++];){
    (function ( type ){
        Type['is' + type] = function( obj ) {
            return Object.prototype.toString.call( obj ) === '[object '+ type +']'
        }
    })( type )
}

console.log( Type.isArray([]) ) //true
console.log( Type.isString('') )//true

3.閉包的更多的作用

在實際開發中,閉包的運用十分廣泛

(1)封裝變數

閉包可以幫助把一些不需要暴露在全局的變數封裝成“私有變數”,假設一個計算乘積的簡單函數。

    var mult = function(){
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i]
        }
        return a
    }

    console.log(mult(10,2,4)) //80

mult函數每次都接受一些number類型的參數,並返回這些參數的乘積,現在我們覺得對於那些相同的參數來說,每次都進行一次計算是一種浪費,我們可以加入緩存機制來提高這個函數的性能。

var cache = {};

var mult = function(){
    var args = Array.prototype.join.call( arguments, ',' );
    if (cache[ args ]) {
        return cache[ args ]
    }

    var a = 1;
    for ( var i = 0, l = arguments.length; i<l;i++ ) {
        a = a * arguments[i]
    }

    return cache[ args ] = a;
}

console.log(mult(10,2,4)) //80

看到cache這個變數僅僅在mult函數中被使用,與其讓cache變數跟mult函數一起暴露在全局作用域下,不如將它封裝在mult內部,這樣可以減少頁面的全局變數,以避免在其它地方不小心修改而引發錯誤。

var mult = (function(){
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ',' );
        if (args in cache){
            return cache[ args ]
        }

        var a = 1;
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            a = a * arguments[i]
        }

        return cache[ args ] = a;
    }
})()

console.log(mult(10,2,4,2)) //160

提煉函數是重構中一種常見的技巧。如果在一個大函數中有一些代碼能獨立出來,我們常常把這些小代碼塊封裝在獨立的小函數裡面。獨立的小函數有助於代碼復用 ,如果這些小函數有一個良好的命名,它們本身起到了註釋的作用,這些小函數不需要在程式的其它地方使用,最好是他們用閉包封閉起來。代碼如下:

var mult = (function(){
    var cache = {};
    var calculate = function(){//封閉calculate函數
        var a = 1;
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            a = a * arguments[i]
        }
        return a;
    }

    return function(){
        var args = Array.prototype.join.call( arguments, ',' );
        if ( args in cache ){
            return cache[ args ];
        }
        return cache[ args ] = calculate.apply( null, arguments )
    }
})()
console.log(mult(10,2,4,2,2)) //320

(2)延續局部變數的壽命

img對象經常用於數據的上報,如下所示

var report = function( src ){
    var img = new Image()
    img.src = src;
}
report('http://.com/getUserinfo')

但是我們結果查詢後,得知,因為一些低版本瀏覽器的實現存在bug,在這些瀏覽器下使用report函數數據的上報會丟失30%,也就是說,reprot函數並不是每次都發起了請求。
丟失的原因是img是report函數中的局部變數,當report函數的調用結束後,img局部變數隨即被銷毀,而此時或許還沒有來的及發出http請求。所有此次的請求就會丟失掉。

現在我們將img變數用閉包封閉起來,便能解決請求丟失的問題。

var report = (function(){
    var img = [];
    return function( src ){
        var img = new Image();
        img.push( img );
        img.src = src;
    }
})()

4.閉包和麵向對象設計

下麵我們來看看跟閉包相關的代碼:

var extent = function(){
    var value = 0;
    return {
        call : function(){
            value++;
            console.log(value)
        }
    }
};
var bb = extent();

bb.call() //1
bb.call() //2
bb.call() //3

如果換成面向對象的寫法,就是:

var extent = {
    value : 0,
    call : function(){
        this.value++;
        console.log(this.value)
    }
}
extent.call();//1
extent.call();//2
extent.call();//3

或者,

var extent = function(){
    this.value = 0;
} 
extent.prototype.call = function(){
    this.value++;
    console.log(this.value)
}

var dd = new extent()
dd.call();//1
dd.call();//2
dd.call();//3

 

此文尚未完結,請關註更新

 

 上一篇文章: (二)this、call和apply


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們知道SQL SERVER建立鏈接伺服器(Linked Server)可以選擇的驅動程式非常多,最近發現使用ODBC 的 Microsoft OLE DB 驅動程式建立的鏈接伺服器(Linked Server), 調用存儲過程過程時,參數不能為NULL值。 否則就會報下麵錯誤提示: 對應的英文錯誤...
  • Tpcc-mysql是percona基於tpcc衍生出來專用於mysql基準測試的產品 ,可以參見 《高性能MySQL第三版》一、安裝rpm -Uvh http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpmyum...
  • SQL變數 全局變數: 全局變數是由系統定義和維護的使用兩個@作為首碼,不能由用戶聲明和賦值! 常用的全局變數如下 @@version :獲取當前使用的SQL Server版本號 EG: select @@version 顯示信息: ---------------------------------...
  • 經常被ORACLE坑,作為一個只需要開發時候連連ORACLE的程式員,在經歷了一次又一次的折騰之後,決定還是把這些瑣碎的事情寫下來。經常在虛擬機中使用ORACLE,ORACLE的網路配置有一些變化就掛了,我對這個也是服了,經常莫名其妙的問題提示。歸結起來就是設置好了ORACLE之後不能改配置,改過了...
  • 棧(stack)、堆(heap)概念:堆和棧都是數據結構,是一個特殊的存儲區,主要用來暫時存放數據和地址。棧:其同數據結構中的棧類似。用於存儲參數和局部變數。該存儲空間不用申請,有操作系統自動分配和釋放。 棧在中文中就說一個倉庫,所以類似倉庫你先放進去的最後才能取出。也就類似先放進去的最後釋放。.....
  • 真機測試步驟1.運行Xcode,Xcode打開後,點左上角菜單'Xcode',點'Preferences'。2.在打開的視窗中,點'Accounts',切換到賬號頁,然後點下麵的'+'號,在彈出菜單中點擊'Add Apple ID'。3.在彈出的對話框中,填入你的AppleID和密碼(不需要$99的...
  • 部分代碼 InteractiveTransition 類繼承NSObject:- (instancetype)initWithPresentingController:(UITableViewController *)presentingVc presentedController:(UIViewC...
  • [1]typeof [2]Object.prototype.toString [3]constructor [4]instanceof
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...