• javascript中的作用域


    前言

      本篇是基于对 《你不知道的JavaScript(上卷)》中的第一、二、三、四章的总结理解。

    编译原理

      在代码执行之前进行的操作叫“编译”,一般有三个步骤:

    1. 分词/词法分析:这个操作是将有意义的代码生成词法单元;
    2. 解析/语法分析:将词法单元生成为AST(抽象语法树);
    3. 代码生成:将AST转换为可执行代码。

      但对于JavaScript来说,编译过程更加复杂一些,会对运行性能进行优化,以及对冗余的元素进行优化,因为 js 是动态语言,编译操作在代码运行的几微秒前,编译后马上执行。

    执行原理(宏观)

      JS 执行代码大概涉及到三个东西,分别是,编译器(进行编译)、引擎(执行)、作用域(维护变量作用域)。

    • 例1:

        假设有 var a = 1 这段代码,编译器、引擎、作用域之间的协同工作是:

          首先编译器进行编译,编译器会检查当前作用域下是否存在相同的变量a,如果存在就忽略(注意,let、const声明会报错),不存在就会在当前作用域中添加一个新的变量,命名为a。最后生成引擎运行时需要的代码。

          最后引擎执行时会首先检查变量a是否存在在当前作用域中(不存在会向外层作用域查找),如果直到全局作用域都没有变量a,则会抛出一个异常,如果存在,就处理 a = 2 这个赋值操作。

    • 例2:

        假设有var a = 1; var b = a; :

          这里编译过程跟例1相同,需要注意 var b = a,这里引擎会先在作用域中查找a的值是什么(没找到就报错),查找b是否存在在作用域中(没找到报错),最后进行赋值。

      

      在编译器或引擎查找某个变量是否存在作用域中叫“LHS”查找;而查找某个变量的值是什么叫 “RHS” 查找。

      在当前作用域进行查找时,如果没有找到某个变量,则会向外层作用域进行查找。

    作用域

      js中,作用域一般分为:全局作用域和函数作用域。没有块作用域的概念。如下:

    if(1) {
            var a = 1
    }
    
    console.log(a)  // 1

      a 仍然输出为1。在其他语言中可能会报错,因为 a 在 if 的块作用域中。而在js中仍然是全局作用域。

    var a = 2
    
    function fun(){
            var a = 1
    
            function fun2() {
                console.log(a) // 1
            }
    
            fun2()
    
    }
    
    fun()

      在函数(fun)作用域内声明了 a ,查找过程中没有在fun2当前作用域内找到,所以到上层作用域(fun)内查找,有 a,所以打印出 1。此时没有往全局作用域内查找。  

    • JS使用块作用域

        在ES6出现之前,想要使用块作用域,只能使用with 或者 try/catch。

    try{
            throw 2
        } catch(a){
            console.log(a) // 2
        }
    
    console.log(a) // 报错

        说明 catch 中是有块作用域的。

        也可以使用let、const将变量绑定在当前块作用域中,但需要支持ES6。

      

    • 欺骗作用域

    var a = 1
    
    function fun(e) {
      console.log(a)  // 2
    }
    
    fun(eval("var a = 2"))

      上面代码打印出的是 2 ,并不是1,是因为使用了eval,就好像在当前作用域中声明了一个跟全局作用域中 a 相同的变量,按照查找规则,就在当前作用域中找到了 a,所以打印出 2,上面代码相当于:

    var a = 1
    
    function fun() {
       var a = 2
      console.log(a)
    }
    
    fun()

      当然,这在js非严格模式下有用。

       

    • IIFE:立即执行函数表达式

      普通的定义一个函数,它的函数名本身就“污染”了作用域。正如一些类库,都会使用 IIFE 来避免“污染”。

    (function IIFE(){
            
    })()

      函数名字不是必须的,可以是匿名函数。

      函数定义外层有一对括号,加了括号就变成了表达式。

      括号将函数的作用域绑定在了表达式自身,从而不会“污染”外层作用域。

      IIFE 还可以传参,在函数执行中传入参数。

    (function IIFE(a){
        console.log(a) // 1
    })(1)
    • 作用域中的声明提升

      代码在执行前会对声明在 当前作用域内 进行提升:

    // 提升前代码
        var a = 1
        console.log(a)
    
        // 提升后代码
        var a
        a = 1
        console.log(a)
    
        /////////////////////////////////////
    
    // 提升前代码 a () function a () { console.log(1) } // 提升后代码 function a () { console.log(1) } a ()

      因为声明经过提升,所以函数a定义在函数执行后才能正确执行。

    // 提升前
    fun()
    function fun () {
            console.log(a) // undefined
            var a = 1
    }
    
    // 提升后
    function fun () {
            var a
            console.log(a) // undefined
            a = 1
    }
    fun()

      上面代码,虽然变量a经过提升,但是在打印前并没有经过赋值,所以打印出 undefined。

    // 提升前
    fun() // 报错
    var fun = function () {}
    
    // 提升后
    var fun
    fun()
    fun = function () {}

      上面代码执行报错,因为var fun = function 是表达式,所以只会提升var fun声明。

      另,变量与函数同时提升,那么会先提升函数:

    // 提升前
    var a = 1
    function fun(){}
    
    // 提升后
    function fun(){}
    var a
    a = 1

      

    Welcome to my blog!
  • 相关阅读:
    学生管理系统
    Selenium元素定位的30种方式
    python-- 多进程
    python 多线程的实现
    python 节省内存的for循环技巧
    python 生成器
    python 字符串编码检测
    opencv-python 图片的几何变换
    opencv-python --图像处理
    目标检测
  • 原文地址:https://www.cnblogs.com/blogCblog/p/14448756.html
Copyright © 2020-2023  润新知