• 我不知道的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。
      词法作用域关注在何处声明,动态作用域关注在何处调用。

  • 相关阅读:
    [原]使用ASP.NET MVC构建RESTful服务
    [原]ASP.NET MVC 3 使用 DotNetOpenAuth 实现SSO
    ASP.NET MVC + RESTful服务之HttpStatusResult
    [原]ASP.NET MVC 3 Razor 多国语言参考解决方案 补充三
    [原]ASP.NET MVC 3 使用jqGrid之TreeGrid
    [原]ASP.NET MVC 3 Razor 多国语言参考解决方案 补充
    vsftp
    对付CC攻击不必动用防火墙
    Symfony and Godaddy
    godaddy的VPS使用SSH登录的方法
  • 原文地址:https://www.cnblogs.com/hiluna/p/9736376.html
Copyright © 2020-2023  润新知