资讯专栏INFORMATION COLUMN

小菊花课堂之JavaScript作用域与闭包

lunaticf / 2011人阅读

摘要:而闭包却能阻止这件事情发生。由于的声明位置使它拥有涵盖内部作用域的闭包,使得该作用域能够一直存在,以供在之后进行引用。到这里,小菊花课堂之闭包的内容就告一段落啦,感谢各位能耐心看到这里。

由于前段时间项目没有那么忙,然后我这人一天不看点啥就非常焦虑,于是二刷《你不知道的JavaScript》,现在读到闭包,想着看完这一章节,写点东西也是挺好的,所以有了下面的内容,如有不对的地方,敬请斧正,欢迎探讨。

作用域

我们一般讲到闭包,就会谈到作用域,那么作用域又分为了函数作用域块级作用域 ,在这里,我们简单的介绍一下这两种作用域。

函数作用域

函数作用域是指,属于这个函数的全部变量都可以在整个函数的范围内使用及服用(事实上在嵌套的作用域中也可以使用)。
我们先来看一个例子

var a = 2;
function foo() {
    var a = 3;
    console.log(a); // 3
}
foo();
console.log(a); // 2

可以看到,这种技术虽然能解决一些问题,但是也会导致其他的问题。首先,必须声明一个具名函数foo(),意味着foo这个名称本身“污染”了所在的作用域。其次,必须显式地(有隐式和显式的区别,这里暂且不表)通过函数名(foo())调用这个函数才能运行其中的代码。
那么我们有没有其他的办法呢,继续往下看。

var a = 2
(function foo() {
    var a = 3;
    console.log(a); // 3
})();
console.log(a); // 2

比较一下这两段代码。第一段中foo被绑定在所在所用域中,可以直接通过foo()来调用调用它。第二段中foo被绑定在函数表达式自身的函数中而不是所在作用域中。

块级作用域

在JavaScript中,并不支持块作用域,但是我们为什么还要说它,因为它的风格在JS开发中很常见。
在日常的开发或者学习工作中,我们其实经常能见到类似块作用域,思考以下代码:

for(var i=0; i<6; i++){
    console.log(i);
}

在for循环的头部直接定义了变量 i,通常是因为只想在for循环内部的上下文中使用 i,而忽略了 i 会被绑定在外部作用域(函数或全局)中的事实。当使用var时,它写在哪里都是一样的,因为它们最终都会属于外部作用域。
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息。

总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。 闭包 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。

继续思考下面代码:

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2

看到了吗,这就是闭包的效果。
函数bar()的词法作用域能够访问foo()的内部作用域。然后将bar()函数本身当作一个值类型进行传递。
foo()执行后,其返回值赋值给变量baz并调用baz(),实际上只是通过不同的标识符引用调用了内部的函数bar()
我们知道,JavaScript引擎有垃圾回收器用来释放不再使用的内存空间。而闭包却能阻止这件事情发生。事实上内部作用域依然存在,而没有被回收。
由于bar()的声明位置使它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存在,以供bar()在之后进行引用。
再看下面例子

function foo() {
    var a = 2;
    function baz() {
        console.log(a); // 2
    }
    bar(baz);
}
function bar(fn) {
    fn(); // 这是闭包
}
foo();

无论通过何种手段将内部函数传递到所在的词法作用域外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

OK,本质上无论何时何地,如果将(访问它们各自词法作用域的)函数当作第一级的值类型并到处传递,你就能看到闭包了。在定时器、事件监听器、Ajax请求或其他异步或同步任务中,只要使用了回调函数,实际上就是在使用闭包。

循环与闭包

先看看最常见的for循环。

for(var i=1; i<=5; i++) {
    setTimeout(function timer(){
        console.log(i);
    }, i*1000)
}

你觉得最后会输出什么,每秒输出一次,分别输出1~5?

那就错啦,实际上,它是会每秒输出一次,但输出~对,就是66666。
为什么?
延迟函数的回调会在循环结束时才执行,而循环结束的条件就是i不再<=5。当定时器运行时,即使每个迭代中执行的是setTimeout(...,0),所有的回调函数依然是在循环结束后才被执行,所以每次都输出6。
根据作用域的原理,尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此只有一个i。
知道原因之后,我们可以对代码进行一些改造,看看有没有好事发生。

foo(var i=1; i<=5; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j);
        }, j*1000);
    })(i);
}

Fine,我们终于改造好了,拥有了更多的词法作用域。在迭代中使用立即执行函数(IIFE)会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会有一个具有正确值的变量。

到这里,小菊花课堂之JavaScript闭包的内容就告一段落啦,感谢各位能耐心看到这里。

此时是0点52分,时候也不早了,该洗洗睡啦。
see u ~ again

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

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

相关文章

  • 菊花课堂JavaScript关于this

    摘要:另外,的绑定和函数声明的位置没有任何关系,之取决于函数的调用方式。请看下面代码这样,我们就可以在调用的时候强制把它的绑定到上绑定在传统的面向类语言中,使用初始化类时会调用类中的构造函数。 关于this 上一章我们讲了关于作用域和闭包的相关知识,现在开始新一轮的学习,那就是JavaScript中最复杂的机制之一---this关键字。它是一个很特别的关键字,被自动定义在所有函数的作用域中。...

    CHENGKANG 评论0 收藏0
  • 菊花课堂JS的防抖与节流

    摘要:文章来源详谈防抖和节流轻松理解函数节流和函数防抖函数防抖和节流好啦,今天的小菊花课堂之的防抖与节流的内容就告一段落啦,感各位能耐心看到这里。 前言 陆游有一首《冬夜读书示子聿》——古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。,其中的意思想必大家都能明白,在学习或工作中,不断的印证着这首诗的内涵。所以,又有了此篇小菊花文章。 详解 在前端开发中,我们经常会碰到一些会持...

    leoperfect 评论0 收藏0
  • 菊花课堂JS的防抖与节流

    摘要:文章来源详谈防抖和节流轻松理解函数节流和函数防抖函数防抖和节流好啦,今天的小菊花课堂之的防抖与节流的内容就告一段落啦,感各位能耐心看到这里。 前言 陆游有一首《冬夜读书示子聿》——古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。,其中的意思想必大家都能明白,在学习或工作中,不断的印证着这首诗的内涵。所以,又有了此篇小菊花文章。 详解 在前端开发中,我们经常会碰到一些会持...

    Yangder 评论0 收藏0
  • Javascript重温OOP作用域与闭包

    摘要:的变量作用域是基于其特有的作用域链的。需要注意的是,用创建的函数,其作用域指向全局作用域。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 作用域 定义 在编程语言中,作用域控制着变量与参数的可见性及生命周期,它能减少名称冲突,而且提供了自动内存管理 --javascript 语言精粹 我理解的是,一个变量、函数或者成员可以在代码中访问到的范围。 js的变量作...

    JessYanCoding 评论0 收藏0
  • 你不知道的JavaScript上卷作用域与闭包·读书笔记

    摘要:的分句会创建一个块作用域,其声明的变量仅在中有效。而闭包的神奇作用是阻止此事发生。依然持有对该作用域的引用,而这个引用就叫做闭包。当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。 date: 16.12.8 Thursday 第一章 作用域是什么 LHS:赋值操作的目标是谁? 比如: a = 2; RHS:谁是赋值操作的源头? 比如: conso...

    Raaabbit 评论0 收藏0

发表评论

0条评论

lunaticf

|高级讲师

TA的文章

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