您的位置:首页 > 其它

自己实现一个select(涉及闭包)

2016-12-24 15:28 274 查看
前言:
        写这一篇博客是因为在做自己在项目中发现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;
}())




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐