个人对 JavaScript 闭包的理解

  • 个人对 JavaScript 闭包的理解已关闭评论
  • 26 次浏览
  • A+
所属分类:Web前端
摘要

在一个作用域中声明相同名称的变量会发生变量名冲突的问题。假如在作用域 A 中声明一个变量 a,作用域 B 也声明一个变量 a,两个作用域的变量都互不影响。


作用域的概念

同级作用域

在一个作用域中声明相同名称的变量会发生变量名冲突的问题。假如在作用域 A 中声明一个变量 a,作用域 B 也声明一个变量 a,两个作用域的变量都互不影响。

// 作用域 A {   let a = 0;   console.log(a); }  // 作用域 B {   let a = 10;   console.log(a); } 

第一个打印 0,第二个打印 10。

嵌套作用域

作用域是可以嵌套的,作用域 A 嵌套作用域 B,此时两个作用域分别声明变量 a,也是不冲突的。

{// 作用域 A   let a = 0;   console.log(a);   { // 作用域 B     let a = 10;     console.log(a);   } } 

第一个打印 0,第二个打印 10。

在嵌套作用域中,子作用域 B 可以访问父作用域 A 的变量,也可以影响它的父作用域 A。

{   let a = 0;   {     a = 10;     console.log(a);   } } 

最终打印的结果是 10。

函数的闭包

JS 的类实际上就是在使用函数的闭包。每执行一次函数就是在生成一个新的作用域,这些新的作用域就像是上面提到的,它们互不影响,只有它们的子作用域可以影响父作用域,即闭包。

闭包缓存数据

function Counter(x) {   return {     add: y => {       return (x = x + y);     },     del: y => {       return (x = x - y);     }   }; } 

add 和 del 都是父函数 Counter 的子函数,它们之间是一个闭包关系。创建两个 Counter 的实例:

let c1 = Counter(10); console.log(c1.add(20)); console.log(c1.del(40)); 

当执行c1.add()时,左边有一个 Closure,说明已经形成了一个闭包:

个人对 JavaScript 闭包的理解

Closure 中只有 x 是受闭包影响被缓存下来的数据,也就是父函数 Counter 的变量。Local 代表 add 函数自身的变量,没有与其他函数之间(或作用域)形成关系,也就不符合闭包的存在条件,只能由 add 函数自己来使用。

再继续往下执行,可以看到此时 Closure 中的 x 已经是 30:

个人对 JavaScript 闭包的理解

再往下执行,调用 del 函数,再执行函数的相减操作之间,我们可以看到 Closure 中的 x 还是上一次的结果:30。

个人对 JavaScript 闭包的理解

再接着往下执行一次,del 函数相减之后,Closure 中的 x 的结果是 -10:

个人对 JavaScript 闭包的理解

本节小结

受闭包的影响,父函数的数据被缓存下来,子函数可以自由地使用,而且再内存中也不会被销毁。

闭包的好处

仔细观察上面的例子,add 和 del 函数都依赖了相同的变量 x,而这个 x 是父函数给的,再闭包中被缓存起来。add 和 del 只需要传递新的参数就可以参与运算,也就是说,闭包可以减少我们函数的参数传递,使得我们一个计算操作更加连贯,且降低代码耦合度。

假如不使用闭包,通过函数的参数传递来计算,替代上面的闭包函数:

function add(x, y) {   return (x = x + y); }  function del(x, y) {   return (x = x - y); }  let res = add(10, 20); let ser = del(res, 40); 

就很没有必要,何不如把 x 抽离出来呢?变成一个全局变量:

let x = 10;  function add(y) {   return (x = x + y); }  function del(y) {   return (x = x - y); }  let res = add(20); let ser = del(40); 

可以,但是不推荐,全局作用域中,变量 x 被声明一次,假如代码越写越多,变量是不是会冲突,代码是不是变得难以维护?闭包可以把 add 和 del 以及 x 都囊括在一个作用域里,也不影响其他的作用域。

本节小节

闭包可以把一块代码容纳在一个里面,形成一个整体,一个不受其他作用域影响的作用域。是不是很像模块开发?没错,我猜测 CommonJS 就是使用的闭包。

总结

  1. 闭包可以让我们使用模块开发思想来写代码,把一系列代码揉进闭包里,是一个有机的结合。类就是使用的闭包,在早期通过闭包来实现模块的开发。

  2. 闭包可以缓存父函数的变量,子函数可以使用,子函数修改父函数的变量,其他子函数也跟着改变。