一.JS闭包
1.定义:
闭包是一个闭合容器,我们可以认为闭包是一个对象{key:value}
2.闭包形成条件:
缺一不可:
-
函数嵌套
-
内部函数引用外部函数局部变量
-
外部函数调用
3.作用:
-
延长外部函数局部变量的声明周期
-
从外部访问函数内部的局部变量
4.闭包缺点:
-
占用内存
-
不及时清除会造成内部泄漏
5.闭包示例:
1 function fun(){ 2 var num = 123; 3 4 return function fun2(){ //函数嵌套 ==》满足1 5 console.log(num); //调用外部函数的局部变量num ==》满足2 6 } 7 } 8 9 var f = fun(); 10 11 f(); //外部函数调用 ==》满足3 12 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 function fun(){ 2 var num = 123; 3 4 return function fun2(){ //作为内部函数返回出来 5 console.log(num); 6 } 7 } 8 9 var f = fun(); 10 11 f(); 12 13 f = null;
(3)作为实参传递给另一个函数调用:
1 <script type="text/javascript"> 2 3 function fun(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 function getstr(){ 7 return str; 8 } 9 10 function getnum(){ 11 return num; 12 } 13 14 //将闭包挂载到window的我们自定义的属性myModule上 15 window.myModule ={ 16 getstr:getstr, 17 getnum:getnum 18 } 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; //缓存this 6 return function () { 7 return that.name2; 8 }; 9 } 10 }; 11 console.log(window.object2.getNameFunc()()); //My Object
分析:由于形成了闭包导致that没有被释放所以得到My Object
例3:
1 function fun(n, o) { 2 console.log(o) 3 return { 4 fun: function (m) { 5 return fun(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 function Foo() { 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函数对象静态方法getName 8 Foo.prototype.getName = ... ==> Foo原型对象中有getName属性 9 var getName ==> 全局变量getName 10 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