function
函數格式
function getPrototyNames(o,/*optional*/ a) { a = a || []; for(var p in o) { a.push(p); } return a; }
caller
func.caller 返回函數調用者
function callfunc() { if(callfunc.caller) { alert(callfunc.caller.toString()); }else { alert("沒有函數調用"); } } function handleCaller() { callfunc(); } handleCaller();//返回 handler callfunc();//沒有函數調用,返回null,執行了《沒有函數調用》
callee
匿名方法遞歸調用
alert( (function (x) { if (x <= ) return ; return x * arguments.callee(x - ); }()));//
scope
作用域大家都不陌生,今天就來說說閉包問題,深刻吃透閉包問題。
<script> var global = "global scope";//這個是全局的作用域 function scope() { var scope = "local scope";//這個是局部的作用域 return scope;//返回的scope,是局部的變量 } </script>
1、:定義的全局變量也能在函數內部訪問。當定義的局部變量和全局變量名字相同時,局部變量的就會隱藏全局變量,不會破壞全局變量的值。
var scope = "global scope"; function f() { var scope = "local scope"; return scope; } alert(f());//local scope alert(scope);//global scope;
上面確實是很容易理解,對吧。
2、 全局變量可以不用var聲明,但是局部變量必須使用var聲明,如果局部變量不使用var聲明,編譯器會默認這個是個全局變量。
<span style="line-height: .; font-family: verdana, Arial, Helvetica, sans-serif; font-size: px; background-color: rgb(, , );"> </span> scope = "global scope"; function f() { scope = "local scope"; return scope; } alert(f());//local scope alert(scope);//local scope
但是全局變量不使用var聲明,也僅限非嚴格模式,如果使用嚴格模式的話,會報錯誤
<script> "use strict"; scope = "global scope"; function f() { scope = "local scope"; return scope; } alert(f());//local scope alert(scope);//local scope </script>
所以建議大家聲明變量時,千萬不要省略var,可以避免不必要的麻煩。
3、 聲明提前,也是可以滴。什麼叫什麼提前。
<script> "use strict"; scope; console.log(scope); var scope = "global scope"; console.log(scope); </script>
這個可能大家看出第一個打印undefined ,是呀還沒有給他賦值, 下面賦值可定打印global scope了。
這樣理解並沒有錯,但是為什麼會這樣的呢,一個變量不是應該先定義才可以使用的嗎?
這裡給大家說下作用域鏈,JavaScript是基於詞法作用域的語言。
1、作用域鏈是一個對象或者鏈表,這組代碼中定義了這段代碼"作用域中“的變量。當JavaScript需要查找變量scope時,就會從鏈中的第一個對象開發查找,如果第一個對象為scope,則會直接返回這個對象的值,如果不存在繼續第二對象開始查找,直到找到。如果在作用域鏈上未查到該變量,則會拋出一個錯誤。
我們可以這個作用鏈可以這樣表示:查找 scope->window(全局對象)很顯然後面是有定義scope的。但是並沒有做賦值操作,後面才做賦值操作,所以此時值為undefined.
4、這個比較具有迷惑性了,大家猜想下打印的值是什麼?
<script> "use strict"; var scope = "global scope"; function f() { console.log(scope); var scope = "local scope"; console.log(scope); } f(); </script>
看到這段代碼:如果你粗心大意的話,很有可能會寫出錯誤的答案:
1、gobal scope
2、local scope
分析: 聲明了全局變量,在函數體中時,第一個表示全局變量,所以打印global,第二定義了局部的變量,覆蓋掉了全局的scope,所以打印local scope。
這樣的分析在C# java ,完全正確。但是這裡分析確實錯誤的。
這說明這個問題前,我們先看一個問題。
這句話很重要:全局變量在程序中始終都是有定義的。局部變量在聲明它的函數體以及其所嵌套的函數內始終是定義的。
如果你是從事高級語言工作開始接觸JavaScript 有點不適應其作用域的定義。我也是這樣的。我們來看一個例子:
<script> var g = "global scope"; function f() { for(var i=;i<;i++) { for(var j=;j<;j++) { ; } console.log(j); } console.log(i); } console.log(g); f(); </script>
打印的結果是什麼?
大家看到{} 表示語句塊,語句塊在一塊作用域,所以大家可能猜想,j、i值已經在內存釋放掉了,所以結果肯定是undefined.
真實的結果可能令你失望,
結果為什麼會是這樣,我開始的表情和你一樣。
這時查看我讓你記住的那句話。。。全局變量在程序中始終都是有定義的。局部變量在聲明它的函數體以及其所嵌套的函數內始終是定義的。
確切的說函數的參數 也是屬於局部變量的范疇。這句話也很重要!!!
那句話大概意思說,只要是在函數內部定義的變量,在整個函數內都是有效的。所以結果也就不難理解了。在回過頭看我們的那個問題,你理解了嗎?
作用鏈還有以下定義:
1、作用鏈是有一個全局對象組成。
2、在不包含嵌套的函數體內,作用鏈上有兩個對象,第一個定義了函數參數和局部變量的對象,第二個是全局對象。
3、在一個嵌套的函數體內,作用鏈上至少包含三個對象。
當定以一個函數的時候,就會保存一個作用域鏈。
當調用這個函數時,它就會創建一個新的對象來存儲它的局部變量,並將這個對象添加到保存的作用鏈上。同時創建一個新的更長的表示函數調用的作用鏈。
對於嵌套的函數,當調用外部函數時,內部函數又會重新定義一遍。因為每次調用外部函數的時候,作用鏈都是不同的。內部函數在每次定義的時候都有微妙的差別,每次調用的外部函數時,內部函數的代碼都是相同的,而且關聯的代碼的作用域也是不同的。
閉包
搞了這麼久終於要講了,但是再將之前我們在來分析下作用域。
<script> var nameg="global" var g = function f() { console.log(name); function demo() { console.log("demo="+name); } var name = ""; function demo() { var name = ""; console.log("demo=" + name); } function demo() { console.log("demo=" + nameg); } demo(); demo(); demo(); }; g(); </script>
我們按照作用鏈來分析:
調用demo0, demo0()->查找name,未找到->f()查找,返回
調用demo1,demo1()->查找name,找到,返回
調用demo2,demo2()->查找nameg,未找到->f()查找nameg,未找到->window查找nameg找到,返回。
看這個例子:
<script> function f() { var count = ; return { counter:function() { return count++; },reset: function() { return count = ; } } } var d = f(); var c = f(); console.log("d第一次調用:"+ d.counter());// console.log("c第一次調用:"+ c.counter());// 互不影響 console.log("d第一次調用:"+ d.reset());// console.log("c第二次調調用"+ c.counter());// </script>
這個例子上大家可以看到,我做了一個計數和置零的操作。
創建了兩個f的對象實例d c,各有自己的作用域鏈,所以其值互不影響。當c第二次調用時,count值被保存了,因為c對象並未被銷毀。明白這個例子後,後面的例子才比較好懂。
這個過程,大家應該十分明了了。那麼現在我們來看閉包問題。我設置四個按鈕,點擊每個按鈕就返回響應的名字。
<body> <script> function btnInit() { for(var i=;i<;i++) { var btn = document.getElementById("btn" + i); btn.addEventListener("click",function() { alert("btn" + i); }); } } window.onload= btnInit; </script> <div> <button id="btn">Btn</button> <button id="btn">Btn</button> <button id="btn">Btn</button> <button id="btn">Btn</button> </div> </body>
點擊運行,結果卻是都是btn5;
我們用剛才的分析在坐下,首先要調用匿名函數->查找i,未找到->btnInit(),找到i在for循環中。找到。我們知道只有函數調用結束才釋放,for中的i總是可見的,所以保留了最後的i值。那麼如何解決呢。
解決i值在函數中不是總是可見的,那麼我們就要使用函數的嵌套了,然後把i值傳進去。
function btnInit() { for(var i=;i<;i++) { (function (data_i) { var btn = document.getElementById("btn" + data_i); btn.addEventListener("click", function () { alert("btn" + data_i); }); }(i)); } }
看修改後的代碼,首先執行第一次for,創建了一個對象,我們首先是執行匿名函數->data_i,未找到->function(data_i)找到,然後再次執行for有創建一個對象,閉包規則說的互不影響。所以能得出正確的結果。
以上所述是小編給大家介紹的 JavaScript必知必會(九)function 說起 閉包問題的相關知識,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對網站的支持!