3

【前端芝士树】Js中的闭包是怎么一回事 && 笔试问题集锦

为什么会有闭包的出现?

这涉及到var作为变量声明的关键词时所出现的一些问题。
比如,var 的 变量提升 以及 函数级作用域

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
另一方面,在函数外部自然无法读取函数内的局部变量。

需要注意的是,如果在函数内部声明变量时没有使用var关键词,实际上声明的是一个全局变量,如下:

function f1(){
    n = 999;
}
//console.log(n); // ReferenceError: n is not defined
f1();
console.log(n); // 999

这里为什么第一处会报错呢?关于这个问题下面有比较基础的解释:

JS的解析过程分为两个阶段:预编译期(预处理)执行期

  • 预编译期 JS会对本代码块(两个script块互不影响)中的所有var声明的变量和函数进行处理(类似与C语言的编译)
    此时处理函数的只是声明式函数,而且变量也只是进行了声明但未进行初始化以及赋值。>
  • 执行期 会按照代码块的顺序筑行执行

正因为从外部访问在函数内部进行声明的局部变量是不可能的,所以出现了闭包这种形式,在函数内部再定义一个函数。

闭包(Closure)是什么?

查阅了一些文章和资料,发现还是下面的定义最容易理解:

闭包:定义在函数内部的一个函数。

扩展一些讲,可以参考一下阮一峰的讲解:

闭包:能够读取其他函数内部变量的函数。

俗话说的好,看定义不如看代码更直观一些,如下

function f1(){
    var n=999;
    function f2(){
        console.log(n);
    }
    return f2;
}

var result=f1();
result(); // 999

如此段代码所示,f2()就是其中的闭包函数,通过f2()我们可以访问到f1()内部的n
更常见的一种简写形式:

function f1(){
    var n=999;
    return function(){
        console.log(n);
    };
}

var result=f1();
result(); // 999

闭包的优缺点

优点:

  1. 可以读取函数内部的变量
  2. 让这些变量的值始终保持在内存中。

缺点:

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包面试题集锦(持续更新)

问题一、简单闭包

var a = 1;
function foo(){
    var a = 2;
    c = 0;
    return function () {
        console.log(a);
        console.log(b++);
        console.log(c);
    }
}
console.log(a);// 1

//console.log(c); // Reference Error
var b = 3;
var x = foo();
x(); //2 3 0
console.log(a); // 1
console.log(b); // 4
console.log(c); // 0

问题二、闭包的链式调用

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
看解答前先思考一下会输出什么哦

【解答】: 首先对函数进行分析,这个函数其实是返回了一个对象,{fun:function(){}},里面有一个函数作为属性,这个函数就是闭包,使得函数内部的变量保留在内存中。

注意,这里会有一个可能误解的地方,return {fun:...}里面的fun是fun(n,o)吗?明白这个区别后后面就容易多了。

好了,明白大概的原理后,我们来分析这个问题:

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);

执行a = fun(0),0是作为n传入的,o没有参数传入,所以输出undefined,之后,

a = {
  fun:function(m){
     return fun(m,0);//这里的n应该变成了第一次调用时的参数n,也就是0
  }
}
也就是
a = {
 fun:function(m){
     return function(n = m, o = 0) {
         console.log(o) //输出0
         return {
            fun:function(m){
              return fun(m,n);
            }
          };
    };
  }
}

所以无论传入的m是什么,输出永远都是0
最后,输出结果如下

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
undefined
0
0
0

搞清楚第一个的过程,第二个的破解关键就在于闭包让函数内部的变量始终保存在内存之中。

//b
undefined
0
1
2
//c
undefined
0
1
1

参考文章

《学习Javascript闭包(Closure) - 阮一峰的网络日志》
《闭包 - 廖雪峰的官方网站》

云中的猫
769 声望56 粉丝

生活是一个BUG。