經過1個for循環理解閉包

場景:

在實際的開發過程當中,常常會遇到這樣的需求:批量添加節點,而且爲每一個節點添加事件綁定。看起來好像也沒什麼難度,一個for循環就能夠搞定前端

是這樣嗎?下面一塊兒來看一段代碼(批量建立<div>,而且綁定click事件,打印其序號)java

function addDivs(){
    var len = 10;
    for(var i=0;i<len;i++){
    	var div = document.createElement('div');
    	div.innerText = i;
  		div.addEventListener('click',function(){
    			alert(i)
    	})
    	document.body.appendChild(div)
    }
}
addDivs();
複製代碼

會有一部分人給出跟上面差很少的代碼,可是這段代碼是不能實現需求的,這段代碼實際上點擊任意div,彈出的都是數字10,這是爲何吶? 想要完全搞清楚其中的原因,咱們首先要了解一些JavaScript的一些知識es6

1. javaScript中是沒有塊級做用域(ES6中除外)的概念。下面進行一下簡單的對比,更加具象的瞭解:

若是你有想c語言這種編程經歷的人來講應該能夠很好的理解塊級做用域這個概念。web

#include <stdio.h> 
void main() 
{ 
    int i=2; 
    i--; 
if(i) 
{ 
    int j=3; 
} 
    printf("%d/n",j); 
}
複製代碼

運行這段代碼,會出現「use an undefined variable:j」的錯誤。能夠看到,C語言擁有塊級做用域,由於j是在if的語句塊中定義的,所以,它在塊外是沒法訪問的編程

可是用javaScript改寫以後並不會報錯,由於js中並無塊級做用域的概念(可是可使用匿名函數的方式實現)瀏覽器

var i =2;
i--;
if(i){
    var j = 3;
}
console.log(j)
複製代碼

2. 執行環境(執行上下文)和做用域鏈

執行環境:定義了變量或者函數有權訪問的數據,並決定了他們各自的行爲。每一個執行環境都有一個與之相關聯的變量對象,執行環境中全部的變量和函數都保存在這個對象中。bash

忘記這些抽象的概念吧,咱們來講人話:閉包

執行環境分兩種,全局執行環境和局部執行環境app

先說全局執行環境: 它是一個最外層的執行環境。在web瀏覽器中,它對應的變量對象是window,這也是爲何全部的全局變量和函數都是做爲window對象的屬性和方法建立的。函數

再說函數執行環境: 當執行某個函數時,會建立一個活動對象,並把這個對象做爲與該函數的執行環境關聯的變量對象,從而建立出函數的執行環境。

那什麼叫做用域鏈?

首先一點,咱們要明確ECMAScript的執行流的工做過程:當執行流進入一個函數時,函數的執行環境就會被推入到一個環境棧中,函數執行完以後,棧將其環境彈出,將控制權返還給以前的執行環境

當代碼在一個執行環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的前端始終都是當前執行的代碼所在環境的變量對象,做用域鏈的末端始終都是全局執行環境的變量對象。這就是標識符解析的基礎——沿着做用域鏈一級一級的搜索標識符的過程。 做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有權訪問 上一個javasc高級程序設計中經典的🌰

var color="blue";
function changeColor(){
    var anotherColor="red";
    function swapColors(){
        var tempColor=anotherColor;
        anotherColor=color;
        color=tempColor;
        //這裏能夠訪問color、anotherColor和tempColor
    }
    //這裏能夠訪問color、anotherColor,不能訪問tempColor
    swapColors();
}
//這裏只能訪問color
changeColor();
複製代碼

做用域鏈圖下圖:

3.閉包

Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.

在理解閉包以前你須要知道:變量的做用域

無非就是兩種:全局變量和局部變量

Javascript語言的特殊之處,就在於函數內部能夠直接讀取全局變量。

var i=1;
function f(){
    alert(i);
}
f() //1;
複製代碼

另外一方面,在函數外部天然沒法讀取函數內的局部變量。

function f(){
    var n=999;
}
alert(n); // error
複製代碼

這裏有一個地方須要注意,函數內部聲明變量的時候,必定要使用var命令。若是不用的話,你實際上聲明瞭一個全局變量。

出於種種緣由,咱們有時候須要獲得函數內的局部變量。可是,前面已經說過了,正常狀況下,這是辦不到的,只有經過變通方法才能實現。

function f(){
	var n=999;
	function f1(){
		alert(n);
	} 
	return f1;
}
var result=f();
result();// 彈出999
複製代碼

  f1()能夠當作閉包。閉包在我看來就是能夠訪問另外一個函數做用域下變量的函數。 主要做用有模擬塊級做用域,建立私有變量等。說白了就是爲了收緊權限,有限封裝

有了這些知識之後 咱們回頭看剛纔的代碼,又發現什麼問題嗎?

function addDivs(){
    var len = 10;
    for(var i=0;i<len;i++){
    	var div = document.createElement('div');
    	div.innerText = i;
  		div.addEventListener('click',function(){
    			alert(i)
    	})
    	document.body.appendChild(div)
    }
}
addDivs();
複製代碼

咱們爲每個div標籤綁定了一個函數實例(Function),表面上看是每一個函數都展現當前的索引值,第一個div展現0,第二個展現1,一次類推。可是實際上,每一個函數展現的都是10。 這是由於每一個函數做用域鏈中都保存着addDivs()的活動對象,因此它們引用的都是同一個變量i,當addDivs()執行完以後,變量i的值是10,此時每一個函數都引用着保存變量i的同一個變量對象(跟執行環境相對應),因此每一個函數內i的值都是10。 #####解決方法 經過新建另外一個匿名函數強制讓閉包的行爲符合預期

1.增長若干個閉包域空間,專門用來存儲原先須要引用的內容,這裏採用的是匿名函數

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	(function(arg){
		div.addEventListener('click',function(){
			alert(arg)
		})
	}(i))
	document.body.appendChild(div)
}
複製代碼

2.將事件綁定在新增的匿名函數返回的函數上

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	div.addEventListener('click',(function(arg){
		return function(){
			alert(arg)
		}
	}(i)))
	document.body.appendChild(div)
}
複製代碼

1,2方法類似又不一樣:

  • 類似點:一樣是增長若干個對應的閉包域空間用來存儲數據
  • 不一樣點:辦法1 是在新增的匿名閉包空間內完成事件的綁定,而此例是將事件綁定在新增的匿名函數返回的函數上

3.es6

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	let j = i;
	div.addEventListener('click',(function(arg){
		alert(j)
	})
	document.body.appendChild(div)
}
複製代碼

如文章有任何疏漏,請各位大聲斧正

未經做者受權 禁止轉載

相關文章
相關標籤/搜索