一.JS闭包
1.定义:
闭包是一个闭合容器,我们可以认为闭包是一个对象{key:value}
2.闭包形成条件:
缺一不可:
函数嵌套
内部函数引用外部函数局部变量
外部函数调用
3.作用:
延长外部函数局部变量的声明周期
从外部访问函数内部的局部变量
4.闭包缺点:
占用内存
不及时清除会造成内部泄漏
5.闭包示例:
1 functionfun(){2 var num = 123;3
4 return functionfun2(){ //函数嵌套 ==》满足15 console.log(num); //调用外部函数的局部变量num ==》满足26 }7 }8
9 var f =fun();10
11 f(); //外部函数调用 ==》满足312
13 f = null; //清除防止内存泄漏
分析:
当在var f = fun();执行结束时应该销毁函数fun以及内部变量num,但是由于执行到f()时需要使用fun()的局部变量num,则虽然fun()被销毁了但是内部num一直保留,直到f = null才结束。
6.使用闭包的场景:
解决循环遍历加事件监听的问题
将内部函数返回出来
将函数作为实参传递给另一个函数调用
(1)解决循环事件监听问题:
1 var btns = document.getElementsByTagName('button');2 console.log(btns);//btns为伪数组:具有数组的一般特性,可以下标取值也有length属性,但没有数组的一般方法不能排序等等
3
4 console.log(Array.prototype.slice.call(btns)); //将伪数组转换为一般数组
5 for (var i = 0; i < btns.length; i++) {6 btns[i].onclick = function(){ //异步方法
7 console.log(i);8 }9
10 }
分析:由于页面加载循环执行完毕,当按钮点击触发才会调用异步方法此时i值错误。
1 var btns = document.getElementsByTagName('button');2 console.log(btns); //btns为伪数组:具有数组的一般特性,可以下标取值也有length属性,但没有数组的一般方法不能排序等等
3
4 console.log(Array.prototype.slice.call(btns)); //将伪数组转换为一般数组
5 for (var i = 0; i < btns.length; i++) {6 (function(i) {7 btns[i].onclick = function () { //异步方法
8 console.log(i);9 }10 })(i);11
12 }
分析:此时按钮点击后打印i寻找上层作用域i发现正确。
(2)将内部函数返回出来:
1 functionfun(){2 var num = 123;3
4 return functionfun2(){ //作为内部函数返回出来5 console.log(num);6 }7 }8
9 var f =fun();10
11 f();12
13 f = null;
(3)作为实参传递给另一个函数调用:
1
2
3 functionfun(msg,time){4 console.log("fun执行开始");5 alert("fun执行开始");6 setTimeout(function(){7 console.log(msg);8 },time);9 console.log("fun执行结束");10 }11
12 fun("xxx",2000);
补:
[1].同步与异步
同步:
同步会阻塞后续代码运行
同步没有回调函数
异步:
异步不会阻塞代码运行
异步必须有回调函数
[2].使用闭包自定义JS模版
1 (function(window){2
3 var str = "abc";4 var num = 123;5
6 functiongetstr(){7 returnstr;8 }9
10 functiongetnum(){11 returnnum;12 }13
14 //将闭包挂载到window的我们自定义的属性myModule上
15 window.myModule ={16 getstr:getstr,17 getnum:getnum18 }19
20 })(window)21
22 console.log(myModule.getstr()); //abc
7.例子
例1:
1 var name = "The Window";2 var object ={3
4 name: "My Object",5
6 getNameFunc: function() {7
8 return function() {9
10 return this.name;11 };12 },13
14 bb: {15 name: "bb",16 getNameFunc: function() {17 return this.name;18
19 }20 }21 };22 console.log(object.getNameFunc()()); //The Window
23
24 console.log(window.object.bb.getNameFunc()); //bb
分析:
1. console.log(object.getNameFunc()()); 相当于打印 window.object.getNameFunc()() 由于先执行
object.getNameFunc() 返回一个匿名函数,再执行 window.xxx() 最后得到this是指向外层的 window.name
2.同理可得bb
例2:
1 var name2 = "The Window";2 var object2 ={3 name2: "My Object",4 getNameFunc: function() {5 var that = this; //缓存this6 return function() {7 return that.name2;
8 };9 }10 };11 console.log(window.object2.getNameFunc()()); //My Object
分析:由于形成了闭包导致that没有被释放所以得到My Object
例3:
1 functionfun(n, o) {2 console.log(o)3 return{4 fun: function(m) {5 returnfun(m, n)6 }7 }8 }9 var a = fun(0)//undefined
10 a.fun(1) //0
11 a.fun(2) //0
12 a.fun(3) //0
13
14 var b = fun(0).fun(1).fun(2).fun(3) //undefined,0,1,2
15
16 var c = fun(0).fun(1)//undefined,0
17 c.fun(2) //1
18 c.fun(3) //1
分析:
a第一次赋值为一个Object对象,然后指针一直不变;b的指针一直改变;c一开始改变一次之后不变
1 var a = fun(0); ==> n=0,o=undefined; a={fun:function(m){ return fun(m,0)}}2 a.fun(1); ==> function(1){return fun(1,0)} ==> fun(1,0) ==> n=1,o=0
3 a.fun(2); ==> function(2){return fun(2,0)} ==> fun(2,0) ==> n=2,o=0
4 a.fun(3); ==> function(3){return fun(3,0)} ==> fun(3,0) ==> n=3,o=0
5
6
7 var b = fun(0). ==> n=0,o=undefined; b={fun:function(m){ return fun(m,0)}}8 fun(1). ==> function(1){return fun(1,0)} ==> fun(1,0) ==> n=1,o=0 ==> b={fun:function(m){ return fun(m,1)}}9 fun(2). ==> function(2){return fun(2,1)} ==> fun(2,1) ==> n=2,o=1 ==> b={fun:function(m){ return fun(m,2)}}10 fun(3) ==> function(3){return fun(3,2)} ==> fun(3,2) ==> n=3,o=2 ==> b={fun:function(m){ return fun(m,3)}}11
12 var c = fun(0). ==> n=0,o=undefined; b={fun:function(m){ return fun(m,0)}}13 fun(1); ==> function(1){return fun(1,0)} ==> fun(1,0) ==> n=1,o=0 ==> b={fun:function(m){ return fun(m,1)}}14 c.fun(2) ==> function(2){return fun(2,1)} ==> fun(2,1) ==> n=2,o=1
15 c.fun(3) ==> function(3){return fun(3,1)} ==> fun(3,1) ==> n=3,o=1
例4:
1 functionFoo() {2 getName = function () { console.log(1); };3 return this;4 }5 Foo.getName = function () { console.log(2);};6 Foo.prototype.getName = function () { console.log(3);};7 var getName = function () { console.log(4);};8 function getName() { console.log(5);}9
10 //请写出以下输出结果:
11 Foo.getName(); //2
12 getName(); //4
13 Foo().getName(); //1
14 getName(); //1
15 new Foo.getName(); //2
16 new Foo().getName(); //3
17 new new Foo().getName(); //3
分析:
1 注:2 1.对于同名的变量和方法,JS引擎会先将同名函数声明覆盖了同名变量的声明,然后定义同名变量3 2.new必须与函数在一起,生成实例化对象4 3.new与最近的小括号匹配5
6 function Foo() {...} ==>全局Foo函数7 Foo.getName = ... ==>Foo函数对象静态方法getName8 Foo.prototype.getName = ... ==>Foo原型对象中有getName属性9 var getName ==>全局变量getName10 function getName(){...} ==>全局方法11
12 执行步骤:13
14 ==>1.声明全局Foo()15 ==>2.声明全局getName()覆盖了getName变量声明16 ==>3.定义Foo.getName = function () { console.log(2);}; (Foo函数对象静态方法getName)17 ==>4.定义Foo.prototype.getName = function () { console.log(3);}; (Foo原型对象中有getName属性)18 ==>5.定义全局getName变量 = function () { console.log(4);};19 ==>6.执行Foo.getName(); Foo函数对象的静态方法执行 ==> 2
20 ==>7.执行全局getName(); ==> 4
21 ==>8.执行Foo().getName(); Foo函数对象执行完 ==> getName = function () { console.log(1); }; 修改了全局getName()22 ==> 返回this指向window ==> 执行window.getName() ==> 1
23 ==>9.执行全局getName(); ==> 1
24 ==>10.执行new Foo.getName(); Foo函数对象的静态方法执行并生成getName类的对象 ==> 2
25 ==>11.执行new Foo().getName(); ==>先执行new Foo() 的到一个Foo类的对象26 ==>Foo类对象.getName() 获得隐式原型对象__proto__的getName方法和Foo.prototype.getName()相同27 ==> 3
28 ==>12.执行new new Foo().getName(); ==>先执行内部new Foo()生成实例对象xxx ==>在执行new xxx.getName()29 ==>Foo类对象.getName() 获得隐式原型对象__proto__的getName方法和Foo.prototype.getName()相同,30 同时生成Foo.prototype.getName的对象 ==> 3