自己实现一个select(涉及闭包)
2016-12-24 15:28
274 查看
前言:
写这一篇博客是因为在做自己在项目中发现ie中select里面的内容不能用text-algin居中,所以决定自己写一个原生的select,不过在写的过程中发现了很多有趣的问题,遇到了循环闭包中出现的常见错误,然后自己决定写一篇博客来记录一下。
代码:
样式上是用input,button配合ul/li实现。(这里就不用我多说了)
[b]Js思路:[/b]
当我第一次去循环绑定的时候是这样做的:
这样去实现的时候你会发现点击根本没效果,因为那个时候i为5了。
出现的原因在于:for里面首先是为每个按钮去绑定一个click事件,但是这里面的函数只有当被触发的时候才会执行,所以这里函数绑定完之后就继续往下走了,当 i 退出循环的时候已经是5了,再当我们去点击的时候,执行函数的时候发现,这个函数的作用域里面并没有 i ,这个时候其实就是一层闭包,所以它就会去它父作用域里面去找这个 i 值,这个时候的 i 就是全局作用域里面的
i (5),所以不管你点什么都没效果。 (关于作用域的知识点,请看我的另一篇博客http://blog.csdn.net/liuzijiang1123/article/details/55258576)
改动:
主要运用的知识点是匿名函数的自执行,函数传参是传值,闭包的作用域。
(下面会有匿名函数的知识点,可以看一看,最好先看下面然后再看我的总结)
我总结一下:
这里我们首先是用匿名函数自执行,执行click事件里面的第一个函数(由于它会再返回一个函数,这个就是我们添加的另一层闭包,这个函数是我们真正点击的时候发生的),将 i 通过函数参数传递给x,由于是传递的参值不传址,所以这里会开辟4个x的内存空间,数值上分别是1,2,3,4。由于又添加了一层闭包,由于闭包的特性,return的这个函数里面用到的x的作用域会一直保存,所以当点击的时候它会去它的父作用域去寻找,这个时候的父作用域不再是全局作用域里面的那个
i 了,而是我们第一次执行的那个函数里面的x,这样就能找到我们想要的那个值。[b] [/b]
当然上面只是联动能够点击每个”select”能出现相应的下拉框,距离我们想要的最后效果还是有差距的。
比如:当我们点击一个select的时候,其他的select下拉框会自动消失。
PS:对于上面改动的地方第一次看如果不清楚的,可以先了解一下我们下面说的一些内容,看完后再回过头去看上面的改动地方,这样方便理解。
更新于2017/6/1
今天发现闭包问题还能用另一种方法解决:
for (var i = 1; i <= 10; i++) {
var doc = document.createElement('button');
doc.innerHTML = i;
//此方法系统消耗最小
(doc.onclick = function() {
console.log(arguments.callee.x);//arguments.callee 是获取当前调用函数的本体
}).x = i;
document.body.appendChild(doc);
自己的理解是:函数其实也是一种对象,所以给其添加一个x属性,这个x属性为当前的i值(1,2,3.....10),所以是将数值保存在对象的属性中去,在我去调用的时候使用agruments.callee.x,其实是调用其函数本身的 x属性。
很妙!~
/*****************************************************************************/
[b]函数声明和函数表达式:[/b]
[b] [/b]
[b]函数声明:[/b]
function init(){
//do somthing
}
[b]函数表达式:[/b]
var a=function(){
//do somthing
}
[b]区别:[/b]
函数声明必须有标识符,也就是常说的函数名;函数表达式可以省略函数名
js的解析器对函数声明与函数表达式并不是一视同仁地对待的。对于函数声明,js解析器会优先读取,确保在所有代码执行之前声明已经被解析,而函数表达式,如同定义其它基本类型的变量一样,只在执行到某一句时也会对其进行解析,所以在实际中,它们还是会有差异的,具体表现在,当使用函数声明的形式来定义函数时,可将调用语句写在函数声明之前,而后者,这样做的话会报错。
Eg:
对 init();
function init(){
//do somthing
}
错init()
init=function(){
//do somthing
}
/*****************************************************************************/
[b]匿名函数:[/b]
形如下面这样的函数称为匿名函数.
function(){
//do something
};
(当然这样会报错,因为前面刚说过函数声明必须有“函数名”)
[b] [/b]
[b] [/b]
[b]匿名函数的“函数名”:[/b]
匿名函数可以依附于一个变量,并且这个变量名就是这个匿名函数的名字。
var a = function(){
//do something
}
[b] [/b]
[b]匿名函数的执行方式:自执行和匿名函数作为参数传递给其他函数[/b]
[b]a.自执行[/b]:
var a = function(b){//可以传参数
Console.log(b);//10
}(10) //输出10
或者
(function(){
Console.log(10)
})()
//按照ECAMScript的规定,函数声明是必须要有名字的,但是我们用括号扩起来那么这个函数就不再是一个函数声明了,而是一个函数表达式
------------------------------------------------------------------------------------------------------------------------------
Tips:当然上述方式你只能执行一次
var a = function(b){//可以传参数
console.log(b);//10
}(10) //10
a();//undefined;
如果将一个自执行的匿名函数并且没有返回值,赋值给一个变量那么这个变量的值就是undefined。因为这个函数在赋值之前已经执行完了,而这个函数没有返回值,所以就是undefined,如果有返回值,那么这个变量的值就是那个匿名函数的返回值.
var a = function(b){//可以传参数
console.log(b);//10
return function(){
console.log(20);
}
}(10) //输出10
a();//输出12;
对于上面这个例子当执行到var a=的时候,其实是var a=function(){ console.log(20) }
----------------------------------------------------------------------------------------------------------------------------
[b] [/b]
[b]b.匿名函数作为参数传递给其他函数:[/b]
[b] [/b]
function Add(a,b){
return a()+b();
}
Add(
function(){
return 1;
},
function(){
return 2;
}
);
//我们经常用到的click方法也是这样的:
$(“#id”).click(function(){
//do somthing;
})
当然这样是我们去点击的时候才去执行,但是我们这里也可以让它自执行(自执行后再点click就没反应了)
$(“#id”).click(function(){
//do somthing;
}())
写这一篇博客是因为在做自己在项目中发现ie中select里面的内容不能用text-algin居中,所以决定自己写一个原生的select,不过在写的过程中发现了很多有趣的问题,遇到了循环闭包中出现的常见错误,然后自己决定写一篇博客来记录一下。
代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Test</title> <style> .caret{ display:inline-block; width:0; height:0; margin-left:2px; vertical-align:middle; border-top:6px solid; border-right:4px solid transparent; border-left:4px solid transparent; } .dropdown-menu{ position: absolute; left: 214px; top:23px; z-index: 1000; display:none; min-width: 157px; padding:0; margin: -2px 0 0 0; list-style: none; background-color: #ffffff; border:1px solid #ccc; height:50px; overflow:auto; } .dropdown-menu a{ outline:none; text-decoration:none; color:#313131; } .dropdown-menu li{ text-align:center; } .dropdown-menu li:hover{ background-color:#c4c4c4; } .mytext{ float:left; height: 18px; text-align:center; background-color: #ffffff; color: #555555; border: 1px solid #cccccc; font-size: 14px; line-height:10px; padding-bottom:-20px; } .mybtn{ float:left; height: 22px; border: 1px solid #cccccc; margin-left:-1px; } .group{ float:right; display: inline-block; margin-left:40px; font-size: 0; white-space: nowrap; vertical-align: middle; } .unitdiv{ position:relative; height:25px; border:0; margin-top:20px; right:10px; } .unitdiv label{ float:left; } .picdiv{ display:block; width:400px; margin-left:110px; } .pictext{ border-bottom:2px solid #c4c4c4; padding:10px 0 10px 0; } </style> </head> <body> <div class="pictext"> <span>抓图参数</span> </div> <div class="picdiv"> <div class="unitdiv"> <label>图片格式:</label> <div class="group"> <input type="text" id="mytext1" class="mytext" disabled="true"></input> <button type="button" class="mybtn"id="mybtn1" ><span class="caret"></span></button> </div> <ul class="dropdown-menu" id="menu1"> <a href="###"><li>1</li></a> <a href="javascript:void(0);"><li>2</li></a> <a href="javascript:void(0);"><li>3</li></a> </ul> </div> <div class="unitdiv"> <label>图片质量:</label> <div class="group"> <input type="text" id="mytext2" class="mytext" disabled="true"></input> <button type="button" id="mybtn2" class="mybtn" ><span class="caret"></span></button> </div> <ul class="dropdown-menu" id="menu2"> <a href="javascript:void(0);"><li>1</li></a> <a href="javascript:void(0);"><li>2</li></a> <a href="javascript:void(0);"><li>3</li></a> </ul> </div> <div class="unitdiv"> <label>抓图时间间隔:</label> <div class="group"> <input type="text" id="mytext3" class="mytext" disabled="true"></input> <button type="button" id="mybtn3" class="mybtn" ><span class="caret"></span></button> </div> <ul class="dropdown-menu" id="menu3"> <a href="javascript:void(0);"><li>1</li></a> <a href="javascript:void(0);"><li>2</li></a> <a href="javascript:void(0);"><li>3</li></a> </ul> </div> <div class="unitdiv"> <label>抓图数量:</label> <div class="group"> <input type="text" id="mytext4" class="mytext" disabled="true"/> <button type="button" id="mybtn4" class="mybtn" ><span class="caret"></span></button> </div> <ul class="dropdown-menu" id="menu4"> <a href="javascript:void(0);"><li>1</li></a> <a href="javascript:void(0);"><li>2</li></a> <a href="javascript:void(0);"><li>3</li></a> </ul> </div> <div class="unitdiv"> <label>抓图数量:</label> <div class="group"> <select style="width:185px" id="select_test"> <option value="2" selected="selected">1</option> <option value="1" >2</option> <option value="3" >3</option> </select> </div> </div> </div> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> $(function(){ initselect(); }) function initselect(){ for(var i=1;i<5;i++) { /*$("#mybtn"+i).click(function(x){ $(".dropdown-menu").removeClass("tmCover"); $("#menu"+i).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); })*/ $("#mybtn"+i).click(function(){ var tmp=i; return function(){ $(".dropdown-menu").removeClass("tmCover"); $("#menu"+tmp).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); //$("#menu"+x).toggle(); $(".tmCover").toggle(); } }()) $("#menu"+i+" li").click(function(x){ return function(){ //$(".dropdown-menu li").removeClass("trCover"); //$(this).addClass("trCover"); //select=$(".trCover").text(); console.log($(this).text()); //console.log(select); $("#mytext"+x).val(""); //$("#mytext"+x).val(select); $("#mytext"+x).val($(this).text());//使用this来获取值,不用去添加trCover,减少代码量 setTimeout("stop();",50); } }(i)) } } function stop(){ $(".dropdown-menu").hide(); } </script> </body> </html>
样式上是用input,button配合ul/li实现。(这里就不用我多说了)
[b]Js思路:[/b]
当我第一次去循环绑定的时候是这样做的:
for(var i=1;i<5;i++) { $("#mybtn"+i).click(function( ){ $(".dropdown-menu").removeClass("tmCover"); $("#menu"+i).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); console.log(i);//5 })}
这样去实现的时候你会发现点击根本没效果,因为那个时候i为5了。
出现的原因在于:for里面首先是为每个按钮去绑定一个click事件,但是这里面的函数只有当被触发的时候才会执行,所以这里函数绑定完之后就继续往下走了,当 i 退出循环的时候已经是5了,再当我们去点击的时候,执行函数的时候发现,这个函数的作用域里面并没有 i ,这个时候其实就是一层闭包,所以它就会去它父作用域里面去找这个 i 值,这个时候的 i 就是全局作用域里面的
i (5),所以不管你点什么都没效果。 (关于作用域的知识点,请看我的另一篇博客http://blog.csdn.net/liuzijiang1123/article/details/55258576)
改动:
for(var i=1;i<5;i++) { $("#mybtn"+i).click(function(x){ console.log(i);//1,2,3,4 console.log(x);//1,2,3,4 return function(){ console.log(i);//4....... console.log(x);//1,2,3,4 $(".dropdown-menu").removeClass("tmCover"); $("#menu"+x).addClass("tmCover") $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); } }(i)) } //相当于:把上面这个函数拆成2个阶段,一个是初始化的时候,一个是执行的时候。 $("#mybtn"+1//1,2,3,4).click(function(){ console.log(x);//1,2,3,4 $(".dropdown-menu").removeClass("tmCover"); $("#menu"+x).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); }) //Or: $("#mybtn"+i).click(function(){ var tmp=i; return function(){ $(".dropdown-menu").removeClass("tmCover"); $("#menu"+tmp).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); } }()) $("#mybtn"+1//1,2,3,4).click(function(){ console.log(tmp);//1,2,3,4 $(".dropdown-menu").removeClass("tmCover"); $("#menu"+tmp).addClass("tmCover"); $(".dropdown-menu").not(".tmCover").hide(); $(".tmCover").toggle(); })
主要运用的知识点是匿名函数的自执行,函数传参是传值,闭包的作用域。
(下面会有匿名函数的知识点,可以看一看,最好先看下面然后再看我的总结)
我总结一下:
这里我们首先是用匿名函数自执行,执行click事件里面的第一个函数(由于它会再返回一个函数,这个就是我们添加的另一层闭包,这个函数是我们真正点击的时候发生的),将 i 通过函数参数传递给x,由于是传递的参值不传址,所以这里会开辟4个x的内存空间,数值上分别是1,2,3,4。由于又添加了一层闭包,由于闭包的特性,return的这个函数里面用到的x的作用域会一直保存,所以当点击的时候它会去它的父作用域去寻找,这个时候的父作用域不再是全局作用域里面的那个
i 了,而是我们第一次执行的那个函数里面的x,这样就能找到我们想要的那个值。[b] [/b]
当然上面只是联动能够点击每个”select”能出现相应的下拉框,距离我们想要的最后效果还是有差距的。
比如:当我们点击一个select的时候,其他的select下拉框会自动消失。
PS:对于上面改动的地方第一次看如果不清楚的,可以先了解一下我们下面说的一些内容,看完后再回过头去看上面的改动地方,这样方便理解。
更新于2017/6/1
今天发现闭包问题还能用另一种方法解决:
for (var i = 1; i <= 10; i++) {
var doc = document.createElement('button');
doc.innerHTML = i;
//此方法系统消耗最小
(doc.onclick = function() {
console.log(arguments.callee.x);//arguments.callee 是获取当前调用函数的本体
}).x = i;
document.body.appendChild(doc);
自己的理解是:函数其实也是一种对象,所以给其添加一个x属性,这个x属性为当前的i值(1,2,3.....10),所以是将数值保存在对象的属性中去,在我去调用的时候使用agruments.callee.x,其实是调用其函数本身的 x属性。
很妙!~
/*****************************************************************************/
[b]函数声明和函数表达式:[/b]
[b] [/b]
[b]函数声明:[/b]
function init(){
//do somthing
}
[b]函数表达式:[/b]
var a=function(){
//do somthing
}
[b]区别:[/b]
函数声明必须有标识符,也就是常说的函数名;函数表达式可以省略函数名
js的解析器对函数声明与函数表达式并不是一视同仁地对待的。对于函数声明,js解析器会优先读取,确保在所有代码执行之前声明已经被解析,而函数表达式,如同定义其它基本类型的变量一样,只在执行到某一句时也会对其进行解析,所以在实际中,它们还是会有差异的,具体表现在,当使用函数声明的形式来定义函数时,可将调用语句写在函数声明之前,而后者,这样做的话会报错。
Eg:
对 init();
function init(){
//do somthing
}
错init()
init=function(){
//do somthing
}
/*****************************************************************************/
[b]匿名函数:[/b]
形如下面这样的函数称为匿名函数.
function(){
//do something
};
(当然这样会报错,因为前面刚说过函数声明必须有“函数名”)
[b] [/b]
[b] [/b]
[b]匿名函数的“函数名”:[/b]
匿名函数可以依附于一个变量,并且这个变量名就是这个匿名函数的名字。
var a = function(){
//do something
}
[b] [/b]
[b]匿名函数的执行方式:自执行和匿名函数作为参数传递给其他函数[/b]
[b]a.自执行[/b]:
var a = function(b){//可以传参数
Console.log(b);//10
}(10) //输出10
或者
(function(){
Console.log(10)
})()
//按照ECAMScript的规定,函数声明是必须要有名字的,但是我们用括号扩起来那么这个函数就不再是一个函数声明了,而是一个函数表达式
------------------------------------------------------------------------------------------------------------------------------
Tips:当然上述方式你只能执行一次
var a = function(b){//可以传参数
console.log(b);//10
}(10) //10
a();//undefined;
如果将一个自执行的匿名函数并且没有返回值,赋值给一个变量那么这个变量的值就是undefined。因为这个函数在赋值之前已经执行完了,而这个函数没有返回值,所以就是undefined,如果有返回值,那么这个变量的值就是那个匿名函数的返回值.
var a = function(b){//可以传参数
console.log(b);//10
return function(){
console.log(20);
}
}(10) //输出10
a();//输出12;
对于上面这个例子当执行到var a=的时候,其实是var a=function(){ console.log(20) }
----------------------------------------------------------------------------------------------------------------------------
[b] [/b]
[b]b.匿名函数作为参数传递给其他函数:[/b]
[b] [/b]
function Add(a,b){
return a()+b();
}
Add(
function(){
return 1;
},
function(){
return 2;
}
);
//我们经常用到的click方法也是这样的:
$(“#id”).click(function(){
//do somthing;
})
当然这样是我们去点击的时候才去执行,但是我们这里也可以让它自执行(自执行后再点click就没反应了)
$(“#id”).click(function(){
//do somthing;
}())
相关文章推荐
- 自己写一个java.lang.reflect.Proxy代理的实现
- iOS自己实现一个单例
- Objective-C如何自己实现一个基于数组下标的属性访问模式
- 扔掉log4j、log4j2,自己动手实现一个多功能日志记录框架,包含文件,数据库日志写入,实测5W+/秒日志文件写入,2W+/秒数据库日志写入,虽然它现在还没有logback那么强大
- 自己动手实现一个队列LGQueue(刚刚)
- 10 分钟实现一个自己的服务器监控器
- 将十进制整形数转换成二进制,然后通过字符型输出 自己实现的一个简单的例子
- 实现过程全纪录——自己写一个“微信朋友圈”(包括移动端与PC端)
- java事务全解析(四)--成功的案例(自己实现一个线程安全的TransactionManager)
- 自己实现一个JPA中的分页
- 基于Nginx实现一个自己的HTTP模块--发送磁盘中的文件
- C++11__自己实现的一个swap类
- 模拟一个自己的jquery(二) 简单实现$
- 创建一个数组, 实现函数init()初始化数组、 实现empty()清空数组、 实现reverse()函数完成数组元素的逆置。 要求:自己设计函数的参数,返回值。
- 深入应用c++11读书笔记--使用c++11让程序更简洁、更现代-2.自己实现一个支持范围for循环的类~
- 自己动手实现一个MVVM库
- C++面试题(二)——自己实现一个String类
- 【转】实现一个自己的promise
- 自己实现的一个简单的相册效果
- 自己手动实现一个简单的线程池