资讯专栏INFORMATION COLUMN

再谈闭包-词法作用域

Ku_Andrew / 2092人阅读

摘要:权威指南第六版关于闭包的说明采用词法作用域,也就是说函数的执行依赖于变量的作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。闭包这个术语的来源指函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量包裹了起来。

最近打算换工作,所以参加了几次面试(国内比较知名的几家互联网公司)。在面试的过程中每当被问起闭包,我都会说闭包是作用域的问题?令人惊讶的是几乎无一例外的当我提到作用域时我都被打断,并提醒我好好的找一本javascript的书籍看看。而当我忍不住去问面试官对于闭包你是怎么理解的?我得到的大多是回答都是通过返回一个函数,然后通过这个函数来访问局部变量(私有变量),有的还会扯上声明提前,this指向等。听到这些,心理默默滴血,没错,我只是菜鸟。

看到有这么多人不理解闭包,所以我不得不再次的提及,如果有误,欢迎指正。

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

先从阮一峰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这种没有原型概念的语言难道就没有作用域链了吗?

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

总结

闭包:函数执行时变量的获取从声明的作用域处去获取(注意链式关系,当前没有就往父级找,知道全局作用域)

this:显示指定调用者则this就指向谁,如未指定则为全局对象

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87715.html

相关文章

  • 先有蛋还是先有鸡?JavaScript 作用闭包探析

    摘要:而闭包的神奇之处正是可以阻止事情的发生。拜所声明的位置所赐,它拥有涵盖内部作用域的闭包,使得该作用域能够一直存活,以供在之后任何时间进行引用。依然持有对该作用域的引用,而这个引用就叫闭包。 引子 先看一个问题,下面两个代码片段会输出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...

    elisa.yang 评论0 收藏0
  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • [JS]《你不知道的Javascript·上》——词法作用闭包

    摘要:吐槽一下,闭包这个词的翻译真是有很大的误解性啊要说闭包,要先说下词法作用域。闭包两个作用通过闭包,在外部环境访问内部环境的变量。闭包使得函数可以继续访问定义时的词法作用域。 闭包是真的让人头晕啊,看了很久还是觉得很模糊。只能把目前自己的一些理解先写下来,这其中必定包含着一些错误,待日后有更深刻的理解时再作更改。 吐槽一下,闭包这个词的翻译真是有很大的误解性啊…… 要说闭包,要先说下词法...

    guqiu 评论0 收藏0
  • 你应该要知道的作用闭包

    摘要:写在前面对于一个前端开发者,应该没有不知道作用域的。欺骗词法作用域有两个机制可以欺骗词法作用域和。关于你不知道的的第一部分作用域和闭包已经结束了,但是,更新不会就此止住未完待续 这是《你不知道的JavaScript》的第一部分。 本系列持续更新中,Github 地址请查阅这里。 写在前面 对于一个前端开发者,应该没有不知道作用域的。它是一个既简单有复杂的概念,简单到每行代码都有它的影子...

    JouyPub 评论0 收藏0
  • 闭包详解一

    摘要:再看一段代码这样就清晰地展示了闭包的词法作用域能访问的作用域将当做一个值返回执行后,将的引用赋值给执行,输出了变量我们知道通过引用的关系,就是函数本身。 在正式学习闭包之前,请各位同学一定要确保自己对词法作用域已经非常的熟悉了,如果对词法作用域还不够熟悉的话,可以先看: 深入理解闭包之前置知识---作用域与词法作用域 前言 现在去面试前端开发的岗位,如果你的面试官也是个前端,并且不是太...

    cnio 评论0 收藏0

发表评论

0条评论

Ku_Andrew

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<