最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 重新认识 JS 中的闭包

    正文概述 掘金(Ethanlv_吹风机)   2021-04-26   597

    浅约束与深约束

    作用域确定了程序里一个语句的引用环境,语言的作用域规则可以大致分为两类,静态作用域规则和动态作用域规则。现代的语言大多是静态作用域的,当然也少有语言是绝对的静态作用域,这就和少有语言是存粹的面向对象式或少有语言是纯粹的解释式一样,语言的边界本就是模糊的。

    关于静态作用域和动态作用域我相信大家都很清楚了,不多做解释。

    • 静态作用域规则 也称为词法作用域,是指引用环境依赖于可以出现名字声明的程序块的词法嵌套关系。

    • 动态作用域规则 是指引用环境依赖于运行时遇到各种声明的顺序。

    但是,上面的规则没有考虑到一个特殊的场景:在那些允许创建子程序引用,例如把 子程序当作参数传递的语言里,何时把作用域规则应用于这种子程序?是在创建这种引用时,还是在子程序被调用时

    对于动态作用域来说,这一问题就显得格外重要,当然静态作用域也是需要考虑到的。

    要讲清楚这个问题,首先,让我们来把 JS 当作一门动态作用域的语言

    现在,我们有个函数 isErCiYuan,它接收两个参数,第一个参数是人物特征,第二个参数是一个子程序参数,我们期望它接收一个打印函数 print,print依据 comment 值的不同而输出不同的结果。自然的,我们会在 isErCiYuan 这个函数中建立一个临时变量 comment 依据函数的第一个参数而为 comment 赋不同的值。

    // 当然这是段无法运行的代码
    function print(){
        console.log(`我${comment}二次元`)
    }
    function isErCiYuan(characteristic,print){
        let {gender,hobby} = characteristic
        let comment = ''
        //1 -> boy,0-> girl
        if(gender === 1 && hobby === '喜欢穿女装'){
            comment = '是'
        }else{
            comment = '不是'
        }
        print()
    }
    
    

    为了让上面的代码以我们期待的方式正常工作,理所当然的我们要在 print 函数被实际调用时再去建立变量 comment 的引用关系。

    而这种让作为参数传递的子程序推迟建立引用环境约束的方式称为 浅约束,通常情况下,采用动态作用域规则的语言都将这种约束方式作为默认方式,与之对应的当然就是深约束了,即 在子程序作为参数传递时就做好环境约束。同样拿上面那段代码来考虑,comment 此时就应该是空字符串了。

    你没有猜错,静态作用域规则的语言采用的方式基本都是深约束

    等等,为什么静态作用域还需要考虑这一问题,我们知道,在静态作用域规则下,名字的意义本来就依赖于其词法嵌套关系/位置,而不是实际的执行流呀。

    看下面这段代码:

    function A(num,fn){
        function B(){
            console.log(num)
        }
        if(num > 0){
            fn()
        }else{
            A(1,B)
        }
    }
    function doNothing(){}
    A(0,doNothing) 
    

    在上面这种情况下,我们看到函数 A 递归执行了,这就导致 num 实际上是存在多个实例的,那么最终输出的到底是 0,还是 1 呢。

    如果是 0,那就是深约束,因为它在子程序 B 作为参数传递时就抓住了当前实例,这一行为没有被推迟,此时 Num 是 0。JS 中打印出来的的结果就是 0,你也可以自己试下。

    如何实现深约束——闭包

    要想实现深约束,就需要创建一种能显式地表达引用环境的东西。我们一般将某个函数(一般是入口函数)以及这种相关联的引用环境(理解为一个符号表)称作闭包。闭包的特点是它捕获了自由变量(在函数外部定义但在函数内被引用)。这一行为可以用于解释我们常说的 “闭包解决了父函数执行后上下文销毁导致子函数不能获取到父函数中变量的问题。”

    现在,我们给上面的代码加点东西,直观的看下闭包是什么:

    function A(num,fn){
        function B(){
            console.log(num)
        }
        if(num > 0){
            fn()
        }else{
            A(1,B)
            console.log(B.prototype) //加在这了
        }
    }
    function doNothing(){}
    A(0,doNothing) 
    

    B.prototype.constructor 下的 [[Scopes]] 中有一个 Closure(A),里面保存着变量 num,其值为 0。

    重新认识 JS 中的闭包

    上面的这种闭包不太容易看出来,我们来看个更普遍的例子。

    function fn1(){
        let a = 0;
        function fn2(){
            let b = 1;
            return function fn3(){
                console.log(a,b)
            }
        }
        return fn2()
    }
    let fn4 = fn1()
    

    打印出 fn4 的 prototype 看看:

    重新认识 JS 中的闭包

    可以看到这里存在两个闭包,以由内到外的顺序排列,看到这,是不是作用域链的概念也更清晰了呢。

    最后,彼得·兰丁(Peter Landin)在1964年将术语“闭包”定义为一种包含环境成分和控制成分的实体,而闭包的概念首次在1970年于 PAL 编程语言中完全实现,用来支持词法作用域的 头等函数,也就是前文所阐述的当子函数能作为参数传递时如何实现深约束的问题。我们可以将闭包简单理解为捕获了特定自由变量的函数,借助闭包的特点,我们可以实现私有变量的持久性和信息隐藏,这在很多情况下非常有用,闭包也因此而广为流传。

    JS 中闭包的实现

    function fn1(){
        let a = 0;
        function fn2(){
            let b = 1;
            return function fn3(){
                console.log(a,b)
            }
        }
        return fn2()
    }
    let fn4 = fn1()
    

    闭包的实现在思路上是比较简单的,以上面的代码为例, JS 引擎在预编译阶段通过对 fn2 内部函数的词法扫描,找出是否存在内部函数引用外部函数变量的情况,如果存在就打入对应的 Closure,最后放到 [[Scopes]] 中。要注意的是,这个过程是静态分析的,那 eval 怎么办呢。

    function test(){
        const a = 1;
        const b = 2;
        return function(){
            const c = 3
            eval('console.log(a,c)')
        }
    }
    

    对于上面这段代码

    重新认识 JS 中的闭包

    你会发现,eval 把变量都包进去了,即使是实际上并没有使用的。这种降级策略也许就是 eval 执行效率低的原因之一吧,而 如果使用 new Function,因为其语法让我们得以显式的指定变量名,自然就可以在静态分析时保证不打包多余变量到 Closure 中。

    function test(){
        const a = 1;
        const b = 2;
        return function(){
            const c = 3
            new Function(a,'console.log(a,c)')
        }
    }
    

    重新认识 JS 中的闭包

    参考

    • zh.wikipedia.org/wiki/%E9%97…
    • 《程序设计语言实践之路》第二版

    起源地下载网 » 重新认识 JS 中的闭包

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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