大家好,又见面了,我是你们的朋友全栈君。
JavaScript闭包
# 前言-什么是闭包函数 闭包函数是声明在另一个函数内的函数,是被嵌套在父函数内部的子函数,在《JS高级程序设计-第3版》中对闭包解释是:”闭包是指有权访问另外一个函数作用域中的变量的函数.” 闭包函数可以访问[包裹其的函数]内的各种参数和变量,即便外部函数已经执行完毕.(至于为什么请看下文).
一、为什么要闭包
- 使外部得以访问函数内部的变量;
- 避免全局变量的使用,防止全局变量污染(匿名函数);
- 让某些关键变量得以常驻内存,免于被回收销毁(闭包函数);
二、让某些变量得以常驻内存
我们需要将立即执行函数与闭包结合;
我们都知道JavaScript是自带垃圾回收机制的,对于函数来说,在其执行完毕后会被垃圾回收机制回收来进行内存释放,函数内部的局部对象(各种变量之类)也会被连同销毁使内存中仅仅保存全局作用域.
1.原理
前言说到闭包函数就是一个嵌套在父函数里面并且有使用父函数变量的子函数, 闭包函数的执行必定依赖于父函数提供的数据,但要是调用闭包函数时父函数已经被销毁,闭包函数怎么执行呢?
没法执行,因为闭包函数所依赖的变量也都被销毁,总不能因为要执行闭包函数再把父函数提出来,不太合理;
所以不能就这么回收掉,但是保存整个父函数又有点离谱,所以JavaScript垃圾回收机制只会保存闭包函数在父函数中所依赖到的变量这些被保存起来的变量不会被内存回收器回收,直到确认子函数不会再被调用(子函数被删除或者失去指针)为止;
类比一下,如果你学过webpack的话,应该会知道webpack在打包一个文件的时候会把这个文件依赖的所有文件一起打包,就是为了防止使用的时候出问题,垃圾回收机制是在删减的时候留下需要的,weboack是在打包的时候加上需要的.
2.Why 立即执行函数?
我想探讨一下为什么推荐用立即执行函数来配合闭包进行变量保存…
一开始我猜为了在闭包函数保存完需要的变量后父函数能被及时回收释放内存,才采用了匿名立即执行函数来作为闭包函数的父函数.因为立即执行函数自我回调执行完成后会被立即销毁回收,用一次就释放,节约内存(但因为销毁快,外界无法引用其内部的变量)
后来看到了一个例子,作者将使用了立即执行函数的闭包和没有使用立即执行函数的闭包进行了比较,让我改变了想法:
//例1,这个例子中没有使用立即执行函数;
function createFunction() {
var Array = [];
for( var i = 0; i<10; i++) {
//将函数赋值给每个数组元素;
Array[i] = function() {
return i;
};
}
return Array;
}
var aa = createFunction();
console.log(aa[0]()); //10
console.log(aa[1]()); //10
由于作用域链的配置机制,因为每个函数的作用域链中保存的都是createFunctions()的活动对象,所以每个函数引用的都是活动对象中的同一个变量 i。
(活动对象: 在JavaScript中,当一个函数被创建时最后一步便是活动对象推入作用域链,函数中访问一个变量时会从作用域链中搜索具有相应名字的变量,函数执行完后局部活动对象会被销毁,活动对象中包含了参数列表和arguments对象等属性. 活动对象包含变量对象所有的属性)
当createFunctions() 函数执行结束返回后,变量 i 的值就已经固定为10,而每个函数保存的变量对象里的 i 都出自createFunctions()的活动对象,每个函数拿到的 i 都是出自同一个活动对象的,都一样,所以最后不论输出哪个数组元素得到的都是10.
即说明了闭包中所保存的是整个活动对象,而不是某个具体的变量,这种机制并不是我们想要的,我们希望它能把每个变量单独保存下来,所以就有了能解决这个问题的,使用了立即执行函数的例子,即例2:
function createFunction() {
var result = new Array();
for( var i = 0; i<10; i++) {
result[i] = function(num) {
//每接收一个num就会创建新的一个函数作用域;
return function() {
//在每个作用域的内部创建并返回一个返回num的闭包函数
return num;
};
}(i);
//变量i的当前值会作为实参赋值给上面的形参num;
}
return result;
}
//在外部使用函数内变量;
var bb = createFunction();
alert(bb[0]()); //0
alert(bb[1]()); //1
闭包函数依赖到了外部立即执行函数的num,所以num会连同闭包函数被保存下来免于销毁,这样result[ ]中被赋值进去的每个函数都能返回一个自己的num,我们的目的就能达到了,完成这一目标的关键就是使用了立即执行函数.
这个闭包函数的父函数函数每接收一个num就会创建新的一个函数作用域(见例3),作用域中传入i后,变量i的当前值会作为实参赋值给上面的形参num,而在当前每个作用域的内部,又创建并返回了一个返回num的闭包函数。这样一来传入每个函数作用域中闭包函数的num就是不同的了.如此类推,被赋值进入result数组中的每个函数作用域都有一个自己num(其实是时num副本),可以返回各自不同的数值了.
for(var i = 0; i < 5; i++) {
abc(i);
};
function abc(i) {
setTimeout(function() {
console.log(i); // 0 1 2 3 4
}, 200);
}
//这里就相当于创建了5个函数作用域;
可见立即执行函数在保存变量时泛用性比普通函数强;
三、让外部得以访问函数内变量
外部访问函数内变量跟立即执行函数没什么必然关系,不使用立即执行函数也可以进行保存,上面说到的结合立即执行函数的写法只是针对某些特殊情况下无法依据需求保存变量的问题,我们不得不承认立即执行函数泛用性好一些.
在外部调用父函数即可拿到闭包函数内的变量;
四、立即执行函数
刚学到的,单独开一篇感觉也没必要,正好这里用到了就写下来吧…
//这两种写法是会报错的;
(function() {
//函数体;
})();
function() {
//函数体;
}();
JavaScript引擎先看到了你的”function”关键字,然后就开始以函数声明标准规范你后续的代码,最终JavaScript引擎发现你用一个小括号结束了你的函数,它觉得这是错的.
我们不能否定它判定的规则,人家认为写了function就是要声明函数,那我们就不要上来直接写function了:
var myFunction = function () {
/* 函数体 */
}();
var myObj = {
myFunction: function () {
/* 函数体 */ }
}();
让JavaScript引擎先看到小括号而不是function关键字,它就会觉得你在写函数表达式,也就判定为合理了;
总结
比较重要的是闭包会造成内存泄漏.闭包会把一些东西永驻保存下来,而且前面提到的它所依赖的东西都不会被销毁,自己的局部活动对象和依赖到的活动对象都会被包含到它自己的作用域链里,所以它的体量往往是比普通函数大上老些;
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148667.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...