Javascript闭包由浅入深题目

闭包就是能够读取其他函数内部变量的函数

两个最大用处
一个是前面提到的可以读取函数内部的变量;
一个就是让这些变量的值始终保持在内存中。

1、变量作用域
要理解闭包,首先要理解javascript的特殊的变量作用域。
变量的作用域无非就两种:全局变量和局部变量。
javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
注意点:在函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明的是一个全局变量!

2、如何从外部读取函数内部的局部变量?
出于种种原因,我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。
那就是在函数内部,再定义一个函数。

 <script> 
function f1(){
    var n=999;
    return function f2(){
      return n;
    }
  }
window.console.log(f1()())//999

</script> 

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
这就是Javascript语言特有的”链式作用域“结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

再思考一下下面两个例子的输出结果:

当前this是全局作用域下的:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
window.console.log(window.object.getNameFunc()());//The Window

当前this是局部作用域下的:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  window.console.log(window.object.getNameFunc()()); //My Object

代码中故意多写了window对象是为了让新同学可以更加清晰点理解整个作用域的过程,希望大神不要吐槽。

基础已经理解了,那么我们来个难的题目:

function fun(n,o){
    console.log(o);
    return {
        fun:function(m){//[2]
            return fun(m,n);//[1]
        }
    }
}
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);

答案如下图:
这里写图片描述

转换为等价代码
return返回的对象的fun属性对应一个新建的函数对象,这个函数对象将形成一个闭包作用域,使其能够访问外层函数的变量n及外层函数fun,为了不将fun函数和fun属性搞混,我们将上述代码修改如下:
代码B

function _fun_(n,o){
    console.log(o);
    return {
        fun:function(m){
            return _fun_(m,n);
        }
    }
}

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

var b=_fun_(0).fun(1).fun(2).fun(3);
//undefined,0,1,2

var c=_fun_(0).fun(1);//undefined,0,
c.fun(2);//1
c.fun(3); //1

那么就有同学问了,为什么可以这样改呢,你怎么能确定[1]处的fun不是[2]代码所在处的fun呢,要知道此处的fun属性可是指向一个函数对象哦~
这里就要说到JS的词法作用域,JS变量作用域存在于函数体中即函数体,并且变量的作用域是在函数定义声明的时候就是确定的,而非在函数运行时。
如下代码

var name="global";
function foo(){
    console.log(name);
}
function fooOuter1(){
    var name="local";
    foo();
}
fooOuter1();//输出global 而不是local,并且和闭包没有任何关系
function fooOuter2(){
    var name="local";
    function foo(){
        console.log(name);
    }
    foo();
}
fooOuter2();//输出local 而不是global,在函数声明是name变量作用域就在其外层函数中,嗯嗯就是闭包~

好了我们回到题目,在函数声明定义阶段,[2]处的匿名函数进行定义声明,发现在[1]处需要引用一个名为fun的函数对象,那么首先在当前函数体内寻找,发现没有,那么就到其外层函数-这个匿名函数的包裹函数中去查找,发现也没有,到外层函数中去,发现外面没有函数包裹了,那就到全局环境下去找,额偶终于找到了……就把fun函数指定为全局环境下的fun函数对象并加入到匿名函数的闭包中去。至此我们就知道代码B为什么和代码A是等价的了~~~

创建闭包作用域
JS在词法分析结束后,确定了1个闭包,就是返回的对象fun属性对应的匿名函数的闭包-访问全局环境下的func及其外层函数的函数内部变量n;
在每次func执行的时候,都会将闭包中变量的作用域信息传递到函数执行环境中,供函数执行时获取变量值时使用

执行输出

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

fun函数执行,因为第2个参数未定义,输出undefined。然后返回一个对象,带有fun属性,指向一个函数对象-带有闭包,能够访问到fun和变量n_
a.fun(1)执行返回的对象的fun方法,传入m的值1,调用返回fun(1,0)
所以输出为0,a.fun(2),a.fun(3)和a.fun(1)
断点下的调试截图:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

var b=_fun_(0).fun(1).fun(2).fun(3);

等价代码

var b=_fun_(0);
var b1=b.fun(1);
var b2=b1.fun(2);//[3]
var b3=b2.fun(3);//[4]

前2句和上面的输出相同undefined,0,当[3]被调用时,b1对象中有一个闭包,引用了fun函数及外层函数变量n=1,所以匿名函数执行的函数调用为fun(2,1),输出结果为1,并返回一个新的对象。
当[4]执行时,b2对象也有一个闭包,引用了fun函数及外层函数变量n=2,执行fun(3,2),输出结果为2

var c=fun(0).fun(1);//undefined,0,
c.fun(2);//1
c.fun(3); //1

能看懂前面的代码执行解释,理解上面的代码执行输出就不会有问题了

猜你喜欢

转载自blog.csdn.net/a419419/article/details/80220372