前言:這是筆者學習之后自己的理解與整理。如果有錯誤或者疑問的地方,請大家指正,我會持續更新!
一直沒搞清楚立即執行函數和閉包之間的關系,總結一下:
- 閉包有很多種理解:訪問不到內部作用域,函數就是這樣,所以函數就是閉包;
- 閉包還有一種理解:通過把函數內部的變量和方法返回出來,這樣外部作用域就可以訪問內部作用域了,並且
- 立即執行函數和閉包之間沒有必然的聯系,雖然它們經常結合一起使用;
- 立即執行函數只是一種函數的調用方式;
- 閉包的目的則是外部函數可以訪問內部函數的作用域;
立即執行函數(IIFE)
大牛的講解,點擊
立即執行函數、立即執行表達式、IIFE(immediately invoked function expression)、自執行函數,叫法不同,都是一樣的;
立即執行函數是指聲明完之后便直接執行的函數,這類函數通常是一次性使用的,因此沒必要給這類函數命名,直接讓它執行就好了;
主要目的是做的一些封裝,防止變量全局污染,以及保證內部變量的安全;
javascript中 沒用私有作用域的概念,如果在多人開發的項目上,你在全局或局部作用域中聲明了一些變量,可能會被其他人不小心用同名的變量給覆蓋掉,根據 javascript 函數作用域鏈的特性,可以使用 IIFE 可以模仿一個私有作用域,用匿名函數作為一個“容器”,“容器”內部可以訪問外部的變量,而外部環境不能訪問“容器”內部的變量,所以 ( function(){…} )() 內部定義的變量不會和外部的變量發生沖突,俗稱“匿名包裹器”或“命名空間”。
JQuery 使用的就是這種方法,將 JQuery 代碼包裹在 ( function (window,undefined){…jquery代碼…} (window) 中,在全局作用域中調用 JQuery 代碼時,可以達到保護 JQuery 內部變量的作用。
我們首先需要有函數表達式的概念,點擊
定義一個函數,函數后面加上括號,即可完成調用;
<script type="text/javascript"> function (){}(); // SyntaxError: Unexpected token ( //引擎在遇到關鍵字function時,會默認將其當做是一個函數聲明,函數聲明必須有一個函數名,所以在執行到第一個左括號時就報語法錯誤了; //函數表達式的函數名則可有可無; //在function前面加!、+、 -、=甚至是逗號等或者把函數用()包起來都可以將函數聲明轉換成函數表達式;我們一般用()把函數聲明包起來或者用 = </script>
引擎在遇到關鍵字 function 時,會默認將其當做是一個函數聲明,函數聲明必須有一個函數名,所以在執行到第一個左括號時就報語法錯誤了;
然后我們給它加上函數名;
<script type="text/javascript"> function abc(){}(); // SyntaxError: Unexpected token ) //在一個表達式后面加上括號,表示該表達式立即執行; //而如果是在一個語句后面加上括號,該括號完全和之前的語句沒什么關系,而只是一個分組操作符,用來控制運算中的優先級(小括號里的先運算)。 </script>
在一個語句后面加上括號,該括號完全和之前的語句沒什么關系,而只是一個分組操作符,用來控制運算中的優先級(小括號里的先運算)。上面代碼可以寫成:
<script type="text/javascript"> function abc(){}; ();// SyntaxError: Unexpected token ) //分組操作符內的表達式不能為空,執行到右括號時,發現表達式為空,所以報錯。 </script>
分組操作符內的表達式不能為空,執行到右括號時,發現表達式為空,所以報錯。
然而函數表達式的函數名則可有可無;
在 function 前面加!、+、 -、=甚至是逗號等或者把函數用()包起來都可以將函數聲明轉換成函數表達式;我們一般用()把函數聲明包起來或者用 = ;
我們在函數后面加上括號,然后再用另一個括號把它們都包起來;
javascript 中,括號內不允許包含語句,但可以是表達式;
引擎先遇到括號,然后遇到關鍵字 function , 就自動把括號里面的代碼識別為函數表達式,而不是函數聲明;
<script type="text/javascript"> (function (){/*code*/}()); //javascript中,括號內不允許包含語句,但可以是表達式; //引擎先遇到括號,然后遇到關鍵字function , 就自動把括號里面的代碼識別為函數表達式,而不是函數聲明 (function(){/*code*/})(); //也可以這樣寫 var a = function(){/*code*/}(); //我們可以在函數表達式后面直接加括號,而不用把函數包起來; //但還是推薦外部加上(): var b = (function(){/*code*/}()); var c = (function(){/*code*/})(); //因為外部如果沒有(),我們得去function(){/*code*/}后面看是否存在(),判斷b/c是一個函數還是一個函數內部的返回值 //為了代碼的可讀性,還是要在外部加上(),無論是否已經是表達式; </script>
javascript 只有函數擁有局部作用域,立即執行函數也有這一特點,我們可以利用它減少全局變量造成的空間污染;
<script type="text/javascript"> (function abc(){ console.log(abc);//function abc(){...} var a = 1; console.log(a);//1 })() // console.log(a); //ReferenceError: a is not defined console.log(abc);//ReferenceError: abc is not defined //立即執行函數的函數名在外部也是找不到的 //函數表達式的標識符在外部作用域是找不到的,只有內部作用域可以找到 </script>
閉包(closure)
大牛的講解,點擊
我們首先需要有作用域的概念,點擊
閉包通常用來創建內部變量,使得這些變量不能被外部隨意修改,同時又可以通過指定的函數接口來操作;簡單的說,外部作用域就可以訪問函數內部作用域的變量了。
由於作用域的關系,我們在函數外部是無法直接訪問到函數內部的變量的,但是函數內部可以把這個變量傳給全局變量或者返回出來,這樣外部作用域就可以訪問函數內部作用域的變量了;
簡單的說,閉包就是有權限訪問另一個函數內部作用域的變量的函數;
- javascript 具有自動垃圾回收機制,函數運行完之后,其內部的變量和數據會被銷毀;
- 但是閉包就是在外部可以訪問此函數內部作用域的變量,所以閉包的一個特點就是只要存在引用函數內部變量的可能,JavaScript 就需要在內存中保留這些變量。而且 JavaScript 運行時需要跟蹤這個內部變量的所有外部引用,直到最后一個引用被解除(主動把外部引用賦為 null 或者頁面關閉),JavaScript 的垃圾收集器才能釋放相應的內存空間;這句話不是很好理解,下面用代碼展示;
<script type="text/javascript"> function outer(){ var a = 1; function inner(){ return a++; } return inner; } var abc = outer(); //outer()只要執行過,就有了引用函數內部變量的可能,然后就會被保存在內存中; //outer()如果沒有執行過,由於作用域的關系,看不到內部作用域,更不會被保存在內存中了; console.log(abc());//1 console.log(abc());//2 //因為a已經在內存中了,所以再次執行abc()的時候,是在第一次的基礎上累加的 var def = outer(); console.log(def());//1 console.log(def());//2 //再次把outer()函數賦給一個新的變量def,相當於綁定了一個新的outer實例; //console.log(a);//ReferenceError: a is not defined //console.log(inner);//ReferenceError: a is not defined //由於作用域的關系我們在外部還是無法直接訪問內部作用域的變量名和函數名 abc = null; //由於閉包占用內存空間,所以要謹慎使用閉包。盡量在使用完閉包后,及時解除引用,釋放內存; </script>
立即執行函數能配合閉包保存狀態。
<script type="text/javascript"> for(var i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); //3 3 3 //在執行到這一行時,發現匿名函數里沒有i,然后向往外部作用域找,然后找到的其實是for循環執行完了的i,也就是2++,3 },0); }; for(var i = 0; i < 3; i++){ setTimeout((function(x){ console.log(x); //0 1 2 })(i),0); //在立即執行函數內部i傳給了x,並且鎖在內存中,所以不會變 }; </script>
插件
用立即函數配合閉包寫插件,防止變量全局污染,以及保證內部變量的安全;
<script type="text/javascript"> var Person = (function(){ var _sayName = function(str){ str = str || 'shane'; return str; } var _sayAge = function(age){ age = age || 18; return age; } return { SayName : _sayName, SayAge : _sayAge } })(); //通過插件提供的API使用插件 console.log(Person.SayName('lucy')); //lucy console.log(Person.SayName());//shane console.log(Person.SayAge());//18 </script>
一道經典面試題
下面的ul中,如何點擊每一個 li 的時候彈出其下標?
<ul> <li>index 00000</li> <li>index 11111</li> <li>index 22222</li> </ul>
方法一:用閉包
<script type="text/javascript"> window.onload = function(){ var oLi = document.getElementsByTagName('ul')[0].children; for (var i = 0; i < oLi.length; i++){ (function(index){ oLi[index].onclick = function(){ console.log(index); }; })(i); } } </script>
方法二:閉包還有一種寫法
<script type="text/javascript"> window.onload = function(){ var oLi = document.getElementsByTagName('ul')[0].children; for (var i = 0; i < oLi.length; i++){ oLi[i].onclick = (function(index){ return function(){ console.log(index); } })(i); } } </script>
方法三:將下標作為對象的一個屬性,添加到每個數組元素中,(name: " i ", value: i 的值);
<script type="text/javascript"> window.onload = function(){ var oLi = document.getElementsByTagName('ul')[0].children; for (var i = 0; i < oLi.length; i++){ oLi[i].i = i; oLi[i].onclick = function(){ console.log(this.i); }; } } </script>
還有其他方法...以后在整理;