您的位置:首页 > Web前端 > JavaScript

再谈闭包-词法作用域

2016-07-14 16:15 459 查看


闭包只是为了实现词法作用域而用到的一种数据结构而已

先从阮一峰09年写的一篇关于闭包的文章开始(原文地址)文中说"可以把闭包理解为就是能够读取其他函数内部变量的函数,因为js中,只有函数内部的子函数才能读取局部变量,因此也可以把闭包定义在一个函数内部的函数。所以闭包本质就是将函数内部和函数外部连接起来的一座桥梁"。

毕竟不是专业学习js的,也不是程序语言方面的专家,在这里就不去计较了,下文会给出更完整的闭包说明。(PS:个人还是比较欣赏阮一峰写的文章的,能将一些概念讲得通俗易懂)

最后还留下了一个思考题

示例01:
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());


示例02:
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());


看完这道题,就希望大家将this和闭包分开,不要给自己找麻烦?

在开始说闭包之前,需要理解好以下的概念:

词法作用域(静态作用域)

函数上下文

之前简单的提过,词法作用域简单的理解就是函数的上下文是在声明是确定的,而不是在调用时确定的。这里不想对这两个名词作过多的解释,我们知道js/ruby/python等主流的语言都是词法作用域就好,因为与之相对的动态作用域有许多的问题,所以现在的语言基本都是词法作用域的。

函数上下文就简单的理解为函数执行的环境好了,在这个环境中保存了函数执行所需的变量。


JavaScript权威指南第六版关于闭包的说明

JavaScript采用词法作用域,也就是说函数的执行依赖于变量的作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。为了实现词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为"闭包"。
当定义一个函数时,它实际上保存了一个作用域链。当调用这个函数时,它创建

一个新的对象来存储它的局部变量,并将这个对象添加到保存的作用域链上。

(闭包可以简单的理解为函数用来存储它的局部变量的对象,这个对象我们来说是不可见的,是js解释器实现的过程中才会用到的。每一个函数都会有这样的一个对象,作用域链则是这些对象之间的关系。)

"闭包"这个术语的来源:指函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量"包裹"了起来。

看文字可能会比较绕,下面是书中的一个例子:
var scope = "global scope";            /*全局变量*/
function checkscope(){
var scope = "local scope";        /*局部变量*/
function f(){return scope;}
return f();
}
checkscope();                        /*=>"local scope"*/

var scope = "global scope";            /*全局变量*/
function checkscope(){
var scope = "local scope";        /*局部变量*/
function f(){return scope;}
return f;
}
checkscope()();                        /*=>"local scope"*/


checkscope最后的返回值都是一样的,即"local scope"。

上面两个例子的不同之处就是函数f执行的地方不同,一个在checkscope这个作用域里调用的(每一个函数都是一个作用域),一个在全局作用域里调用的。回忆之前说的,函数调用时的上下文或者说作用域是在声明时确定的,所以与调用的位置无关,即f函数的调用位置虽然不同,调用的环境虽然不同,但最终的结果都是一样的。

说到这里,闭包就说得差不多了,回过头来看一下常规的对于闭包的理解:

通过返回函数的形式取得函数的局部变量。

这种说法本身没有错,但它只是闭包的一种表现形式,

比如将上例进行下更改:
var scope = "global scope";            /*全局变量*/
function checkscope(fn){
var scope = "local scope";        /*局部变量*/
function f(){return scope;}
fn(f);
}
checkscope(function(func){
var scope = "func scope";
return func();
});                                    /*=>"local scope"*/


改成类似回调的执行方式,结果还是一样的,注意结果并不是func scope,但是并没有返回f函数这一说,难道这就不是闭包了吗?(当然这里有点扣字眼)


说说this

想起最开始时的那个思考题了吗?与闭包就没什么关系(注:任何一个函数其实都用到了闭包,但我们暂且考虑两层以及两层以上的嵌套情况,未嵌套情况下因为使用的都是全局作用域,结果应该是很直观的)。this一般用来代表函数的调用对象,它和上下文对象并不是同一个,上下文对象对我们来说是不可见的,除了全局作用域。

示例01:
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());        /*The Window*/


示例02:
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
var that = {name: 'xiaofu'};            /*干扰项*/
  alert(object.getNameFunc()());        /*My Object*/


我们再来看一下题目,示例01输出的是"The Window",示例02输出的是"My Object"。

说明:

示例01最终执行的是 function(){return this.name},因为没有显示指明调用对象,所以其this执行全局对象。

示例02先调用object.getNameFunc(),因为显示的指定了调用对象,所以内部的this是object(注:这里说的是this指向的问题,还没有说闭包),接着执行function (){return that.name},这个函数在getNameFunc这个函数作用域中声明的,所以它调用的时候使用的是这个作用域,即得到var that = this;的这个that;而不是外面的that。


作用域链不等同于原型链

真不知道这两个不相关的东西怎么会被等同起来看待,以后谁告诉我它们是同一个东西,我就想问了,ruby、python这种没有原型概念的语言难道就没有作用域链了吗?

更有甚者将变量声明提升和闭包混在一起,也是醉了。

源引:https://segmentfault.com/a/1190000004549730
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  闭包 javascript