最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一道“作用域”题引发的思考

    正文概述 掘金(zhangwinwin)   2021-01-19   406

    前言

    相信很多人都已经看过《你不知道的JavaScript》上卷这本书。我是在大三时看的这本书,这本书对我有思维上的冲击,第一次深入JavaScript语言内部。也由于当时我学JS不久,对书里很多知识都理解不透彻,比如下面这个在闭包章节中出现的例子:

    (关于这个例子的前生后世在这不多解释,详情请看书)

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

    书上的解释大概是这样的:每次迭代时let都会创建一个新的块作用域,而且变量i在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

    这带来几个问题:

    • for语句的循环条件声明不是只会执行一次吗?为什么会多次执行呢
    • let/const声明不是说不能重复声明吗?
    • varlet/constfor循环中的机制到底是怎么样的?

    那时似懂非懂,哪能想那么多,只能囫囵吞枣地背下这个例子,而且以后for循环都用上了let

    循环语句中的块作用域

    for循环的语法:

    for ([initializaion]; [condition]; [final-expression])
        statement
    

    for循环说起,首先无论是var声明还是let/const声明,循环体总是被多次初始化的。

    循环体总是被多次初始化的

    首先并不是所有看起来使用了一对大括号的都是块语句。forfor..in/of语句中的循环体被称为body,它将循环执行于一个由for语句创建的作用域中。尽管这个作用域对body中的语句有效,并且是按“块的实例化环境”的方式构建的,但它的生存周期以及其内部的“声明的实例化”过程都是由for语句负责的。

    循环体在每次迭代时都将处于一个全新的,为当前循环创建的实例化环境中。这就意味着所有的let/const声明将被重新初始化。例如在下面例子中:

    for (var i = 0; i < 3; i++) {
        let x;
        console.log(typeof x)
        console.log(x = i+1)
    }
    

    一道“作用域”题引发的思考 在这个例子中,变量x在最后被赋予一个新值,而在上一行中总是显示undefined。这表明在下一次循环中,变量x并没有继承上一次迭代的值。

    所以说在for循环里重复使用const定义一个变量是可以的。

    for (var i = 0; i < 3; i++) {
        const x = i + 1;
    }
    

    一道“作用域”题引发的思考

    循环条件

    body不同的是,for循环的3个表达式是处于同一个由for语句创建的环境中,这个环境也同时是循环体body每次创建的环境的父环境。

    如果在initialization表达式中出现了let/const声明时,那么无论statement是否是块,for语句总是会有一个自己的块,以便使用独立环境来登记这些标识符,let/const声明的变量总是位于上述for语句中自有的块中,而且按照for语句的语义,statement是执行于该块中的唯一一个语句。

    var声明的变量将被独立登记并在执行期由外部的块创建,而for语句只是引用它,因为var总是将变量声明在全局、模块或函数上下文中,而并不一定是当前块的作用域中。例如:

    for (var x = 5; x < 10; x++) 
        console.log(x)
        
    var x = 5;
    for (var x = 11; x < 15; x++)
        console.log(x)
    

    所以,for...offor await...offor...in等所有的for循环,只要在循环表达式部分使用了let/const声明,那么就具有一个自有的块,否则都没有这一特性。更进一步,所有的while/do...while循环也都不具有这一特性,因为它们不具备声明条件的语义。

    而当循环条件使用了let/const时,JavaScript处理后续的循环体的方式也会有所不同。在正常情况下像上面一样,如果循环体中存在块(也就是用{}),那么该块的父级将指向for语句所在的块,由于for语句没有块,所以循环体的块的父级将指向当前块中。

    // 循环体的块的父级指向当前块,也就是全局作用域
    for (var x = 5; x < 10; x++) {
        console.log(x)
    }
    

    一旦使用了let/const,那么循环体的块的父级将指向for语句自有的块。这样做的目的是上述块能够通过查询父级的块来找到for语句自有的块中的变量声明x

    // 循环体的块的父级指向for语句自有的块
    for (let x = 5; x < 10; x++) {
        console.log(x)
    }
    

    由于循环体可以不是一个块(也就是不使用{}),那么它应该执行在for语法所定义的块中。

    而且for语句还会为每次循环创建一个新的环境,这也是for...in,for...of等能够实现的原因。

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

    由于每次循环都有一个新环境,因此能够起到闭包的效果,将i的引用保留到使用它的时候。

    性能问题

    使用在循环条件中使用let/const时,每次循环都会创建一个新环境,很明显这会增加系统消耗。所以除非是在setTimeout或者Promise等机制中,通常不建议使用let/const来声明for语句的循环条件中的变量。

    但这并非不可避免的,只要弄清问题的本质!

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

    在这个例子中,本质是要保存for循环中每一个i值,而并非需要一个闭包来添加一个层。所以可以在循环过程中产生的不同的函数实例,由这些实例自己去保存值,从而避免let/const语法带来的新环境或者自己在外层包裹一个闭包带来的开销:

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

    一道“作用域”题引发的思考

    使用匿名函数:

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

    一道“作用域”题引发的思考

    结尾

    阅读完,如果觉得有帮助的请点点赞,支持一下。

    更多文章请移步楼主github,如果喜欢请点一下star,对作者也是一种鼓励。


    下载网 » 一道“作用域”题引发的思考

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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