JS中的作用域(scope)

1、android:

// 注入js函數監聽
    private void addCloseClickListner() { 
        wv_Show_Document.loadUrl(
                "javascript:" +
                        "(function()" +
                        "{" +
                        "   var objs = document.getElementsByClassName(\"icon-3\"); " +
                        "   for(var i=0;i<objs.length;i++)  " +
                        "   {" +
                        "       var imgstr = objs[i].getElementsByTagName(\"a\")[0].href;" +
                        "       objs[i].οnclick= (function myclick(str)" +
                        "                        {" +
                        "                              return function(){ " +
                        "                                   window.imagelistner.openImage(str);" +
                        "                                   return false;" +
                        "                              };" +
                        "                        })(imgstr);" +
                        "  }" +
                        "}" +
                        ")()"
        );
    }

2、測試:

<!doctype html>
<html>
<head>
<script Language="JavaScript">
window.onload = function(){

    var objs = document.getElementsByClassName("icon-3"); 
    for(var i=0;i<objs.length;i++)  {
         //alert(objs[i].getElementsByTagName("a").href);
         var imgstr = objs[i].getElementsByTagName("a")[0].href;
         //console.log('--------------'+imgs[i]);
         objs[i].οnclick= (function myclick(str){
                return function(){ //inner function 
                    console.log(str);
                    return false;
                };
         })(imgstr);
    }

}



function getElementsByClassName2(fatherId,tagName,className){
    node = fatherId&&document.getElementById(fatherId) || document;
    tagName = tagName || "*";
    className = className.split(" ");
    var classNameLength = className.length;
    for(var i=0,j=classNameLength;i<j;i++){
        //創建匹配類名的正則
        className[i]= new RegExp("(^|\\s)" + className[i].replace(/\-/g, "\\-") + "(\\s|$)"); 
    }
    var elements = node.getElementsByTagName(tagName);
    var result = [];
    for(var i=0,j=elements.length,k=0;i<j;i++){//緩存length屬性
        var element = elements[i];
            while(className[k++].test(element.className)){//優化循環
            if(k === classNameLength){
                //result[result.length] = element;
                result[result.length] = element.getElementsByTagName("a")[0];
                break;
            }   
        }
        k = 0;
    } 

    return result;
}

function TEST(){
     console.log('TEST');
}    
</script>
<body onload="TEST">
<ul class="list-txt">
<li class="icon-3">
<a href="http://static.kocla.com/kocla/2015-08-04/8a20ae9c468ac4400146a85f5d17233e/document/8cfc0af88519489c835dcc297550ca65.pdf">產品使用說明頁-音樂寶寶v1.0</a>
</li>
<li class="icon-3">
<a   href="http://static.kocla.com/kocla/2015-08-04/8a20ae9c468ac4400146a85f5d17233e/document/37e181cd880146a09f89000831350450.jpg">電子元器件學習入門知識</a>
</li>
<li class="icon-3">
<a href="www.sina.com.cn">新浪網</a>
</li>
</ul>

</body>
</head>
</html>

依次點擊獲得<a>的href:
這裏寫圖片描述

3、其他
參考:JavaScript 循環添加事件時閉包的影響有哪些解法?
JavaScript 循環添加事件時閉包的影響有哪些解法?修改
網上搜到的關於該問題的一個方案是借一層函數避免問題
http://blog.csdn.net/victorn/article/details/3899261
不過到底還是很難理解.. 還有其他的方法去理解和解決嗎?
更新: 我草草套了一層函數還好也避開了
修改
舉報 1 條評論 分享 • 邀請回答
按投票排序
按時間排序
9 個回答

贊同
35
反對,不會顯示你的姓名
楊志,很遺憾很少有pure jser或pure js questio…
[已重置]、等風來、jude tony 等人贊同
很高興有一個純JS的問題。
1,@楊咖啡 說的JS傳參是傳值不傳址,其實不是這樣的。JS中傳參有兩種方式:by value and by sharing.
像C,C++,Java,他們傳參方式是by value 和 by reference。前者就是傳值,後者是傳址。而JS也是這樣的,前者是傳值,後者是傳址。
By value是對於原始數據類型,例如int,char之類的;而By sharing 和By reference是對於高級數據結構,如Object,struct之類。我們可以想象到一個Object或是struct 不能僅僅通過傳值進行傳參。
一個簡單的例子說明by reference和 by sharing的不同。

var bar;
var foo = bar;
bar = {'key' : 'value'};
console.log(foo , bar );

By sharing 中foo 是undefined , bar 是{‘key’ : ‘value’}; 而By reference 則應該兩者都是{‘key’ : ‘value’}。

  1. 其實LZ要理解這個問題,要明白JS中的作用域(scope)。
    每個函數在創建完成時,他有3個重要的內置屬性(property)也同時被創建。
    {
    AO //記錄function內的變量,參數等信息
    this // 就是在調用this.xx的時候的this
    scope // 指向外層函數AO的一個鏈(在實現的時候,可能通過數組來實現).
    }
    JS中,大家經常講的Scope其實是這樣:SCOPE=AO+scope.
    回到閉包的問題上:
    如果我們這樣寫這個程序:
for(var i =0; i<link.length; i++){ //window scope
link[i].onclick = function(){ alert(i); }; // inner function 
}

可以得到inner function的SCOPE是這樣的:

{
AO
this // 等於link[i]
scope // 指向window的記錄,包括我們需要的變量i
}
這個for循環會立即執行完畢,那麼當onclick觸發時,inner function查找變量 i 時,會在AO+scope中找,AO中沒有,scope中的變量i已經成爲了link.length.

利用大家所說的閉包寫這個程序:

//here is the window scope
for(var i =0; i<link.length; i++){ 

link[i].onclick = (function(i){ // outer function 
return function(){ //inner function 
alert(i);
};
})(i);
}

分析inner function的SCOPE:
{
AO // no important infomation
this // we don’t care it.
scope //outer function and window scope
}
outer function的SCOPE
{
AO // 包含參數i
this // don’t care it .
scope // window scope.
}

這時,如果inner function被觸發,他會從自己的AO以及scope(outer function的AO 和 window scope)中找尋變量i. 可以看到outer function的AO中已經包含了i,而且對於這個for循環,會有對應有N個(function(){})() 被創建執行。所以每個inner function都有一個特定的包含了變量 i 的outer function。

這樣就可以順利輸出0,1,2,3。。。。。。。。。

結論: 我們可以看到,閉包其實就是因爲Scope產生的,所以,廣義上來講,所有函數都是閉包。

另外,這裏面也包含了,this, function expression 和function declaration的區別,這裏就不一一講了。

  1. 另外一種方法:
    利用 dom onclick事件的bubble特性,也就是@xiiiiiin所講的弄個代理。

在link dom節點的父節點上定義onclick事件監聽。參數爲e(其他的名字也可以,但要有參數)。 這樣我們通過e.target就可以知道是那個子節點被click了,也可以做相應的處理。
這是一個比較好的方法。(閉包有時會產生內存泄漏)。

大概就說這麼多吧,還要上班呢。希望對LZ有用。如果哪裏錯了,也請多多批評指正。
發佈於 2012-01-13 18 條評論 • 作者保留權利

贊同
6
反對,不會顯示你的姓名
松鼠奧利奧,← 我廠招聘 Web 前後端開發
孟達、fankyC、楊興洲 等人贊同
我覺得最好的方式就是通過包裝一層函數來解決。

將原來的
alink.onclick = function(){alert(i)};
改成:
(function(i) { alink.onclick = function(){alert(i)}; })(i);

我覺得這是最好的方法了,js 中只有 function 纔會劃分作用域(和 python 有點像),if/else、for 循環都不會劃分作用域,所以原來的方式六次循環引用的都是同一個變量 i,由於閉包綁定到 function 中去。
現在包裝了一層之後,i 被傳遞到內層的匿名函數 local 作用域中去,所以六次循環都會建立獨立的 i (因爲是六個不同的作用域)。
發佈於 2012-01-12 5 條評論 • 作者保留權利

贊同
2
反對,不會顯示你的姓名
依雲,擺脫過去,夢想漸近。四處展望,行業發達…
死跑龍套的、松鼠奧利奧 贊同
不看那文章你的問題還真難理解。
你把參數進去嘛:

alink.onclick = (function(i){
return function(){
alert(i);
};
})(i);

另外我不喜歡 onxxx 屬性。

PS: 知乎用富文本編輯器弄得我貼代碼都麻煩,另外,答案的後部分第 N 次消失看不到了。

編輯於 2012-01-12 3 條評論 • 作者保留權利

贊同
3
反對,不會顯示你的姓名
楊奇超,你快長肉啊
樑新宇、泛泛而談、陳框框 贊同
這跟JS函數的傳參方式和事件的賦值方式有關。
1、JS函數傳參是傳值不傳址的。
2、onclick的值應該給一個函數聲明,事件觸發時只會傳一個event參數給聲明的函數。

如果在循環中使用alink[i].onclick = function() { alert(i); };
i 不是這個匿名函數的參數,是傳址進去的,當onclick事件觸發的時候循環已經結束了,i 已經是最後一個值了。
如果聲明 function(i) { alert(i); }
那這個 i 就指代了 event,這時候事件觸發的時候只會彈出觸發的事件名。

而使用alink[i].onclick = (function(_i) { return function() { alert(_i); } })(i);
這裏是執行外層的匿名函數,返回內層的這個匿名函數傳給onclick。
這裏注意外層函數是立即執行的,帶一個參數,是我們傳給它的,而不是事件觸發器。
內層函數是不帶參數的,事件執行時觸發器會傳給它一個event值。
對循環中的每一個 i 都會生成一個匿名函數,i 作爲生成的匿名函數的參數,是傳值的。
相當於循環中當 i = 2 的時候,生成了這樣一個函數:function() { alert(2); }; 賦值給了alink[2].onclick,即 alink[2].onclick = function() { alert(2); };
這纔是我們想要的。

PS:閉包只是個手法,而不是解決問題的核心所在。
這種手法跟下面的方法是等價的,而下面並沒有用閉包。

var al = function(param) {
return function() {
alert(param);
}
}

循環中alink[i].onlick = al(i);
編輯於 2012-01-12 添加評論 • 作者保留權利

贊同
1
反對,不會顯示你的姓名
彬仔,碼農
whiletrue 贊同
呃……這例子,更偏向於變量作用域的問題吧
發佈於 2012-01-13 1 條評論 • 作者保留權利

贊同
0
反對,不會顯示你的姓名
匿名用戶
在這裏我覺得排名第一的回答太過於晦澀,一般人根本看不懂。
在這裏我提出一個比較有意思的猜想,
//————————————————————————————————————————
//貼出代碼

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>無標題文檔</title>
<style>
li{height:20px;background:#222;width:200px;border-bottom:2px solid #fff;}
</style>
</head>

<body>
<ul id='ul1'>
<li></li>
<Li></Li>
<li></li>
<Li></Li>
</ul>
<script>
var oUl1=document.getElementById('ul1');
//alert(oUl1);
var aLi=document.getElementsByTagName('li');
var length=aLi.length;
for(var i=0;i<length;i++)
{

aLi[i].οnclick=(function (index){
var y=i;
return function (){

// alert(index);
alert(y);
}; 
}
)(i);
}


//提出猜想,引用次數加一變成2不再有跨作用域問題

//猜測正確
</script>
</body>
</html>

//————————————————————————————————————
如你所見,index緩衝值就是大多數人所採用的方法,這是一種非常靠譜的做法,但是很少有人能說的清爲什麼,而是用所謂的專業來讓你困惑。

//————————————————————————————————————
其實這個也是偶然間看到垃圾回收機制,引用次數所提出的猜想,
正常情況下,如果採用var y=i,然後繼續在return fn裏引用的話,確實只能引用到最後一個值,因爲大多數情況下,0,1,2…..這些序列號只會被一個變量保存,大家也不會想到用另一個變量再次保存,
但是現在我們用y再次引用,讓它的次數變成了2,這樣所謂的跨作用域就不存在了,我們可以引用到每個值。
//————————————————————————————
當然了這只是個猜想,只是在這裏得到了驗證,至於能否繼續,還要繼續考察。
發佈於 2014-10-28 1 條評論 • 作者保留權利

贊同
0
反對,不會顯示你的姓名
xiiiiiin,WEB DEVELOPER…
做個代理嘛。
循環添加每個節點的attribute,然後在外層elem綁一個事件。
能不用閉包就不用嘛
發佈於 2012-01-12 3 條評論 • 作者保留權利
包義德,編程,文學
重新思考了一下這個問題,它的關鍵在於弄清JavaScript變量作用域和作用域鏈,前面的回答似乎都沒有解釋清楚。

《JavaScript: The Good Parts》中提到了這個問題,看這個例子:

// BAD EXAMPLE
var addActions = function (nodes) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
// END BAD EXAMPLE

解釋是這樣的:
函數的本意是給每個事件處理器不同的 i 。但它未能達到目的,因爲事件處理器函數綁定了 i 本身,而不是函數在構造時的變量 i 的值。

之所以是這樣的,看過《JavaScript: The Definitive Guide》就會明白:
所有的JavaScript函數都是閉包:它們都是對象,都關聯到作用域鏈。每次調用JavaScript函數的時候,都會爲之創建一個新的對象來保存局部變量,並把這個對象添加至作用域鏈中(簡單理解:作用域鏈就是對象列表)。
這個糟糕的例子中function (e) { alert(i); }都會在同一個函數調用中定義,因此它們共享變量 i 。也就是說它們的作用域鏈裏面的 i 都是同一個 i 。即關聯到閉包的作用域鏈都是“活動的”。

《JavaScript: The Good Parts》當然也給出瞭解決方案:

var addActions = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};

for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = helper(i);
}
};

解釋: 在循環之外創建一個輔助函數,而不是在循環中創建函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章