• 你不知道的JS之作用域和闭包 附录


     原文:你不知道的js系列

    A 动态作用域

    动态作用域 是和 JavaScript中的词法作用域 对立的概念。

    动态作用域和 JavaScript 中的另外一个机制 (this)很相似。

    词法作用域是在代码编写时就定义好了的(假设没有使用 eval() 或者 with 欺骗词法作用域)

    动态作用域也就意味着在运行时才能动态确定。

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

    词法作用域会对 foo() 中的 a 进行 RHS 引用查询,最后会得到全局变量 a 的值。

    而动态作用域相反,它不关心函数和代码块声明的位置和方式,只关心他们在哪儿被调用。换句话说,作用域链是基于调用栈,而不是代码的嵌套。

    所以,如果 JavaScript 有动态作用域,那么当 foo() 执行的时候,理论上输出的结果会是 3。

    因为这时候,foo() 会从调用它的那个栈向上去查找 a ,就找到 bar() 内部的值为 3 的那个变量 a 了。

    JavaScript 事实上不存在动态作用域,虽然 this 机制看起来想动态作用域。

    词法作用域是代码编写时确定的,动态作用域(和 this)是在运行时确定的。

    词法作用域关心的函数声明的位置,动态作用域关心的是函数被调用的位置。

    最后:this 关心是一个函数调用的方式,关于这部分要去看 “this 和 对象原型” 的内容

    B 块作用域的 Polyfill

    在第(三)节中介绍了块作用域。在 ES3 的时候就已经存在以 with 和 catch 语句创建的块作用域了。

    但是 ES6 引入了 let 之后才是对块作用域的完整支持,但是如果想在 ES6 之前的环境中使用块作用域应该怎么做?

    比如下面的代码:

    {
        let a = 2;
        console.log( a ); // 2
    }
    
    console.log( a ); // ReferenceError

    在 ES6 之前的环境中可以这么做:

    try{throw 2}catch(a){
        console.log( a ); // 2
    }
    
    console.log( a ); // ReferenceError

    但是这样的代码既难看又奇怪。

    其实,你可以使用一些工具将 ES6+ 的代码转译成可以在老版本的环境中运行的代码。

    Tracer

    Google 有一个叫做 Tracuer 的项目,它就是普遍用来转译 ES6 代码的工具。TC39 (ECMA 的 Technical Committee 39)依赖这个工具测试 JavaScript 的新特性。

    它可以把上面的代码转成如下的代码:

    {
        try {
            throw undefined;
        } catch (a) {
            a = 2;
            console.log( a );
        }
    }
    
    console.log( a );

    隐式 vs. 显示的代码块

    下面是另外一个 let 的形式,叫做 let 语句或者 let 块(相对于 let 声明):

    let (a = 2) {
        console.log( a ); // 2
    }
    
    console.log( a ); // ReferenceError

    这个 let 语句显式地创建了一个作用域绑定。这不仅是让块看起来更明显,在代码重构方面鲁棒性也更强,它把所以声明强制放在块的顶部,更容易分清块作用域包含了哪些内容。

    与它对应的模式是,很多开发者在函数作用域的时候,也会将他们的 var 声明提前到函数的顶部。

    这种 let 语句刻意将声明提前,如果你没有在代码中滥用 let 声明的话,你的块作用域声明会更容易识别和维护。

    但是,ES6 中还没有支持 let 语句。官方的 Traceur 也不接受这种方式。

    所以我们可以在代码中加一点注释:

    /*let*/ { let a = 2;
        console.log( a );
    }
    
    console.log( a ); // ReferenceError

    性能

    为什么不使用 IIFE 创建作用域呢?

    首先,try/catch 的性能是比较低的,但它没有原因必须这么低。TC39 官方支持的 ES6 转译工具也使用 try/catch,所以 Traceur 团队已经让 Chrome 提升了 try/catch 的性能。

    第二,IIFE 和 try/catch 没有可比性。将任何一段代码包装在一个函数中都可能会改变这段代码里面 return,this,break 以及 continue的含义。所以 IIFE 只适用于特定场景。

    问题变了:你是否真的想要块作用域,如果需要,这些工具可以帮你实现。如果不需要,继续使用 var 写代码也没事的!

    C 词法-this

    ES6 添加了一个特殊的语法——箭头函数:

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

    这个箭头经常被当作关键字 function 的简写形式。

    但箭头函数不只是用来减少敲击键盘的次数的

    下面的代码就有一个问题

    var obj = {
        id: "awesome",
        cool: function coolFn() {
            console.log( this.id );
        }
    };
    
    var id = "not awesome";
    
    obj.cool(); // awesome
    
    setTimeout( obj.cool, 100 ); // not awesome

    在 setTimeout 中的 cool() 回调的 this 绑定丢了,通常的解决办法是在内部声明 var self = this;,如下:

    var obj = {
        count: 0,
        cool: function coolFn() {
            var self = this;
    
            if (self.count < 1) {
                setTimeout( function timer(){
                    self.count++;
                    console.log( "awesome?" );
                }, 100 );
            }
        }
    };
    
    obj.cool(); // awesome?

    这只是把整个问题从 理解和正确使用 this 绑定 回退到我们更使用起来更舒服的词法作用域上,self 成了一个标识符,不用关心 this 绑定发生了什么。

    大家都不喜欢写冗余的东西,尤其是一遍又一遍重复这些东西。所以 ES6 出现了一个解决办法,箭头函数引入了一个现象叫做 “词法 this”

    var obj = {
        count: 0,
        cool: function coolFn() {
            if (this.count < 1) {
                setTimeout( () => { // arrow-function ftw?
                    this.count++;
                    console.log( "awesome?" );
                }, 100 );
            }
        }
    };
    
    obj.cool(); // awesome?

    简单的解释就是箭头函数不像普通函数的 this 绑定一样,他们会将 this 的值绑定在当前的词法作用域中。

    在这段代码中,this 的绑定 “继承” 了函数 coo() 绑定的 this。

    箭头函数实际上只是将开发者普遍存在的错误写进了语法,因为他们将 this 绑定规则 和词法作用域混为一谈。

    注:另一个箭头函数非议是他们都是匿名的,第(三)节介绍了为什么匿名函数不如命名的函数。

    解决这个问题的另一个办法就是,正确使用 this绑定:

    var obj = {
        count: 0,
        cool: function coolFn() {
            if (this.count < 1) {
                setTimeout( function timer(){
                    this.count++; // `this` is safe because of `bind(..)`
                    console.log( "more awesome" );
                }.bind( this ), 100 ); // look, `bind()`!
            }
        }
    };
    
    obj.cool(); // more awesome

    如果你完全理解了词法作用域和闭包,那么理解 词法this 的用法是轻而易举!小菜一碟

  • 相关阅读:
    Locale IDs Assigned by Microsoft (zz)
    MFC 版本
    vs macro shortcuts
    关于strassen矩阵乘法的矩阵大小不是2^k的形式时,时间复杂度是否还是比朴素算法好的看法
    团体程序设计天梯赛 L2-016. 愿天下有情人都是失散多年的兄妹
    团体程序设计天梯赛-练习集 L1-031. 到底是不是太胖了
    团体程序设计天梯赛 L3-004. 肿瘤诊断
    团体程序设计天梯赛 L2-006. 树的遍历 L2-011. 玩转二叉树
    团体程序设计天梯赛 L1-011. A-B
    团体程序设计天梯赛 L1-010. 比较大小
  • 原文地址:https://www.cnblogs.com/xiyouchen/p/10319899.html
Copyright © 2020-2023  润新知