JS中的作用域(scope)

来源:互联网 发布:大数据 公司 编辑:程序博客网 时间:2024/05/22 14:39

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].onclick= (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].onclick= (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 scopelink[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 scopefor(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].onclick=(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 EXAMPLEvar 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);}};

解释: 在循环之外创建一个辅助函数,而不是在循环中创建函数。

0 0
原创粉丝点击