IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    再读《你不知道的 JavaScript》 · 看不见我的美 · 是你瞎了眼

    馬腊咯稽发表于 2020-02-05 00:00:00
    love 0
    再读《你不知道的 JavaScript》

    词法作用域

    传统编译语言的源代码在执行前会经历三个步骤,统称为编译:

    • 词法分析,将源代码分解为词法单元;
    • 语法分析,将词法单元流转换为抽象语法树(AST);
    • 代码生成,将 AST 转换为可执行代码(一组机器指令)。

    JS 是一门解释型语言,在执行前也需要进行编译,不过它的编译不是发生在构建前而是在运行时。

    JS 对变量有两种查询方法:LHS 和 RHS;LHS 用来找到变量本身,RHS 用来查找变量的值。作用域就是查询变量的规则,如果查找的目的是对变量赋值则进行 LHS 查询;如果查找的目的是获取变量的值则进行 RHS 查询。

    作用域有两种工作模型:动态作用域和词法作用域,JS 采用词法作用域。词法作用域就是定义在词法阶段的作用域,是由变量和块作用域的位置来决定的。动态作用域基于调用栈,是在运行时确定的,this 就像是动态作用域的表亲。

    欺骗词法作用域:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    function foo(str) {
     eval(str); // 欺骗,修改词法作用域并对全局变量 a 进行遮蔽
     console.log(a);
    }
    var a = 1;
    foo('var a = 2'); // 2
    
    function bar(str) {
     'use strict';
     eval(str); // 严格模式下,eval 有自己的作用域,无法欺骗
     console.log(b);
    }
    var b = 1;
    bar('var b = 2'); // 1
    function we(o) {
     with (o) {
     c = 2;
     }
    }
    var o1 = {
     c: 1
    };
    var o2 = {
     d: 1
    };
    we(o1);
    we(o2);
    console.log(o1.c); // 2
    console.log(o2.c); // undefined
    console.log(c); // 2,c 被泄露到全局作用域
    

    闭包

    当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    function foo() {
     var a = 2;
     function bar() {
     // 拥有涵盖 foo 内部作用域的闭包,bar 可以在之后任何时间位置引用
     console.log(a);
     }
     return bar;
    }
    var fn = foo();
    fn(); // bar 拥有对 foo 内部作用域的引用,而这个引用就叫作闭包
    

    运算符优先级

    this

    this 的行为很像动态作用域,是在运行时进行绑定的。当一个函数被调用时,会创建一条活动记录(执行上下文),这条记录会包含函数在哪里被调用(调用栈)、调用方式、传入参数等信息,this 就是这条记录的一个属性。

    this 的绑定规则:默认绑定、隐式绑定、显示绑定、new 绑定。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    // 默认绑定在 window 上
    function foo() {
     console.log(this); // window
    }
    foo();
    // 在严格模式下,默认为 undefined
    function bar() {
     'use strict';
     console.log(this); // undefined
    }
    bar();
    // 隐式绑定
    var o = {
     a: 1,
     foo
    };
    // 调用时,对象 o 拥有 foo
    o.foo(); // o
    // 隐形丢失,这里 o.foo 只是 foo 的引用
    setTimeout(o.foo, 0); // window
    // 显示绑定
    var g = {
     list: [1, 2, 3]
    };
    g.list.forEach(function () {
     console.log(this.list);
    }, g);
    // new 绑定
    function V(name) {
     this.name = name;
    }
    var v = new V('case');
    console.log(v.name); // case
    

    在箭头函数中,放弃了 this 的绑定规则,用当前的词法作用域取代了 this 机制。

    == 与 ===

    常见的误区是:“== 检查值是否相等,=== 检查值和类型是否相等”;正确的解释是:“== 允许在相等比较中进行强制类型转换,而 === 不允许”。实际上 == 的工作量要大一些。

    1
    2
    3
    
    // == 最容易出错的一个地方是 true 和 false 与其他类型之间的相等比较,要避免布尔值的宽松相等
    console.log('42' == true); // false
    console.log('42' == false); // false
    

    子类构造器

    对于类,构造器不是必须的,如果省略,二者都会提供默认的构造器;默认子类构造器自动调用父类构造器并传递所有参数。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    class Child extends Parent {
     constructor(...args) {
     // super 指向 Parent 的构造函数
     super(...args);
     }
     hello() {
     // super 指向 Parent.prototype
     super.hello();
     }
    }
    


沪ICP备19023445号-2号
友情链接