浅谈JavaScript闭包

最近朋友面试被问到了 JS 闭包的问题,本人一时语塞,想起了袁华的一句话:“这道题太难了,我不会做,不会做啊!”。

JS 闭包属于面向对象的一个重要知识点,特此本人又开始了一段说走就走的旅程。

闭包就是外层函数的作用域(AO)对象被内层函数所引用,无法被释放。

上面那句话听起来可能不是很理解,本人在之前写过一篇Python 闭包小记》的关于 Python 闭包的一些知识的文章,里面写了百度百科对于闭包的理解,虽然由于才疏学浅大部分都是引用的他人的知识架构,但语言这种东西都是相通的,我们不需要去记那些晦涩的名词,对于闭包,作为初学者我们只需知道:

函数作为返回值,函数作为参数传递。就可以将其理解为闭包。

话不多说,先上个代码缓和一下尴尬的气氛:

function outer() { var max = 10; function inner(num) { if (num > max) { console.log(num) } } return inner; } var foo = outer(); foo(20); // 20

上面代码满足函数作为返回值的条件,所以是一个闭包函数。

根据 JS 函数的执行机制,先执行第 10 行的 foo 代码,在函数执行完之后会被 JS 的垃圾回收机制将 outer 函数回收,但是在执行到第 3 行的时候我们发现 outer 函数内部又出现了一个 inner 函数,且 inner 函数里引用着 outer 函数的 max = 10; 的变量,这就无法被回收并且留在了内存里,当执行到第 11 行时由于 outer 函数内的 max = 10; 被留在内存中,所以会被 inner 函数调用,并满足 if 条件判断,所以输出 20;

以上我们实现了一个简单的闭包函数,但是却产生了一个问题,那就是无法被释放的对象留在了内存当中,造成了不必要的内存开销。

再看如下代码:

var max = 10, foo = function (num) { if (num > max) { console.log(num); } }; (function (bar) { var max = 100; bar(20) })(foo); // 20

上面代码满足函数作为参数传递的条件,所以是一个闭包函数。

函数 foo 作为一个参数被传入函数中,赋值个 bar 参数,当执行到 bar() 函数时,函数内部的 max 并不是 100,而是 10,这似乎匪夷所思。我们暂且将 7 — 10 行的函数叫 “父作用域”,其余叫“全局作用域”,当执行到 bar(20) 时,函数去执行第 2 行的代码,此时 foo 函数内部的 max 要去取值,而 max = 10; 正好在他所在的 “全局作用域” 内,所以会取 max = 10; 的值而不是 max = 100; 的值。由此可见,取值时要去创造这个函数的作用域内取值,而不是所谓的 “父作用域” 或者离函数近的地方取值。

我们再来看一段代码:

var num = 20; function outer() { var max = 10; function inner() { if (num > max) { console.log(num); } } return inner; } var foo = outer(), num = 30; foo(); //30

上面的代码在看完上面的解释后可以得知它是一个闭包函数,且定义了一个全局变量 num,最初定义为 num = 20,当代码执行到第 11 行时去调用执行第 2 行,待第 11 行执行完毕后执行第 12 行,此时将全局的 num = 20; 变为了 num = 30; 再执行第 13 行,此时执行时调用 inner 函数时,从输出结果我们可以看出调用的 num 为之后赋值的 30,

由此可见全局的 num 变量被污染了。

我们再来看下一段代码:

function outer() { var max = 10; function inner(num) { if (num > max) { console.log(num); } } return inner; } var foo = outer(), max = 100; foo(20); //20

上面的代码中当函数执行时,先执行第 10 行,然后调用执行第 1 行的函数,此时将 max 赋值为 10,但需要注意的是此时的 max = 10;并不是在全局作用域内,而是在 outer() 函数的作用域内,执行完第 10 行再执行第 11 行,此时将 max 赋值为 100,但需要注意的是此时的 max = 100;是在全局作用域内。所以在执行到第 12 行代码的时候调用执行 inner() 函数并将参数 20 传入,输出结果为 20,由此可见outer() 函数作用域内的对象 num 并没有被全局的对象 num 所污染。

由以上四段代码我们初步了解了一些闭包的基本特征,但是由于才疏学浅,怕总结的不够全面,这时突然想到了东东大神的笔记,于是上网搜到了一些,下面就将其再归纳总结一下。

闭包:既重用一个变量,又保护变量不被污染的一种机制。

为什么使用闭包:

全局变量和局部变量都具有不可兼得的优缺点。

全局变量:

优: 可重用,

缺: 易被污染。

局部变量:

优: 仅函数内可用,不会被污染

缺: 不可重用!

何时使用:

只要既重用一个变量,又保护变量不被污染时。

如何使用:

1. 用外层函数包裹要保护的变量和内层函数。

2. 外层函数将内层函数返回到外部。

3. 调用外层函数,获得内层函数的对象,保存在外部的变量中——形成了闭包。

闭包形成的原因:

外层函数调用后,外层函数的函数作用域(AO)对象无法释放,被内层函数引用着。

闭包的缺点:

比普通函数占用更多的内存。

解决:闭包不在使用时,要及时释放。

将引用内层函数对象的变量赋值为null。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/13ac347fd95bdb84b625d3851495bf6d.html