• js——作用域和闭包


    1. js是编译语言,但是它不是提前编译,编译结果不能在分布式系统中移植。大部分情况下,js的编译发生在代码执行前的几微秒(甚至更短)

    2. 一般的编译步骤                      

    • 分词/词法分析:把字符串分解成词法单元
    • 解析/语法分析:将词法单元转换成一个由元素组成的语法结构树,抽象语法树AST
    • 代码生成:将AST转换成一组机器指令

    3. 三个工具

    • 引擎:控制整个程序的编译及执行过程
    • 编译器:负责语法分析及代码生成等
    • 作用域:收集并维护所有声明的标识符的访问权限

    4. var a = 2 的编译过程

    var a=2; --分解成-->

    词法单元

    var a = 2; =>

    var、a、=、2

    --解析成-->

    树结构

    AST

    --代码生成-->

    1. var a:询问作用域是否有a。

    如果有,则忽略。如果没有,则在当前作用域添加一个声明a

    2. a = 2:当前作用域是否有a,

    如果有,则赋值。如果没有,则向上一层作用域查找

    5. 代码生成中查找判断作用域是否存在某个变量的两种查找类型

      LHS RHS
    直观区别 变量在=左侧 变量不在=左侧
    操作 对变量赋值 取变量的值
    找不到?

    1. 严格模式

    抛出ReferenceError异常

    2. 非严格模式

    自动隐式创建一个全局变量

    抛出ReferenceError异常

    6. 作用域

    • 词法作用域

    定义在词法阶段的作用域。也就是在写代码时将变量和块作用域写在哪决定的。函数的作用域完全由声明时的位置决定

      • 全局作用域                                                                                                                                       
      • 函数作用域:每声明一个函数就会创建一个作用域。在该作用域内声明的变量或函数(标识符)都附属于它,可在整个函数范围内被使用。
      • 块作用域
    var
    //变量绑定在所在的函数内
    (function c(){
         //a是局部变量,b是全局变量
      var a = b = 3;
    })()
    (function c(){
         //a和b都是局部变量
      var a = 1, b = 3;
    })()
    try/catch
    try{
      //异常操作
         //catch创建一个块作用域,这里的变量只能在catch中使用
    }catch(err){
      //只有这里可以访问err
    }
    let

    ES6新引入的。将变量绑定在所在任意作用域{}中

    在循环中for(let i = 1; i < 5; i++),i在每次迭代中会声明,且每次迭代会用上一个迭代结束时的值来初始化

    const ES6新引入的,将变量绑定在所在任意作用域{}中,且值是固定不可修改的
    • 运行时修改作用域  eval、with

    7. 作用域嵌套

    在一个作用域A内创建一个新的作用域B,则B被嵌套在A中

    B可以访问A中的标识符。A不可以访问B中的标识符

    最外层的作用域是全局作用域

    作用域层层嵌套形成作用域链,在访问查找一个标识符时从最内层开始向外查找,一旦找到就停止,因此会出现外层的标识符被内层同名的所屏蔽 

    8. 闭包

    在各个文章中对闭包进行了解释,但是好像有很多说法。我理解得了的一个说法是:

    当函数可以记住并访问所在的词法作用域时,就产生了闭包

    函数A创建一个作用域A,在A中声明一个函数B(创建了作用域B),把函数B作为结果返回,作用域B会记得自己的作用域链,利用B可以向上层作用域访问

    9. 循环和闭包(一个好像特别常见的例子)

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

    说明:

    var i = 1:定义了一个全局变量

    setTimeout():在i秒后执行timer函数。timer是回调函数,在for循环执行完成才开始调用。timer会记住循环的作用域

    结果:for执行完成后开始调用timer,以每秒一次的频率输出5次6

    期待:每秒一次输出1,2,3,4,5

    结果解释:

    setTimeout时并没有让timer保存i的副本

    timer函数执行时,会去引用i的值,这时只有一个i=6

    修改1——立即执行

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

    结果:以每秒一次的频率输出5次6

    即使是立即执行,最后访问的变量也是全局的i

    修改2——立即执行+参数

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

    结果:每秒一次输出1,2,3,4,5

    修改3——块作用域循环变量

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

    结果:每秒一次输出1,2,3,4,5

    修改4——在循环中添加一个变量var j

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

    结果:以每秒一次的频率输出5次5(j和i一样为全局变量,j=5)

    修改5——块作用域变量

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

    结果:每秒一次输出1,2,3,4,5

    参考

    1. 《你不知道的javascript》上卷

    2. 还看了很多网上的说明,就不一一列举了,因为没记住具体哪些了

  • 相关阅读:
    行为树AI设计及BehaviorTree结构分析
    Android填坑—Error:Execution failed for task ':app:transformClassesWithDexForRelease'
    编程练习-字母异位词分组
    编程练习-判断是否为易混淆数
    编程练习-寻找最长回文串
    Android 8悬浮窗适配
    编程练习-字符串展开
    编程练习-只用0交换排序数组
    Android工程方法数超过64k,The number of method references in a .dex file cannot exceed 64K.
    Eclipse项目导入到Android Studio中
  • 原文地址:https://www.cnblogs.com/coolqiyu/p/7138220.html
Copyright © 2020-2023  润新知