闭包的定义:在函数中创建子函数,并且子函数中调用了函数中的变量。称之为闭包
闭包和普通函数的区别是:
-
多了一层外部函数的作用域链
-
普通函数的作用域链为:函数本身的变量 -> 全局变量;而闭包作用域链为:函数本身的变量 -> 父函数的变量-> 全局变量
-
应尽量少用闭包,因为会增加内存的占用
标准的闭包实现:
1 (function () { 2 function createComparisonFunction(propertyName) { 3 return function (obj1, obj2) { 4 var value1 = obj1[propertyName] 5 var value2 = obj2[propertyName] 6 7 var result = 0 8 if (value1 > value2) { 9 result = 1 10 } 11 else if (value1 < value2) { 12 result = -1 13 } 14 15 return result 16 } 17 } 18 19 var compare = createComparisonFunction('name') 20 var obj1 = { name: 'a' } 21 var obj2 = { neme: 'b' } 22 console.log(compare(obj1, obj2)) // -1 23 })();
闭包与变量
因为闭包会共享父级作用域链的变量,所以下面demo中的闭包,打印出的index结果都是10
1 (function () { 2 function createFunction() { 3 var arr = [] 4 5 for (var index = 0; index < 10; index++) { 6 arr[index] = function () { 7 return index 8 } 9 } 10 11 return arr 12 } 13 14 15 var arr = createFunction() 16 17 for (var index = 0; index < arr.length; index++) { 18 var element = arr[index]; 19 console.log(element()) // 全部都是10 20 } 21 })();
为了解决上面的变量共享问题,我们可以给数组赋值时,增加一个立即执行的闭包。
思路:因为参数是按值传递的,所以多写一个闭包,并且马上执行,就可以将每个参数都隔离开,达到预期的效果
1 (function () { 2 function anotherCreateFunction() { 3 var arr = [] 4 5 for (var index = 0; index < 10; index++) { 6 arr[index] = (function (num) { 7 return function () { 8 return num 9 } 10 })(index) 11 } 12 13 return arr 14 } 15 16 var arr = anotherCreateFunction() 17 18 for (var index = 0; index < arr.length; index++) { 19 var element = arr[index]; 20 console.log(element()) // 1~10 21 } 22 })();
闭包中的this
默认闭包中的this会指向window
1 (function () { 2 var name = 'Cheery' 3 var thisObject = { 4 name: 'Colyn', 5 getNameFunc: function () { 6 return function () { 7 return this.name 8 } 9 } 10 }; 11 12 console.log(thisObject.getNameFunc()()) // Cheery 13 })();
如果想要在闭包中访问父级函数的作用域,可以通过提前将this的值保存起来,以达到预期效果
1 (function () { 2 var name = 'Cheery' 3 var thisObject = { 4 name: 'Colyn', 5 getNameFunc: function () { 6 var that = this 7 return function () { 8 return that.name 9 } 10 } 11 }; 12 13 console.log(thisObject.getNameFunc()()) // Colyn 14 })();
闭包的内存泄漏问题
- IE9之前的javascript和COM对象的回收机制不同
-
如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁
下面这个方法,只要匿名函数存在,element的引用数至少是1,因此它所占用的内存就永远不会被回收
1 (function () { 2 function assignHandler() { 3 var element = document.getElementById('someElement') 4 element.click = function () { 5 alert(element.id) 6 } 7 } 8 })();
解决办法
-
将闭包中要用到的变量提取到父级作用域链中
-
销毁element
1 (function () { 2 function anotherAssignHandler() { 3 var element = document.getElementById('someElement') 4 // 重点1 5 var id = element.id 6 element.click = function () { 7 alert(id) 8 } 9 10 // 重点2 11 element = null 12 } 13 })();
模仿块级作用域
以下函数的index会存在于整个函数
1 (function () { 2 function blockFunction() { 3 for (var index = 0; index < 10; index++) { 4 5 } 6 7 console.log(index) //10 8 } 9 })();
想要解决变量污染的问题,可以创建一个立即执行的闭包来解决
1 (function () { 2 function anotherBlockFunction() { 3 (function () { 4 for (var blockIndex = 0; blockIndex < 10; blockIndex++) { 5 6 } 7 })() 8 9 console.log(blockIndex) //Uncaught ReferenceError: blockIndex is not defined 10 } 11 })();
特权方法:指的是有权访问私有变量的共有方法
方式一:每个实例独享变量
1 (function () { 2 function PrivilegeFunction(name) { 3 var privateName = name || 'Cheery' 4 5 this.publicSetName = function (name) { 6 privateName = name 7 } 8 9 10 this.publicGetName = function () { 11 return privateName 12 } 13 } 14 15 var p1 = new PrivilegeFunction 16 var p2 = new PrivilegeFunction 17 18 console.log(p1.publicGetName()) // Cheery 19 console.log(p2.publicGetName()) // Cheery 20 21 p1.publicSetName('Colyn') 22 23 console.log(p1.publicGetName()) // Colyn 24 console.log(p2.publicGetName()) // Cheery 25 })();
方式二:实例间共享变量
1 (function () { 2 var staticPrivateName 3 4 function StaticPrivilegeFunction(name) { 5 staticPrivateName = name || 'Cheery' 6 } 7 8 StaticPrivilegeFunction.prototype.publicSetName = function (name) { 9 staticPrivateName = name 10 } 11 12 StaticPrivilegeFunction.prototype.publicGetName = function () { 13 return staticPrivateName 14 } 15 16 var p1 = new StaticPrivilegeFunction 17 var p2 = new StaticPrivilegeFunction 18 19 console.log(p1.publicGetName()) // Cheery 20 console.log(p2.publicGetName()) // Cheery 21 22 p1.publicSetName('Colyn') 23 24 console.log(p1.publicGetName()) // Colyn 25 console.log(p2.publicGetName()) // Colyn 26 })();