閉包及閉包的應用


title: 閉包及閉包的應用 date: 2019-04-25 17:00:00 tags: -JavaScript
-closure categories: JavaScript password: tgfe abstract: Welcome to my blog, enter password to read. message: Welcome to my blog, enter password to read.

前端開發人員對閉包這個知識點,必定都不陌生,咱們都知道閉包的概念是指有權訪問另外一個函數做用域中的變量的函數。那麼,閉包在js中的實際應用都有哪些呢,今天就一塊兒來了解一下吧。javascript

執行環境及做用域鏈

要理解閉包,首先要對js的執行環境和做用域有了解。html

概念太長不想看系列:前端

執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有與之關聯的變量對象集合[[scope]],環境中定義的全部變量和函數都保存在這個對象中,這個集合被稱爲該環境的做用域鏈。vue

js中,每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。在函數執行以後,棧將其環境彈出,環境隨之銷燬,控制權返回給以前的執行環境。java

當代碼在一個函數的環境中執行時,會建立一個變量對象的做用域鏈。做用域鏈的前端,始終都是當前執行的代碼所在的變量對象。若是這個環境是函數,則將其活動對象(active object)做爲變量對象。活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個對象來自包含(外部)環境,而再下一個變量則來自下一個變量環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。 當運行期上下文被銷燬,活動對象也隨之銷燬。react

標識符解析是沿着做用域鏈一級一級搜索標識符的過程。搜索過程始終從做用域鏈的前端開始,而後逐級的向後回溯,直至找到標識符爲止。web

總結:面試

  • 每一個函數有一個執行環境,一個執行環境關聯一個變量對象,變量對象的集合叫作做用域鏈。
  • 做用域鏈的前端是當前的執行代碼所在的變量對象,下一個對象是外部函數,一直延續到全局變量。
  • 標識符解析是沿着做用域鏈從前端開始逐級回溯的過程。
  • 代碼執行完畢後,所在的環境會被銷燬,web中全局執行環境是window對象,全局環境會在應用程序退出時被銷燬。

閉包的定義

閉包是指有權訪問另外一個函數做用域中的變量的函數。編程

建立閉包的常見方式,就是在一個函數內部建立另外一個函數。設計模式

在javascript語言中,閉包就是函數和該函數做用域的組合。從這個概念上來說,在js中,全部函數都是閉包(函數都是對象而且函數都有和他們相關聯的做用域鏈scope chain)。

大多數函數被調用時(invoked),使用的做用域和他們被定義時(defined)使用的做用域是同一個做用域,這種狀況下,閉包神馬的,可有可無。可是,當他們被invoked的時候,使用的做用域不一樣於他們定義時使用的做用域的時候,閉包就會變的很是有趣,而且開始有了不少的使用場景,這就是你之因此要掌握閉包的緣由。 用途:

  • 讀取函數內部的變量。
  • 讓這些變量的值始終保持在內存中。不會在f1調用後被自動清除。
  • 方便調用上下文的局部變量。利於代碼封裝。

面試題助消化

var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f();
}
checkScope();   //=> "local scope"
複製代碼
  • checkScope被invoke時,return f(),運行內部嵌套函數f,f沿着做用域鏈從內向外尋找變量scope,找到「local scope」,中止尋找,所以,函數返回 「local scope」;
var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f;
}
checkScope()();   //=> "local scope"
複製代碼
  • checkScope被invoke時,將內部嵌套的函數f返回,所以checkScope()()這句執行時,其實運行的是f(),f函數返回scope變量,在這種狀況下,f會從哪一個做用域裏去尋找變量scope呢?
  • 詞法做用域的基礎規則:函數被執行時(executed)使用的做用域鏈(scope chain)是被定義時的scope chain,而不是執行時的scope chain。 嵌套函數f(), 被定義時,所在的做用域鏈中,變量scope是被綁定的值是「local scope」,而不是"window scope",所以,以上代碼的結果是啥?沒錯,是"local scope"。 這就是閉包的神奇特性:閉包能夠捕獲到局部變量和參數的外部函數綁定,即使外部函數的調用已經結束。
var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return this.scope;
    }
    return f;
}
checkScope()();   //=> "window scope"
複製代碼
  • 閉包的this指向的是它定義的地方的this,非嚴格模式下,函數內部的this指向全局對象(嚴格模式下,this爲undefined),函數 checkScope 的this指向的是window對象,因此返回了window scope

閉包的應用場景

爲節點循環綁定click事件

當前頁面有5個button,要求是點擊每一個button的時候彈出對應的編號

//html代碼

<!DOCTYPE html>
<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    <button>Button0</button>
    <button>Button1</button>
    <button>Button2</button>
    <button>Button3</button>
    <button>Button4</button>
</body>
</html>
複製代碼

//js

for(var i = 0;i<btnList.length;i++){
	//錯誤的代碼 onclick是異步觸發的,
	btnList[i].onclick = function(){
		console.log(i)
	}
 
	//正確的代碼
	//採用「當即執行函數Immediately-Invoked Function Expression (IIFE)」的方式建立做用域
	(function(i){
		btnList[i].onclick = function(){
			console.log(i)
		}
	})(i);
}
複製代碼

todo 當即執行函數不傳i

延續局部變量的壽命

img對象常常用於數據上報,以下:

var report = function(src) {
    var img = new Image();
    img.src = src;
}
report('http://xxx.com/getUserInfo');
複製代碼

這段代碼在運行時,發如今一些低版本瀏覽器上存在bug,會丟失部分數據上報,緣由是img是report函數中的局部變量,當report函數調用結束後,img對象隨即被銷燬,而此時可能還沒來得及發出http請求,因此這次請求就會丟失。

所以,咱們使用閉包把img對象封閉起來,就能夠解決數據丟失的問題:

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


 (function(){
        //i在外部就不認識啦
        for(var i=0;i<count;i++){}
  })();
  console.log(i);//報錯,沒法訪問
複製代碼

對結果進行緩存

todo 傳參複雜數據 react vue 的實現 tgou https

var fn=function(){
    var sum=0;
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i];
    }
    return sum;
}
console.log(fn(1,2));//3
 
//優化版本
var fn=(function(){
    var cache={}//將結果緩存到該對象中
    return function(){
        var str=JSON.stringify(arguments);
        if(cache[str]){//判斷緩存中是否存在傳遞過來的參數,存在直接返回結果,無需計算
            return cache[str];
        }else{//進行計算並返回結果
            var sum=0;
            for(var i=0;i<arguments.length;i++){
                sum+=arguments[i];
            }
            return cache[str]=sum;
        }
    }
})()
複製代碼

設計模式之 構造器模式

在經典面向對象的編程語言中,Constructor是一種在內存已分配給該對象的狀況下,用於初始化新建立對象的方法。 在JavaScript中,幾乎全部的東西都是對象,咱們一般最感興趣的是object構造器。

Object構造器用於構建特定類型的對象--準備好對象以備使用,同時接收構造器可使用的參數,以在第一次建立對象時,設置成員屬性和方法的值。

// 構造器模式
function Car(model, year, miles){
	this.model = model;
	this.year = year;
	this.miles = miles;

	Car.prototype.toString = function(){
		return this.model + 'has done ' + this.miles + ' miles';
	}
}
 
var civic = new Car('honda civic', 2019, 2000);
console.log(civic.toString()); 
複製代碼
  • avaScript不支持類的概念,但支持與對象一塊兒用的特殊constructor(構造器)函數,經過在構造器前面加new關鍵字,告訴JavaScript像使用構造器同樣實例化一個新對象,而且對象成員由該函數定義。
  • toString這樣的函數在這裏在每次建立新的實例的時候都被從新定義,這不是最理想的,toString應該在全部的Car構造的實例之間共享。因此把toString放在Car的prototype(原型對象)上,Car構建的全部實例,都會訪問同一個原型對象並獲取到toString方法。 這裏toString就是一個閉包,function能夠訪問實例中的model,miles和year變量。

todo prototype位置

Module(模塊)模式

在JavaScript中,Module模式用於進一步模擬類的概念。經過這種方式,可以使一個單獨的對象擁有公有/私有方法和變量,從而屏蔽來自全局做用域的特殊部分。 產生的結果是:函數名與在頁面上其餘腳本定義的函數衝突的可能性下降。

var myNamespace = (function(){
    var obj = {};
    // 私有計數器變量
    var myPrivateVar = 0;
    
    // 記錄全部參數的私有函數
    var myPrivateMethods  = function(bar){
        console.log('my privateVar in private = '+ bar);
    }
    
    //公有變量
    var myPublicVar = 'foo';
    // 調用私有變量和函數的公有函數
    function myPublicMethods(bar){
        // 增長私有計數器值
        myPrivateVar = bar;
        console.log('my privateVar in public = '+ myPrivateVar);
        // 調用私有函數 並傳入參數
        myPrivateMethods(bar);
    }
    
    obj.myPublicVar = myPublicVar;
    obj.myPublicMethods = myPublicMethods;
    
    return obj;
    
})();

// myNamespace.myPrivateVar;
// 用戶能夠調用公有函數,訪問和變動私有函數及私有變量
myNamespace.myPublicMethods('user input');
複製代碼

Module模式使用閉包封裝「私有」狀態和組織。它提供了一種包裝混合共有/私有方法和變量的方式,防止其泄漏至全局做用域,並與別的開發人員的接口發生衝突。經過該模式,只需返回一個共有API,而其餘的一切都維持在私有閉包裏。 在Module模式中,共有部分(閉包)能夠接觸私有部分,然而外界沒法接觸類的私有部分,模塊中的 myPrivateVar 和 myPrivateMethods 是私有的,所以應用程序的其餘部分沒法直接讀取它。它只與模塊的閉包一塊兒存在。

相關文章
相關標籤/搜索