《你不知道的JS》第一部分:作用域和闭包

56 阅读4分钟

一、作用域是什么

作用域即变量的作用范围,作用域链为查找变量提供方向!

var a = 2

上述这段代码发生了啥?

  1. 遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的 集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。

  2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值 操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的 变量。如果是,引擎就会使用这个变量;如果否,引擎会沿着作用域链继续查找该变量

  3. 总结:这个过程是分开的,可以理解为声明 + 赋值。而查询变量a的过程,即LHS查询,注意与RHS查询相比较。下方的var b = a中,引擎查找b是LHS查询,查找a是RHS查询,这里的并非是根据在等号的左(L)、右(R)来区分的。对于一个变量,你如果想获取其地址就是LHS,想获取其值就是RHS。

    var a;
    a = 123;
    var b = a;
    
  4. 关于RHS、LHS的异常处理,代码a = b

    • 对于变量a是LHS查询,如果没有该变量,则报错ReferenceError异常;
    • 对于b是RHS查询,如果没有该变量,在非严格模式中,会自动创建全局变量b,并赋值为undefined;在严格模式中,则报错ReferenceError异常。
  5. 有机会一定要补上《编译原理》这一课!

二、词法作用域

即静态作用域:词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。

遮蔽效应:作用域查找会在找到第一个匹配的标识符时停止。

欺骗词法:借此来在运行时修改词法作用域,

  • 但是出于性能损耗,并不推荐这种奇淫巧计。
  • 此外,其功能往往会受“严格模式”的限制
  1. 非严格模式下的eval()函数。在严格模式的程序中,eval(..) 在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域
  function change(str) {
    'use strict'
     var b = 3;
     eval(str);
     console.log(b); // 严格模式下是3;非严格模式下是666。
   }
   change('var b = 666')
  1. with关键字 使用场景:
    function foo(obj) {
         with (obj) { 
            a = 2; 
         } 
      }
      var o1 = { a: 3 };
      var o2 = { b: 3 };
      foo( o1 ); 
      console.log( o1.a ); // 2 
      foo( o2 ); 
      console.log( o2.a ); // undefined   o2中没有a,也不会自动创建a
      console.log( a );  // 2  在非严格模式下,自动创建了全局变量

函数作用域和块作用域

1.函数的本来作用:隐藏内部实现。既可以理解为“声明函数,然后向里面添加代码”;也可以理解为“从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,即隐藏”

函数的优点:

  • 规避命名冲突
  • 符合最小暴露原则

2.函数作用域 即使函数隐藏了内部实现,但是它对外暴露了函数名且需要调用该函数才能访问内部的东西。解决方案————“函数表达式”。

区分“函数声明”和“函数表达式”这两个概念:

  • 实质区别:看函数声明的名称会被绑定在何处。
  • 表象:是看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
// 函数声明:该foo被绑定在整个作用域中,都可以调用foo1;且funciton是第一个词
function foo1() {}

// 函数表达式:该foo2仅在该括号内生效;且是以(先开头的
(function foo2() {})()

3.“函数表达式”的两种用处

  • 回调函数:注意回调函数的匿名、具名都是可以的,只是平时用惯了匿名,但是具名更好一点
  • 立即执行函数表达式
  • 注意书中那个应用:使用立即执行函数解决 undefined 标识符的默认值被错误覆盖导致的异常(虽然不常见)
    • 首先现阶段的ES已经不允许对关键字进行赋值了,极大地避免了这种问题;
    • 在大括号中还是会出现这种情况的,立即执行函数借用了形参未传入实参的话,其值是undefined来保证undefined不会被污染
    {
         let undefined = 123456, a = 123456
         console.log(a === undefined); // true
         (function IIFE( undefined ) {
         var a = 123456;
         console.log(a === undefined); // false
         if (a === undefined) { console.log( "Undefined is safe here!" ); } }
         )();
          console.log(undefined);
      }
  1. 块作用域