JavaScript必知必會(huì)(九)function 說(shuō)起 閉包問(wèn)題
function
函數(shù)格式
function getPrototyNames(o,/*optional*/ a)
{
a = a || [];
for(var p in o)
{
a.push(p);
}
return a;
}
caller
func.caller 返回函數(shù)調(diào)用者
function callfunc()
{
if(callfunc.caller)
{
alert(callfunc.caller.toString());
}else
{
alert("沒(méi)有函數(shù)調(diào)用");
}
}
function handleCaller()
{
callfunc();
}
handleCaller();//返回 handler
callfunc();//沒(méi)有函數(shù)調(diào)用,返回null,執(zhí)行了《沒(méi)有函數(shù)調(diào)用》
callee
匿名方法遞歸調(diào)用
alert( (function (x) {
if (x <= ) return ;
return x * arguments.callee(x - );
}()));//
scope
作用域大家都不陌生,今天就來(lái)說(shuō)說(shuō)閉包問(wèn)題,深刻吃透閉包問(wèn)題。
<script>
var global = "global scope";//這個(gè)是全局的作用域
function scope()
{
var scope = "local scope";//這個(gè)是局部的作用域
return scope;//返回的scope,是局部的變量
}
</script>
1、:定義的全局變量也能在函數(shù)內(nèi)部訪問(wèn)。當(dāng)定義的局部變量和全局變量名字相同時(shí),局部變量的就會(huì)隱藏全局變量,不會(huì)破壞全局變量的值。
var scope = "global scope";
function f()
{
var scope = "local scope";
return scope;
}
alert(f());//local scope
alert(scope);//global scope;
上面確實(shí)是很容易理解,對(duì)吧。
2、 全局變量可以不用var聲明,但是局部變量必須使用var聲明,如果局部變量不使用var聲明,編譯器會(huì)默認(rèn)這個(gè)是個(gè)全局變量。
<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聲明,也僅限非嚴(yán)格模式,如果使用嚴(yán)格模式的話,會(huì)報(bào)錯(cuò)誤
<script>
"use strict";
scope = "global scope";
function f()
{
scope = "local scope";
return scope;
}
alert(f());//local scope
alert(scope);//local scope
</script>

所以建議大家聲明變量時(shí),千萬(wàn)不要省略var,可以避免不必要的麻煩。
3、 聲明提前,也是可以滴。什么叫什么提前。

<script> "use strict"; scope; console.log(scope); var scope = "global scope"; console.log(scope); </script>
這個(gè)可能大家看出第一個(gè)打印undefined ,是呀還沒(méi)有給他賦值, 下面賦值可定打印global scope了。
這樣理解并沒(méi)有錯(cuò),但是為什么會(huì)這樣的呢,一個(gè)變量不是應(yīng)該先定義才可以使用的嗎?
這里給大家說(shuō)下作用域鏈,JavaScript是基于詞法作用域的語(yǔ)言。
1、作用域鏈?zhǔn)且粋€(gè)對(duì)象或者鏈表,這組代碼中定義了這段代碼"作用域中“的變量。當(dāng)JavaScript需要查找變量scope時(shí),就會(huì)從鏈中的第一個(gè)對(duì)象開(kāi)發(fā)查找,如果第一個(gè)對(duì)象為scope,則會(huì)直接返回這個(gè)對(duì)象的值,如果不存在繼續(xù)第二對(duì)象開(kāi)始查找,直到找到。如果在作用域鏈上未查到該變量,則會(huì)拋出一個(gè)錯(cuò)誤。
我們可以這個(gè)作用鏈可以這樣表示:查找 scope->window(全局對(duì)象)很顯然后面是有定義scope的。但是并沒(méi)有做賦值操作,后面才做賦值操作,所以此時(shí)值為undefined.
4、這個(gè)比較具有迷惑性了,大家猜想下打印的值是什么?
<script>
"use strict";
var scope = "global scope";
function f() {
console.log(scope);
var scope = "local scope";
console.log(scope);
}
f();
</script>
看到這段代碼:如果你粗心大意的話,很有可能會(huì)寫出錯(cuò)誤的答案:
1、gobal scope
2、local scope
分析: 聲明了全局變量,在函數(shù)體中時(shí),第一個(gè)表示全局變量,所以打印global,第二定義了局部的變量,覆蓋掉了全局的scope,所以打印local scope。
這樣的分析在C# java ,完全正確。但是這里分析確實(shí)錯(cuò)誤的。
這說(shuō)明這個(gè)問(wèn)題前,我們先看一個(gè)問(wèn)題。
這句話很重要:全局變量在程序中始終都是有定義的。局部變量在聲明它的函數(shù)體以及其所嵌套的函數(shù)內(nèi)始終是定義的。
如果你是從事高級(jí)語(yǔ)言工作開(kāi)始接觸JavaScript 有點(diǎn)不適應(yīng)其作用域的定義。我也是這樣的。我們來(lái)看一個(gè)例子:
<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>
打印的結(jié)果是什么?
大家看到{} 表示語(yǔ)句塊,語(yǔ)句塊在一塊作用域,所以大家可能猜想,j、i值已經(jīng)在內(nèi)存釋放掉了,所以結(jié)果肯定是undefined.
真實(shí)的結(jié)果可能令你失望,

結(jié)果為什么會(huì)是這樣,我開(kāi)始的表情和你一樣。

這時(shí)查看我讓你記住的那句話。。。全局變量在程序中始終都是有定義的。局部變量在聲明它的函數(shù)體以及其所嵌套的函數(shù)內(nèi)始終是定義的。
確切的說(shuō)函數(shù)的參數(shù) 也是屬于局部變量的范疇。這句話也很重要?。。?br />
那句話大概意思說(shuō),只要是在函數(shù)內(nèi)部定義的變量,在整個(gè)函數(shù)內(nèi)都是有效的。所以結(jié)果也就不難理解了。在回過(guò)頭看我們的那個(gè)問(wèn)題,你理解了嗎?
作用鏈還有以下定義:
1、作用鏈?zhǔn)怯幸粋€(gè)全局對(duì)象組成。
2、在不包含嵌套的函數(shù)體內(nèi),作用鏈上有兩個(gè)對(duì)象,第一個(gè)定義了函數(shù)參數(shù)和局部變量的對(duì)象,第二個(gè)是全局對(duì)象。
3、在一個(gè)嵌套的函數(shù)體內(nèi),作用鏈上至少包含三個(gè)對(duì)象。
當(dāng)定以一個(gè)函數(shù)的時(shí)候,就會(huì)保存一個(gè)作用域鏈。
當(dāng)調(diào)用這個(gè)函數(shù)時(shí),它就會(huì)創(chuàng)建一個(gè)新的對(duì)象來(lái)存儲(chǔ)它的局部變量,并將這個(gè)對(duì)象添加到保存的作用鏈上。同時(shí)創(chuàng)建一個(gè)新的更長(zhǎng)的表示函數(shù)調(diào)用的作用鏈。
對(duì)于嵌套的函數(shù),當(dāng)調(diào)用外部函數(shù)時(shí),內(nèi)部函數(shù)又會(huì)重新定義一遍。因?yàn)槊看握{(diào)用外部函數(shù)的時(shí)候,作用鏈都是不同的。內(nèi)部函數(shù)在每次定義的時(shí)候都有微妙的差別,每次調(diào)用的外部函數(shù)時(shí),內(nèi)部函數(shù)的代碼都是相同的,而且關(guān)聯(lián)的代碼的作用域也是不同的。
閉包
搞了這么久終于要講了,但是再將之前我們?cè)趤?lái)分析下作用域。
<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>
我們按照作用鏈來(lái)分析:
調(diào)用demo0, demo0()->查找name,未找到->f()查找,返回
調(diào)用demo1,demo1()->查找name,找到,返回
調(diào)用demo2,demo2()->查找nameg,未找到->f()查找nameg,未找到->window查找nameg找到,返回。
看這個(gè)例子:
<script>
function f()
{
var count = ;
return { counter:function() {
return count++;
},reset:
function()
{
return count = ;
}
}
}
var d = f();
var c = f();
console.log("d第一次調(diào)用:"+ d.counter());//
console.log("c第一次調(diào)用:"+ c.counter());// 互不影響
console.log("d第一次調(diào)用:"+ d.reset());//
console.log("c第二次調(diào)調(diào)用"+ c.counter());//
</script>
這個(gè)例子上大家可以看到,我做了一個(gè)計(jì)數(shù)和置零的操作。
創(chuàng)建了兩個(gè)f的對(duì)象實(shí)例d c,各有自己的作用域鏈,所以其值互不影響。當(dāng)c第二次調(diào)用時(shí),count值被保存了,因?yàn)閏對(duì)象并未被銷毀。明白這個(gè)例子后,后面的例子才比較好懂。
這個(gè)過(guò)程,大家應(yīng)該十分明了了。那么現(xiàn)在我們來(lái)看閉包問(wèn)題。我設(shè)置四個(gè)按鈕,點(diǎn)擊每個(gè)按鈕就返回響應(yīng)的名字。
<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>
點(diǎn)擊運(yùn)行,結(jié)果卻是都是btn5;
我們用剛才的分析在坐下,首先要調(diào)用匿名函數(shù)->查找i,未找到->btnInit(),找到i在for循環(huán)中。找到。我們知道只有函數(shù)調(diào)用結(jié)束才釋放,for中的i總是可見(jiàn)的,所以保留了最后的i值。那么如何解決呢。
解決i值在函數(shù)中不是總是可見(jiàn)的,那么我們就要使用函數(shù)的嵌套了,然后把i值傳進(jìn)去。
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));
}
}
看修改后的代碼,首先執(zhí)行第一次for,創(chuàng)建了一個(gè)對(duì)象,我們首先是執(zhí)行匿名函數(shù)->data_i,未找到->function(data_i)找到,然后再次執(zhí)行for有創(chuàng)建一個(gè)對(duì)象,閉包規(guī)則說(shuō)的互不影響。所以能得出正確的結(jié)果。
以上所述是小編給大家介紹的 JavaScript必知必會(huì)(九)function 說(shuō)起 閉包問(wèn)題的相關(guān)知識(shí),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 深入解析JavaScript的閉包機(jī)制
- 理解和運(yùn)用JavaScript的閉包機(jī)制
- javascript使用閉包模擬對(duì)象的私有屬性和方法
- JavaScript 閉包詳細(xì)介紹
- 幾句話帶你理解JS中的this、閉包、原型鏈
- 學(xué)習(xí)Javascript閉包(Closure)知識(shí)
- JavaScript中閉包的寫法和作用詳解
- Javascript閉包與函數(shù)柯里化淺析
- JS工作中的小貼士之”閉包“與事件委托的”阻止冒泡“
- JavaScript閉包實(shí)例詳解
- JS閉包、作用域鏈、垃圾回收、內(nèi)存泄露相關(guān)知識(shí)小結(jié)
- JavaScript 閉包機(jī)制詳解及實(shí)例代碼
相關(guān)文章
學(xué)習(xí)javascript面向?qū)ο?掌握創(chuàng)建對(duì)象的9種方式
這篇文章主要為大家介紹了創(chuàng)建對(duì)象的9種方式,幫助大家更好地學(xué)習(xí)javascript面向?qū)ο螅信d趣的小伙伴們可以參考一下2016-01-01
微信小程序?qū)崿F(xiàn)自定義動(dòng)畫彈框/提示框的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)自定義動(dòng)畫彈框/提示框的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
JavaScript中“基本類型”之爭(zhēng)小結(jié)
所謂“基本類型(primitive types)”的概念ECMAScript(V3,V5)中壓根就沒(méi)有,它只是將類型分為6種,感興趣的朋友可以參考下2013-01-01
去除JavaScript對(duì)象中空值和空對(duì)象的四種方式
開(kāi)發(fā)時(shí)遇到一個(gè)問(wèn)題,需要將對(duì)象中的空值和空對(duì)象去除,所以這篇文章主要給大家介紹了關(guān)于去除JavaScript對(duì)象中空值和空對(duì)象的四種方式,需要的朋友可以參考下2023-09-09
JavaScript實(shí)現(xiàn)省市聯(lián)動(dòng)過(guò)程中bug的解決方法
這篇文章主要為大家詳細(xì)介紹了解決JavaScript實(shí)現(xiàn)省市聯(lián)動(dòng)過(guò)程中的bug,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
js function定義函數(shù)的幾種不錯(cuò)方法
這篇文章主要介紹了js function定義函數(shù)的幾種方法,需要的朋友可以參考下2014-02-02

