闭包这家伙,从刚开始接触到javascript基础就走进我的脑子里,但是总感觉自己理解的不透彻,不清晰。然而,几乎每个面试官呢,又好像都挺喜欢问这个问题的,所有,没办法罗,只有再深入去探讨一下咯,哈哈。
1.什么是闭包
这里先给闭包下一个定义,简单概括为两点:
1).函数嵌套函数;
2).内层函数可以访问外层函数的变量和参数。
2.js中的垃圾回收机制
看代码说话:
1 function aa() { 2 a = 10; 3 } 4 aa(); 5 console.log(a); // 10 a没有var申明,默认是一个全局变量 6 //上面代码等价于: 7 var a; 8 function aa(){ 9 a = 10; 10 } 11 12 13 // 对比下面这个例子: 14 function aa() { 15 var a = 10; 16 } 17 aa(); 18 console.log(a); //js中的垃圾回收机制 a is not defined
3.闭包的两个最大的用处
1).可以读取函数内部的变量
2).让这些变量的值始终保持在内存中(不会被垃圾回收机制所回收)
出于种种原因,我们有时候需要得到函数内的局部变量。但是,正常情况下,这是办不到的(js的垃圾回收机制),只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
1 //example1: 2 function f1() { 3 var n = 999; 4 nAdd = function () { 5 n += 1 6 }; //全局变量 7 function f2() { 8 alert(n); 9 } 10 11 return f2; 12 } 13 var result = f1(); 14 result(); // 999 15 nAdd(); 16 result(); // 1000 17 //这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行, 18 // 首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。 19 20 //example2: 21 (function (x) { 22 return (function (y) { 23 console.log(x); 24 })(2) 25 })(1); 26 // result: 1 输出1,闭包能够访问外部作用域的变量或参数。
4.闭包只能取得包含函数中任何变量的最后一个值
看看下面这个闭包函数:
1 function test1() { 2 var result = []; 3 for (var i = 0; i < 10; i++) { 4 result[i] = function () { 5 alert(i); 6 }; 7 } 8 return result; 9 } 10 11 test1()[1](); // 10
执行任何一个test1()[i](); 结果都会弹出10.
5.我们可以通过创建另一个匿名函数强制让闭包的行为符合预期
1 function test2() { 2 var result = []; 3 for (var i = 0; i < 10; i++) { 4 result[i] = function (num) { 5 return function () { 6 alert(num); 7 } 8 }(i); 9 //也可以这样写 10 /*(function(m){ 11 result[m] = function(){ 12 alert(m); 13 }; 14 })(i);*/ 15 } 16 return result; 17 } 18 for (var i = 0; i < test2().length; i++) { 19 test2()[i](); // 0,1,2,3,...,8,9 20 }
这里用到了立即执行函数表达式。将 JavaScript 代码包含在一个函数块中有神马意思呢?为什么要这么做?句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。
1 for (var i = 0; i < 5; i++) { 2 setTimeout(function () { 3 console.log(i); 4 }, 1000); 5 } 6 /*上面的输出并不是你以为的0,1,2,3,4,而输出的全部是5,这时 IIFE 就能有用了:*/ 7 for (var i = 0; i < 5; i++) { 8 (function (m) { 9 setTimeout(function () { 10 console.log(m); 11 }, 1000); 12 })(i); 13 } 14 /* 输出 0,1,2,3,4 */ 15 16 /*而在 JQuery/Node 的插件和模块开发中,为避免变量污染,也是一个大大的 IIFE:*/ 17 (function($){ 18 //代码 19 })(jQuery);
6.闭包中关于this对象
1 //example1: 2 var name = "The Window"; 3 var object = { 4 name: "My Object", 5 getNameFunc: function () { 6 return function () { 7 return this.name; 8 }; 9 } 10 }; 11 console.log(object.getNameFunc()()); // The Window 【匿名函数的执行环境具有全局性,this指向window】 12 13 //example2: 改进 14 var name = "The Window"; 15 var object = { 16 name: "My Object", 17 getNameFunc: function () { 18 var that = this; 19 return function () { 20 return that.name; 21 }; 22 } 23 }; 24 console.log(object.getNameFunc()()); // My Object 25 26 //example3: 27 var myObject = { 28 foo: "bar", 29 func: function () { 30 var self = this; 31 console.log("outer func: this.foo = " + this.foo); 32 console.log("outer func: self.foo = " + self.foo); 33 (function () { 34 console.log("inner func: this.foo = " + this.foo); 35 console.log("inner func: self.foo = " + self.foo); 36 }()); 37 38 //用闭包来解决 39 /*(function (test) { 40 console.log("inner func: this.foo = " + test.foo); //'bar' 41 console.log("inner func: self.foo = " + self.foo); 42 })(self);*/ 43 } 44 }; 45 myObject.func(); 46 /* 47 * 输出结果: 48 outer func: this.foo = bar 49 outer func: self.foo = bar 50 inner func: this.foo = undefined 51 inner func: self.foo = bar 52 * */