zoukankan      html  css  js  c++  java
  • 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!
  • 相关阅读:
    C++笔记(2018/2/6)
    2017级面向对象程序设计寒假作业1
    谁是你的潜在朋友
    A1095 Cars on Campus (30)(30 分)
    A1083 List Grades (25)(25 分)
    A1075 PAT Judge (25)(25 分)
    A1012 The Best Rank (25)(25 分)
    1009 说反话 (20)(20 分)
    A1055 The World's Richest(25 分)
    A1025 PAT Ranking (25)(25 分)
  • 原文地址:https://www.cnblogs.com/blogCblog/p/14448756.html
Copyright © 2011-2022 走看看