进入主体前先理解一点小知识(js中最常见的两个关键字(自行了解):var function):
1,全局变量:下面例子中和c都是全局变量
<script>
var a=10;
function fn(){
c=20;
var d=30;
}
fn();
console.log(a,c);//10 20
console.log(d);//报错:d is not defined
</script>
在全局形势下(js域的直接作用域中)用var声明的变量和在function局部作用域中不用var声明的变量。
(function(){
var a = b =5;
})();
console.log(b);//5 局部内,var声明的只是a,所以a是局部变量,b是全局变量
console.log(a);//a is not defined
2,局部变量:上面的d就是局部变量,只能在function内使用。
3,自由变量:凡是跨了自己作用域(无论外跨内还是内跨外)的变量都是自由变量,下面的a是一个全局变量,但是在function被引用了,所以它就是一个自由变量(为什么可以跨域,后面我会用预解析机制来讲,局部作用域会像下面的这种一样由自己开始一层一层往上去找自己需要的变量,称为链式作用域)
<script>
var a=10;
function fn(){
console.log(a);
}
fn();
</script>
4,链式作用域是js中本身自然存在的一种获取变量的方式,原因是全局变量保存在空间中,只有在程序结束时才会被销毁,所以可以被访问,被使用,而局部变量在其作用域内使用完毕后是立马被销毁的(为避免浪费空间),所以正常自然情况下,外部是无法访问局部变量的,所以引出了闭包概念,做了闭包,外部就可以访问了。
网上关于闭包的概念和对应案例五花八门,但是请记住最原始最实用的一点,为什么会产生闭包:由于链式作用域内部可以访问外部数据,那么外部如何能访问内部数据呢,然后就是设置了一个函数,叫闭包。总觉得闭包这个词用的不好不够贴切,而且还从字面上容易误导大众,反过来说也只是一个名称而已
5,理想意义上的闭包:所有函数都构成一个闭包(就像你家的卫生间和公共卫生间一样,你可以去公共卫生间,但是外面的人却不能都来用你家的),真正意义上的闭包(从闭包实现的原理上)要符合两个条件:
A:即使创建它的上下文都已经销毁(局部作用域的销毁机制)它仍然存在
B:引入了自由变量。
理解各种闭包案例先整清函数能运行的一些方法(只介绍相关的,其他自行了解):
- 独立的函数a,直接执行:a();
- 嵌套b函数的a,直接执行:a();对于b函数,可以在a中直接执行:b(),也可以由a用return返回后,在外部被人调用。
- 用一些事件(鼠标事件,键盘事件)把控制权交给用户,达到人机交互的目的
- 用一些像定时器的神奇的方法去设定,让它多少时间后执行
再扯扯匿名函数
什么是匿名函数就是没有名字的函数,有三种状态
第一种:语法:function(){}自己单独存在意义不大,因为只是定义,没有执行,最常使用于,各种方法中,各种事件中,各种回调函数中。多好。简单直接,没有多余的变量名
setTimeout(function(){
console,log("hello world");
},1000);
$('#btn').on("click",function(){
console,log("hello world");
})
第二种:定义一个变量来存储它,这种常用于做大项目时需要一些辅助函数,可以把他们放到一个json数组中减少var的调用,看起来也整齐,注意return后面也可以跟json
var getPos=function(){//获取行的函数都用get开头,一般直接返回得到的值
return {
x:20+(20+100)*i,
y:20+(20+100)*j
}
}
var hasSpace=function(){//判断有没有用has,返回布尔值
for(var i=0;i<4;i++)
for(var j=0;j<4;j++){
if(board[i][j]==0)
return false;
return true;
}
}
var canMove=function(){//判断是否可以用can,返回布尔值
for(var j=0;j<4;j++)
for(var i=2;i>=0;i--)
if(board[i][j]!=0){
if(board[i+1][j]==0 || board[i+1][j]==board[i][j])
return true;
}
return false;
}
var isYou//balabala
第三种最神奇:自执行匿名函数(不需要调用):(function(参数){函数内容})(调用时的参数);
(function(a,b){console.log(a+b)})(2,3);//5
这一神器,用处可想而知。
理论很苍白,看代码,介绍四种情况下的闭包。
第一种:闭包经典语法:采用了return,返回闭包函数,以便外部调用
<script>
function fn1(){
var a=10;
/*闭包函数写法1
function fn2(){//闭包函数部分,用来获取母函数中的变量
console.log(a);
}
return fn2;//用来返回闭包内容
*/
return function fn2(){
console.log(a);
}
}
/*调用部分写法1
var result=fn1();//接收fn1的返回值
result();//并运行它
*/
fn1()();//10
</script>
这个闭包由两部分组成:
1,闭包函数的定义function fn2(){ }
2,闭包函数的返回
当然也造成一些问题,多次使用闭包,会占用很多空间,造成浪费。
第二种:不用return的
function foo(){
var a = 2;
function bar(){//闭包函数的定义
console.log(a);
}
bar();//闭包函数额执
}
foo();// 2
function fn(){
var i=6;
b=function(){//闭包函数的定义,b是个全局变量哦
console.log(i);
}
b();//闭包函数额执行
}
fn();// 6
这里闭包包括两部分:
1,闭包函数的定义function fn2(){ }
2,闭包函数的执行
第三种:用事件或神奇方法
function fn() {
var msg = "Hello, world!";
window.onload = function() {
console.log(msg);
}
}
fn();//Hello, world!
function fn1() {
var msg = "嗨,魔法世界!";
setTimeout(function() {
console.log(msg);
},2000);
}
fn1();//嗨,魔法世界!
此时闭包由一部分组成:
1,加了触发方法的函数(其实也是两步了,定义与执行。只是一个语句而已)
最后一种是自执行匿名函数和闭包这对cp。
var fn = (function() {
// 创建一个自执行匿名函数,持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个闭包函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
if (val === undefined) {
console.log(data[key]);
} // get
else {
console.log(data[key] = val );
} // set
}
})();
fn('x'); // undefined,因为只有一个参数就是data的属性名:data['x']没有赋值
fn('x', 1); //1 给出两个参数后者是赋值data['x']=1
fn('x'); //1 用了闭包data已经是个全局数据,而且上面已经赋值过了,它一直占着空间。
A:这不仅展示了闭包的(除了获取内部数据)另外一个作用:还能修改数据
解释:fn正常执行完后,内部的数据就会被销毁,所以在全局下你是不能怎么着它里面的东西的,用了闭包后,获取到你内部的数据,我给你返回到全局中,这样当我销毁时,不会影响到你,既然你在全局中已经稳定了,那我给你个值,后,你就永远有这个值了(全局数据的相对好处)
B:再来看看这个匿名自执行函数,用了一个变量来接收该函数中的数据,然后可以任意调用,传参也是一大特色,闭包中的参数能直接获取母函数的参数,是不是很666
总结:
1,闭包的写法之多完全不是因为它本身,而是因为函数的类型,函数的调用,多种一组合,这样那样就各种各样,只要搞清楚原理就好:函数有定义有执行才算是函数的作用嘛,其实按照用途分函数也有用来做判断条件的(值为布尔值),用来专门做功能函数的。。。。
2,闭包有好处有坏处,合理利用。
常见问题及改正:
A:循环+定时器类
// 毛病案例:
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i + ' ');
}, 100);
}//打印五个5
// 修正案例
for (var i = 0; i < 5; ++i) {
(function(a){//母函数
setTimeout(function () {//闭包
console.log(a + '');//转成字符串
}, 100);
})(i)//传入i参数
}//0 1 2 3 4 5
还有我抄的一些案例,下面这个才是正确的写法,
<html>
<head>
.test {
width:100px;
height:100px;
border:red 1px solid;
}
</head>
<body>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
</body>
</html>
<script type="text/javascript">
var k = document.getElementsByTagName("div");
for(var i = 0; i < k.length; i ++) {
(function(i){
k[i].onclick = function() {
console.log(i);
}
})(i)
}
</script>
总结for循环系列都是在for内加自执行匿名函数,在它内部又加获取数据的闭包函数
网上案例很多,大家可以去找找。
最后一个预解析机制,我只写总结吧(这篇耽误了好久,本来早写好了,就是总觉得不完善,不过实践还是留给大家比较好)
预解析机制:浏览器对js文件读取的第一个步骤就是预解析,原理就是浏览器在读取js文件时,并不是一个字一个字的往下读。
1,碰到域它才开始预解析(什么域呢就是比如 script,比如执行函数时(不是定义函数时))
2,预解析的过程:它先通篇从上往下去找var function等关键字开始进行预解析,解析结果就是把所有关键字对应的数据(var对应的变量,function对应的函数块)存放到对应的区域中(全局变量,全局函数就放到全局中,局部变量等放到局部中),预解析内的数据都是undefined,因为此时只是预解析,并没有赋值。
注意:碰到相同名称的,函数块会覆盖变量,下面的会覆盖上面的。
3,预解析完成后,就开始读取其对应的数值(只要是碰到表达式就会赋值),而且每读取一次,就去解析库中查找一次,读到方法类的东西就会去执行,代码块就不用管,直到遇到执行函数,就会中断读取,开始查找这个执行函数对应的函数块({}内就是是一个局部域),就像刚开始的预解析一样,把这部分数据存到这个局部中,完了后,并不是接着中断的那个继续,而是在这个局部域中读取里面的表达式等进行赋值,若是里面找不到这个变量就会一层一层往外找。完了才继续那个中断的往下找。