一梦七年。
一派青春撞了南墙,一生热爱回头太难。
文章 20
标签 29
分类 11
JS闭包的前世今生

JS闭包的前世今生

JS 闭包的前世今生

一、闭包是什么

闭包是 JavaScript 的一大特点,同时也是一大难点,简单来说就是,如果闭包用的好对于平时开发来说很有帮助,但是用得不好就可能会使你陷入泥沼。说到 JS 闭包,首先就得先讲讲 JS 的变量作用域、作用域链。

在 JS 中变量作用域分两种,一个是全局作用域一个是局部作用域:

var a = 1; // 全局作用域中的变量

function fun() {
  var b = 2; // 局部作用域中的变量
  console.log(a, b);
}

fun();

上面代码中 a 是全局变量,b 是局部变量,由于在 JS 中函数内部可以直接读取到全局变量,以及作用域链的存在,在执行 console 时,会先在函数内部查找变量 a,没有则向上一层作用域查找,直到找到或者找不到而报错。

以上的变量访问需求是最简单的情况,但是在开发中我们也常常会遇到需要在函数外部访问函数内部的变量的情况。我们都知道,正常情况下我们是无法在外面获取到函数内部的变量的,所以我们通常会在函数内部再定义一个函数,然后将这个函数 return 出来,return 出来的这个函数自然就能获取到我们需要的变量了:

function fun1() {
  const a = 1;
  return function fun2() {
    console.log(a);
  };
}

const result = fun1();

result(); // 打印 1

那么上面的代码就中就形成了一个闭包,即函数 fun2。对于闭包的解释网上很多资料,但是看了好几个发现看完了也还是很模糊的概念,所以我就暂且简单的将他理解为:定义在某个函数内部,并且作为该函数返回值的函数就是闭包。实际上闭包只是连接函数内部和函数外部的一种方法。

在 JS 中很多地方都能用到闭包,闭包主要是用来在函数外部读取函数内部的变量,以及让这些变量始终保存在内存里,而不会被回收掉。

function f1(){

    var n=999;

    Add = function(){
        console.log('执行add前,n=' + n);
        n++;
    }

    return function(){
        console.log(n);
    }
}

var result = f1();

Add(); // 999

result(); // 1000

Add(); // 1000

result(); // 1001

通过上面的代码可以说明闭包读取的变量不会被回收,所以 f1 中的变量 n 一直保存在内存中。那为什么会有这种情况呢?首先按照 JS 的特点,f2 是 f1 的子函数,f2 可以读取 f1 中的变量,而 f2 被作为返回值赋值给了全局变量 result,因此 result 始终在内存中,那么 f2 也始终在内存中,而 f2 是在 f1 中定义的,因此 f1 也始终在内存中,这就导致了 f1 中的变量也始终在内存中。

有人也许会疑惑为什么 Add 函数能在外部调用,这里的话是因为 Add 它是个变量,而且是在局部定义的全局变量,而这个变量的值是个匿名函数,所以可以在函数外部调用 Add。

正是由于这种特性,闭包的内存消耗是很大的,所以一旦闭包使用不当,则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的变量全都删除掉。

还有一个需要注意的就是闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。