最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 词法/函数/块作用域及闭包

    正文概述 掘金(littlebird)   2021-01-25   471

    说到作用域,同学们可以一起想象一下它的词义 “在某一个区域里产生作用“。让我们看一段代码:

    词法/函数/块作用域及闭包

    毫无疑问,这段代码输出的是张三。

    词法/函数/块作用域及闭包  

    这行代码输出的也是张三,很简单,name是window全局作用域下的变量。

    任何javascript代码片段都要在执行前进行编译。变量的赋值操作会执行两个操作,一是编译器会在当前作用域下声明一个变量,二是运行时引擎会在作用域中查找变量,如果找到就为变量赋值,否则就会抛出一个异常 。

    我们先来了解一个概念LHS and RHS 。当变量出现在赋值操作的左边时,采用LHS查询,出现在右边,则采用RHS查询 。

    consoloe.log(name)这句代码name采用的是RHS引用,需要查找并赋值给name,name = '张三',这里的name则是采用LHS引用,查找到为 = '张三‘存放的容器 。赋值操作的左右并不意味着等号的左右,LHS可以理解为赋值操作的目标是谁,RHS可以理解为谁是赋值操作的源头 。

    我们看看下面这段代码:

    词法/函数/块作用域及闭包

    这段代码到底发生了什么呢:

    1. 引擎对foo进行RHS引用,查找foo的目标是什么
    2. 编译器查找作用域,找到foo是一个函数
    3. 引擎执行foo函数
    4. 引擎一行一行执行下来,对name进行LHS引用,查找name的源头是谁
    5. 编译器查找作用域,找到foo的形式参数
    6. 引擎把张三赋值给name
    7. 引擎对consoleRHS引用
    8. console是一个内置对象
    9. 引擎查找console,找到log()函数
    10. 引擎再次对name进行RHS
    11. 编译器查找作用域
    12. 引擎把name的值传入log()函数中

    在上面是用var来声明变量的,值得注意的是:

    在浏览器对象中,所有用var所声明的全局变量和函数都会变成window对象的方法和属性。

    词法/函数/块作用域及闭包

    词法/函数/块作用域及闭包

    如果把var换成let和const,就会是如下的情况:

    词法/函数/块作用域及闭包

    词法/函数/块作用域及闭包

    因为let和const不会被放进window对象里,this是运行时的东西,现在this是对window的映射,所以无法在window对象里找到变量。

    接下来我们来看看词法作用域

    词法作用于是由你在写代码时把变量和块作用域写在哪里决定的,因此当词法分析器处理代码时会保持作用域不变:

    词法/函数/块作用域及闭包

    在这个例子中有三个逐级嵌套的作用域,包含着整个全局作用域,其中只有一个标识符: foo 。包含着 foo 所创建的作用域,其中有三个标识符: a 、 bar 和 b 。包含着 bar 所创建的作用域,其中只有一个标识符: c 。

    作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止,这个也被称为作用域链。

    现在我们来看看函数作用域

    词法/函数/块作用域及闭包

    在函数作用域中,函数外部访问函数内部。这个特点可以用来做隐藏效果。从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,实际上就是把这些代码“隐藏”起来了。

    词法/函数/块作用域及闭包

    在这段代码中,foo2()和b应该是foo1的私有内容,这样暴露在全局,很危险,我们可以做以下改造。

    词法/函数/块作用域及闭包

    在上面代码中最外层foo也是暴露在全局中的,我们可以用一个立即调用函数来实现隐藏

    词法/函数/块作用域及闭包

    我们再来看看块作用域

    词法/函数/块作用域及闭包

    我们在这个循环体外面打印i,得到的是10,这是因为在这段代码中i是绑定在外部作用域的。这个时候就可以用到块作用域了,就是在es6引入的let const

    词法/函数/块作用域及闭包

    我们再来看看提升的问题:

    词法/函数/块作用域及闭包

    词法/函数/块作用域及闭包

    这是因为js引擎会先对js代码编译,然后再执行,变量和函数在内的所有声明都会在任何代码被执行前首先被处理。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。

    所以代码会处理成这样:

    词法/函数/块作用域及闭包

    这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫作提升。

    有一个注意的点,函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量。

    词法/函数/块作用域及闭包

    这段代码被处理成了这样:

    词法/函数/块作用域及闭包

    var foo 尽管出现在 function foo()的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

    最后我们就可以来学习闭包了:

    当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

    我们看看下面这段代码

    词法/函数/块作用域及闭包

    在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz() ,实际上只是通过不同的标识符引用调用了内部的函数 bar() 。bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。js引擎有垃圾回收机制,在变量使用完后会被销毁,但是这段程序内部会被保存下来,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

    词法/函数/块作用域及闭包

    词法/函数/块作用域及闭包

    我们来看看这段代码

    词法/函数/块作用域及闭包

    这个循环的终止条件是 i 不再 <=5 。条件首次成立时 i 的值是6。因此,输出显示的是循环结束时 i 的最终值。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i 。

    所以我们做出以下改动:

    词法/函数/块作用域及闭包

    每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的,作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。当然也可以用let const来解决,因为它们也有单独的块作用域。

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

    本文章参考《你不知道的javascript上卷》《javascript高级程序设计》


    起源地下载网 » 词法/函数/块作用域及闭包

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元