我不知道的js作用域与闭包

Posted 小字辈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我不知道的js作用域与闭包相关的知识,希望对你有一定的参考价值。

作用域与闭包


作用域

  • 什么是作用域
    作用域就是一套规则,它负责解决(1)将变量存在哪儿?(2)如何找到变量?的问题

  • 作用域工作的前提
    • 谁赋予了作用域的权利?——js引擎
    • 传统编译语言编译的过程
      • 分词/词法分析:字符串 =》 词法单元
      var a=2; => var a = 2 ;(共5个单元)
      • 解析/语法分析:词法单元流 =》 抽象语法树(Abstract syntax tree,AST)
      • 代码的生成: AST =》 可执行代码(机器指令)
    • js引擎编译的特点:
      • 代码在执行前进行编译(需要用到JIT(just in time)进行延迟编译甚至实施重编译来保证性能最佳)
      • 针对 ** var a = 2; **例子的编译流程
        • 分词/词法分析:将var a=2; => var a = 2 ; 分成5个词法单元
        • 解析/语法分析:解析成抽象语法树
        • 代码生成:** 重点来了 **
          • var a (声明操作)=》此时编译器会 询问作用域是否有叫 a 的变量存在 ?忽略该声明,继续编译 : 在当前作用域下声明一个新变量,命名为a
          • a=2 (赋值操作) =》此时编译器会 询问作用域是否有叫 a 的变量存在 ?使用该变量 : 引擎根据作用域链继续向上查找该变量。
            是否能找到叫 a 的变量 ?直接赋值 : 引擎抛出异常。
        • 引擎在查找变量a是否被声明的过程中,是如何进行的查找?
          • 作用域的协助
          • 查找的目的是赋值=》LHS(left hand side)查询 :(1) 当变量出现在赋值操作的左侧时,使用 LHS 查询。类似赋值 a=2; (2)若在顶层作用域内没有找到,严格模式下:Reference异常;非严格模式下:自动隐式创建全局变量
          • 查找的目的是取值=》RHS(right hand side)查询 :(1) 当变量出现在赋值操作的右侧时,使用 RHS 查询。类似取值 a; (2) 若在顶层作用域内没有找到,直接抛出Reference异常
  • 作用域是如何工作的?词法作用域,动态作用域。
    • 词法作用域
      • 什么是词法作用域
        • 词法作用域就是定义在词法分析阶段的作用域。词法作用域就是在写代码时将变量和块作用域写在哪里决定的。一般是不会变的。
      • 词法作用域的查找规则(作用域链
        • 作用域查找从运行时所处的最内部的作用域开始,逐级向上或向外查找,直到遇见第一个匹配的标识符(变量、函数)为止。
      • 改变词法作用域(二般情况出现:欺骗词法
        • 2种方法
          • eval();接受一个字符串为参数,即动态插入程序代码,伪装成词法期就存在的代码。
          function foo(str, a){
              eval(str);
              console.log(a, b);
          }
          var b=2;
          foo(‘var b=3;‘,1); //1,3
          • with;通常被当做重复引用同一个对象的多个属性的快捷方式,可以不需要重复引用对象本身。
          var obj = {
              a:1,
              b:2,
              c:3
          };
          //使用with
          with(obj){
              a=3;
              b=4;
              c=5;
          }
        • 后果
          • 引擎无法在编译时对作用域对的查找进行优化
          • 在严格模式下,with被完全禁止,eval(...)也被禁止
  • 常见的作用域单元

    • 函数作用域
      • 什么是函数作用域
        • 属于这个函数的全部变量都可以在整个函数的范围内使用及复用
      • 函数的好处:隐藏内部实现,规避同名标志符之间的冲突(有以下两个方法)
        • 声明全局命名空间(一个对象,用来存储全局作用域种的变量)
      • 函数相关常识
        • 函数声明 vs 函数表达式
        function是声明中的第一个词 ? 函数声明 : 函数表达式。
        • 具名 vs 匿名 :
        有无名字的区别
        函数表达式可以没有名字,但是函数声明必须有名字
        鼓励所有的函数都有名字
        • 立即执行函数表达式
        两种写法: 
        (function(){}());
        (function(){})();//常用,第一个括号( )将函数变成表达式,第二个( )执行这个函数
    • 块作用域(es5之前并没有该概念)
      • 什么是块作用域
      变量的声明离使用的地方越近越好,并最大程度的本地化。
      • 块作用域的例子
        • with关键字 : with从对象中创建出的作用域仅在with声明中而非外部作用域有效
        • try/catch : catch分句创建作用域,且声明的变量只能在catch中使用
        • let : let为其声明的变量提供块作用域{...},且let进行的声明在块中不会变量提升
        • const : const声明常量。
  • 提升
    • 声明提升
      • 变量声明提升
      • 函数声明提升
      • 函数表达式声明不会被提升
      foo();//Uncaught TypeError: foo is not a function//foo()对于undefined值进行函数调用而导致非法操作
      var foo= function bar(){
          console.log(‘1‘);
      };
      foo();//1
      foo;//function bar(){}
      bar;//ReferenceError: bar is not defined
      bar();//ReferenceError: bar is not defined
    • 提升的优先级
      • 函数优先,其次才是变量
      • 避免在块内声明函数

  • 练习题(1)找出所有的 LHS 查询和 RHS 查询
function foo(a) {
    var b=a;
     return a+b;
}
var c= foo(2);

答案:
LHS(3处) c=..., a=2(隐式变量分配),b=...,
RHS(4处) foo(2..., =a, a.., ..b


作用域闭包(晦涩难懂,常常搞错的地方)

  • 什么是闭包?
    • 函数可以记住并访问所在的词法作用域时,就产生了闭包。即使是在当前词法作用域之外的地方执行。
    闭包     = 那些能够访问自由变量的函数 = 函数 + 函数能访问的自由变量
    自由变量  = 在函数中使用,但既不是函数参数也不是函数的局部变量
  • 闭包作用
    • 可以读取函数内部的变量
    • 让这些变量的值始终保持在内存中。
  • 闭包例子
    • 回调函数(定时器,ajax跨域,异步等)
    • for循环
    for(var i=1;i<=5;i++){
        setTimeout( function timer(){
            console.log(i);
        },i*1000);
    }  //以每秒一次的频率输出5个6
    
    for(var i=1;i<=5;i++){
        (function(j){
            setTimeout( function timer(){
                console.log(j);
            },j*1000);
        })(i);
    } //立即执行函数为每一次迭代生成一个新的作用域,以每秒一次依次输出1-5
    
    for(let i=1;i<=5;i++){
        setTimeout(function timer(){
            console.log(i)
        },i*1000);
    }

动态作用域

  • 什么是动态作用域
    动态作用域是在函数运行时确定的,类似this。
    词法作用域关注在何处声明,动态作用域关注在何处调用。
    ***

以上是关于我不知道的js作用域与闭包的主要内容,如果未能解决你的问题,请参考以下文章

你不知道的JavaScript1(作用域与闭包)

js面试题-----作用域与闭包

JS基础 作用域与闭包

js系列-3 js作用域与闭包

js-高级(原型与原型链作用域与作用域链闭包)

YDKJS:作用域与闭包