最近在看闭包的一些知识点,理解闭包之前先理解下调用栈的概念。现将书上的内容做下记录,方便查阅。
调用栈
function f1(){
f2()
}
function f2(){
f3()
}
function f3(){
f4()
}
function f4(){
console.log('f4');
}
f1() //f4
调用栈:先进后出(后进先出)。f1 -> f2 -> f3-> f4 。个具体过程:f1先入栈,紧接着f1调用f2,f2再入栈。以此类推,直到f4执行完,然后f4先出栈,f3再出栈,接着f2出栈,最后f1出栈。这个过程是满足先进后出的规则,因此形成调用栈。
闭包定义:函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问。
function numGenerator(){
let num = 1
num++
return ()=>{
console.log(num);
}
}
var getNum = numGenerator()
getNum() //2
没有及时把变量清空导致的内存泄漏
var element = document.getElementById('element');
element.style.color = 'red'
function remove(){
element.parentNode.removeChild(element)
}
这会导致内存泄漏,最好在remove()方法中增加element = null
不用的变量要置为null
function foo(){
let value = 123
function bar(){
alert(value)
}
return bar
}
let bar = foo()()
上面代码中,变量value将会被保存在内存中,如果加上bar = null,则随着Bar不再被引用,value也会被清除。
下面实例来熟悉借助chrome devtool排查内存泄漏的具体应用。
var array = []
function createNodes(){
let div
let i = 100;
let frag = document.createDocumentFragment()
for(;i>0;i--){
div = document.createElement('div');
div.appendChild(document.createTextNode(i))
frag.appendChild(div)
}
document.body.appendChild(frag)
}
function badCode(){
array.push([...Array(100000).keys()])
createNodes()
setTimeout(badCode,1000)
}
badCode()
以上代码递归调用了 badCode, 这个函数每次向 array 数组中写入新 的由 100000 项 0~1 数字组 成的新数组, badCode 函数使用全局变址 array 后并没有手动释放内存 , 垃圾回收机制不会处理 array, 因此会导致内存泄涌 ;同时, badCode 函数调用了 createNodes 函数, 每秒会创建 100 个 div 节点 。
拍下Chrome devTools的performance快照
由上图可以看出,JS Heap和Nodes线随着时间线一直在上升,并没有被拉圾回收机制回收。因此,可以判定这段代码存在较大的内存泄漏风险。如果不知道问题代码的位置,要想找出风险点,那就需要在Chrome Memory标签中,对JS Heap中的每一项,尤其是Size较大的前几项展开调查。
下面进入闭包的实战环节
1. 下面代码会输出什么?
const foo = (function(){
var v=0;
return ()=>{
return v++
}
}())
for(let i=0; i<10; i++){
foo()
}
console.log(foo());
答案是:10. 下面进行分析: foo是一个立即执行函数,当我们尝试打印foo时,要执行如下代码
const foo = (function(){
var v=0;
return ()=>{
return v++
}
}())
console.log(foo)
输出结果如下:
()=>{
return v++
}
在循环执行foo时,引用自由变量10次,v自增10次,最后执行foo时,得到10.这里的自由变量是指没有在相关函数作用域中声明,但却被使用了的变量。
2. 例题2 下面代码会输出什么?
const foo = ()=>{
var arr = []
var i
for(i=0; i<10; i++){
arr[i] = function(){
console.log(i);
}
}
return arr[0]
}
foo()()
答案依然是:10. 自由变量为i,烦人例题1,执行foo返回的是arr[0],arr[0]此时是函数,其中变量i的值为10.
3. 实战例题3 ,下面代码输出什么?
var fn = null
const foo = ()=>{
var a = 2
function innerFoo(){
console.log(a)
}
fn = innerFoo
}
const bar = ()=>{
fn()
}
foo()
bar()
bar()运行后输出2. 正常来说,根据调用栈的知识,foo函数执行完毕后,其执行环境生命周期会结束,所占用的内存会被垃圾收集器释放,上下文消失。但是通过将innerFoo函数赋值给全局变量fn,foo的变量对象a也会被保留下来,所以,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象,输出结果为2.
4. 例题4 对3题的代码进行修改
var fn = null
const foo = ()=>{
var a = 2
function innerFoo(){
console.log(c)
console.log(a)
}
fn = innerFoo
}
const bar = ()=>{
var c = 100
fn()
}
foo()
bar()
会报错:ReferenceError: c is not defined 。因为bar中执行fn时,fn已经被复制为innerFoo,变量c并不在其作用域链上,c只是bar函数的内部变量。因此会报错。如下图: