『你不知道的闭包』【JavaScript面试不艰难系列】

332 阅读5分钟

本文将带你深入了解闭包,彻底搞定闭包! 看完这篇文章你将收获:

  1. 闭包是什么?为什么需要闭包?
  2. 举例说下闭包的运用
  3. 在项目中的哪些地方用到了闭包

相信大家在面试中被问到闭包时都是这样回答的闭包是能够读取其他函数内部变量的函数,这样回答其实是对的,但是并没有什么侧重点,而且也没有那么深入从而可能会被pass掉。

闭包是什么

闭包其实是javascript的一大难点,也是它的特色,很多高级应用都要依靠闭包实现(所以平时写代码基本不会用到闭包...)要理解闭包,首先要理解javascript的全局变量和局部变量。并且,javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。来举例子!

var a=10; // 全局变量,可以在任何地方访问
function f1(){
    var a=100 // 局部变量
}
f1(); // 即使调用f1函数也不能访问到函数内部定义的变量,故而还是会打印10
console.log(a) // 10

那我们现在再来看这句话---闭包是能够读取其他函数内部变量的函数,上述代码可知,在全局作用域下无法访问局部作用域下定义的局部变量,那这样看来似乎闭包可以实现这一操作。我们知道,在javascript中,只有在函数内部才能读取到局部变量,那么在函数内部定义一个子函数应该也可以读取到局部变量,所以,闭包可以简单理解成“定义在一个函数内部的函数”。所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。于是诞生了以下形式:

var a=10;
function f1(){
    var a=100;
    function f2(){
        console.log(a)
    }
    f2();
}
f1(); // 100

在第一段代码中我们发现,从外部调用f1函数并不能直接读取到函数内部的局部变量,因此我们需要变通一下改成以上的写法,现在确实可以访问到局部变量a了,那我们现在可以说它就是一个闭包吗?答案是不是

这里再强调一下上述代码这样写的目的,是为了获取a这个局部变量,既然如此这样写不更显得直接一些吗?

// No.1
var a=10;
function f1(){
    var a=100;
    return function f2(){
        console.log(a)
    }
}
f1()() // 100

// No.2
var a=10;
function f1(){
    var a=100;
    return a;
}
console.log(f1()) // 100

以上两种写法都可以获取到局部变量,因为闭包的真实目的并不只是为了去获取一个局部变量,还有防止全局变量污染的作用。这里我们再解释一下全局变量和局部变量,window对象是浏览器对象,全局变量就存在于window对象下,打开浏览器时会自动生成一个全局的作用域就是window,在window对象下的定义的变量就叫全局变量,当浏览器关闭时全局作用域才会被销毁。而局部变量属于局部作用域只能在函数内部访问,当函数执行完毕时局部变量就会被销毁,所以它们两个的生命周期不一样。

// 全局变量和局部变量的不同
var a=10;
function f1(){
    a++;
    console.log(a)
}
f1(); // 11
f1(); // 12
f1(); // 13  全局变量并没有被销毁 会维持其状态


function f1(){
    var a=10;
    a++;
    console.log(a)
}
f1(); // 11
f1(); // 11
f1(); // 11 函数执行完之后局部变量就被销毁掉了

为什么我们平时不会去大量的定义全局变量呢?

很显然是为了防止污染全局变量,一旦声明了全局变量无论是在函数还是函数内的函数都可以被访问或修改。那么有没有一种机制既可以维持变量的状态(变量的值始终保存在内存中)又不会对全局变量造成污染呢?这就是闭包诞生的原因!下面我们来看一下闭包的写法。

闭包的写法

以下是闭包的正确写法:

function f1(){
        var a=10; // 局部变量
    function f2(){
        a++;
        console.log(a)
    }
    return f2;
}
var f=f1();
f(); // 11
f(); // 12
f(); // 13

// 注意这里不能这样调用 f1()();

这里f1函数执行返回的结果就是闭包,我们来分析一下上述代码,为什么把写法变更为这样局部变量的值就被维持下来了呢?是因为f1函数的执行结果就是f2,相当于把f2赋值给了f变量,正因为这样导致f2函数中的a始终都被保存在内存中没有被回收,为什么没有被回收呢?f1函数的内部函数f2的执行一直需要依赖于f1函数中的变量,这样就形成了闭包的包含关系(返回的是一个函数,并且这个函数对局部变量存在引用),这样才能维持一个变量的存在。

继续看另一种写法:

function f1(){
    var a=10; // 局部变量
    function f2(){
        a++;
        console.log(a)
    }
    return f2;
}
var f=f1();
f(); // 11
f(); // 12
var x=f1();
x();// 11
x();// 12

可以看出闭包的另一个特点就是一个闭包对变量的修改不会影响另一个闭包中的变量,两个是完全独立的,当然缺点也很明显,因为存在引用关系所以会增大内存的使用量。

最常见的场景就是vue项目中data需要返回一个对象,否则声明变量会造成污染。