zoukankan      html  css  js  c++  java
  • 读书笔记-you-don't-konw-js

    第一部分:作用域和闭包

    不要满足于只是让代码正常工作,而是弄清楚为什么是这样

    作用域是什么


    定义的变量存储在哪里?程序是如何找到变量的?实现的 规则就是作用域

    传统编译语言执行前的编译三步骤(p5)

    1. 分词/ 词法分析(Tokenizing/ Lexing)
    2. 解析/ 语法分析 (Parsing)
    3. 代码生成

    理解作用域

    1. 引擎--负责编译与执行整个过程
    2. 编译器 -- 语法分析与代码生成等
    3. 作用域 -- 收集并维护声明的变量查询,确定标识符的访问权限

    LHS和RHS
    含义是赋值操作的左侧或者右侧,并不一定是“=赋值操作符的左侧或者右侧”,因此在概念上可以理解为“赋值的对象是谁(LHS)”和“谁是赋值操作的源头(取源RHS)”

    // 练习
    // 请问引擎和作用域通话,分别进行了多少次LHS和RHS查询
    // 请指出
    
    function foo (a) {
        var b = a;
        return a + b;
    }
    
    var c = foo(2);
    
    3处LHS查询是
        me:
        1:foo(..)赋值给c;
        2:隐式将2 赋值给 a;
        3:a赋值给b;
        
        参考:
        c = ..、 a = 2(隐式变量分配)、b = ..
        
    4处RHS查询是
        me:
        1:引擎通过作用域知道c是一个foo
        2:引擎通过作用域知道foo是个函数
        3:?
        4:return
        
        参考:
        foo(2..、 = a、 a..、 ..b(return 处的a 与 b 分别进行了一次RHS)
    

    作用域嵌套
    一个块或者函数嵌套在另一个块或者函数中就发生了作用域的嵌套。
    嵌套作用域中的查询规则:“就近原则”,当前作用域中没有找到目标,则沿着原型链往外一层一层找,直到全局作用域。在全局作用域中未找到,查找停止,抛出异常。

    区分LHS和RHS的重要性:变量未声明时,两种查询的行为不一样。

    function foo (a) {
        console.log(a + b);
        b = a;
    }
    

    第一次在对b进行RHS查询时找不到该变量,引擎抛出ReferenceError错误;相较之下LHS查询在到不到时, “非严格模式”下会隐式创建全局变量,比如 b = a

    • ReferenceError 同作用域判别失败相关, 而 TypeError 则代表作用域判别成功了, 但是对结果的操作是非法或不合理的

    词法作用域


    函数作用域和块作用域


    函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
    我们都知道JS具有基于函数的作用域,(ES6出现了块级作用域)在函数内声明的变量和嵌套的函数都可以在函数内调用,外部不允许访问(符合最小特权原则)。这样设计的好处是能充分利用JavaScript 变量可以根据需要改变值类型的“ 动态” 特性。与此同时,还要避免隐式全局变量的声明。

    规避冲突
    全局命名空间
    模块管理 (p26)

    函数声明和函数表达式
    两者之前最重要的区别是他们的名称标识符将会绑定在何处。
    补充: 名称标识符

    立即执行函数表达式IIFE( Immediately Invoked Function Expression)p28

    块作用域

    1. with语句会创建
    2. try/catch 语句中catch分句会创建
    3. let关键字
    4. const关键字

    提升是指声明会被视为存在于其所出现的作用域的整个范围内。
    let声明的变量不会提升

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

    显示声明块级作用域

    // 使用{ } 显示声明let
    {
        let a = 0;
        console.log(a);
    }
    

    const定义个一个块常量,定义之后不允许改变。

    块作用域与垃圾回收

    function process(data) {
        // 在这里做点有趣的事情
    }
    // 1.内存可能没有释放
    var someReallyBigData = { .. };
    process( someReallyBigData );
    
    // 2.在这个块中定义的内容可以销毁了!
    //{
    //    let someReallyBigData = { .. };
    //    process( someReallyBigData );
    // }
    
    var btn = document.getElementById( "my_button" );
    btn.addEventListener( "click", function click(evt) {
        console.log("button clicked");
    }, /*capturingPhase=*/false );
    
    由于 click 函数形成了一个覆盖整个作用域的闭包, JavaScript 引擎极有可能依然保存着someReallyBigData ( 取决于具体实现)。但是2. 声明了块作用域,引擎明确知道click函数不需要someReallyBigData ,使someReallyBigData 得内存占用得到释放。
    
    为变量显式声明块作用域, 并对变量进行本地绑定是非常有用的工具
    

    提升


    概念:代码在执行前,声明(函数和变量)会都会在其作用域的顶端,可看做声明的变量或者函数移动到了作用域的最顶端。

    // 举例:
    {
        console.log(a) // undefined
        var a = 1
    } 
    

    引擎在执行前词法分析一步将上面代码看做 var a 声明和 a = 1 赋值两步,声明var a 将会被提升,因此在执行性console.log(a) 时, 引擎已经知道a,由于赋值保留在代码赋值的位置,所以a为undefined。如果a没有提升,浏览器将会报ReferenceError 错误。

    注意:

    1. 函数表达式不会提升。

    2. 函数优先

      foo() // foo
      var foo;
      function foo () {
      console.log('foo')
      }

    尽管 var foo定义在function foo ... 之前, 但由于function foo .. 函数声明优于变量声明, var foo 重复声明被忽略,因此 foo() 能够正确执行。如果var foo 变量在function foo 之前声明, 那么函数foo将作为重复声明而被忽略, 导致foo()执行包ReferenceError错误。

    作用域闭包


  • 相关阅读:
    [原理][源代码解析]spring中@Transactional,Propagation.SUPPORTS,以及 Hibernate Session,以及jdbc Connection关系---转载
    凡事预则立,不预则废
    Linux下history命令详解---转载
    12 个最佳的免费网络监控工具--转载
    Linux curl命令参数详解--转载
    从源码角度深入分析ant
    SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
    使用split进行分割时遇到特殊字符的问题
    ES查看segment大小
    Twitter的流处理器系统Heron——升级的storm,可以利用mesos来进行资源调度
  • 原文地址:https://www.cnblogs.com/wbengineer/p/6127817.html
Copyright © 2011-2022 走看看