js的作用域,声明提升和let,const与var的区别

159 阅读5分钟

在讲作用域之前,我们先来聊聊js的编译和执行

编译

  1. 找到某个域当中的有效标识符。注意函数在没有调用的时候是不执行的,即并不是和全局一起编译,而是先不编译,等要执行的时候再编译(编译永远只发生在执行的前一步).虽说函数在全局编译的时候是不编译的,但是他有声明在.

执行

  1. 变量的查找会先从内到外的作用域中查找(即先查找自己的作用域,找不到再去外层查找),不能从外到内

作用域

  1. js是弱类型动态语言
    弱类型动态语言即创建变量时不需要声明数据类型,所以一个变量可以被赋值为其他任意一种类型
  2. 执行代码之前是需要先编译的

全局作用域

  • 全局作用域即定义在当前文件的内容所在的一个场所,通俗来讲,就是你打开一个文件,写了这样几行代码,其中 a 和 fd()函数所在的场所就是全局,a 也叫全局变量

      var a = 1
    
      function fd(){
          console.log(a);
      }
      fd()
    

函数体作用域

  • 因为函数在没有调用的时候是不执行的,即并不是和全局一起编译,所以等我们第一次编译完开始执行的时候,执行到函数时,编译器会先停在这,对这个函数进行再次编译,等这个函数编译完,再继续执行。对这个函数编译的时候,该函数会产生一个自己的作用域,这就叫做函数体作用域

    举个例子

      var a = 1
      function fd(){
          console.log(a);
      }
      fd()
    

    如上述代码,画个图更好理解全局作用域和函数体作用域

1.1.png 我们来判断一下上述代码能否执行,如果能执行那么输出结果是什么

答案是上述代码能执行,并且输出结果为1。我们来梳理一下思路,当第一遍代码编译的时候,全局作用域声明了一个变量 a 和函数 fd(),函数fd()里面的内容并没有进行编译,之后代码开始执行,执行到第一行代码,给变量 a 赋值为1,再往后执行到读取函数fd()这个代码,此时对这个函数进行第二次编译,这时这个函数产生了函数体作用域,因为这个函数里面没有标识符,所以第二次编译结束,继续进行执行,此时执行到console.log(a)这行代码,按理说我们要在这行代码本身的作用域里找 a 这个标识符,但是因为函数体作用域没有这个标识符,所以我们往外找,这时全局变量里面有这个标识符,并且有值且值为1,所以最终输出结果为1

再来个例子

      var a = 1
      function fd(){
          var a = 3
          function bar(){
              console.log(a);
          }
      }
      fd()
      bar()

判断一下上述代码能否执行,如果能执行那么输出结果是什么

答案是上述代码不能执行,会报错,报的错为 bar 未被定义。因为 bar()函数是在 fd() 的函数体作用域内的,而bar()这行代码是在全局作用域中执行的,又因为执行不能从外到内,所以上述代码执行不了

声明提升

在说块级作用域之前我们先来聊聊声明提升以及 let , const 和 var 的区别

  1. var 声明的变量存在声明提升,提升到当前作用域的顶端

    如:

     //我们写的:
     console.log(a);
     var a = 1
     
     //事实上编译的:
     var a
     console.log(a);
     a = 1
     
     //输出:undefined
    
  2. 函数声明会整体提升

    如:

     //我们写的:
     function fd(){
         console.log('123')
     }
     fd()
     
     //事实上编译的:
     fd()
     function fd(){
         console.log('123')
     }
    

let

  1. 不会声明提升

    如:

     console.log(a)
     let a = 1
     //输出:报错
    
  2. 不能重复声明同一变量

    如:

     let a = 1
     let a = 2
     //输出:报错
    

const

  1. 不会声明提升

  2. 不能重复声明同一变量

  3. 不能改值,所有一般用来声明常量

    如:

     const a = 1
     a = 'hello'
     //输出:报错
    

块级作用域

  1. {}和let会将整个{}变成块级作用域,{}和const也会形成作用域

    如:

     if(1){
         var a = 1
     }
     console.log(a);
     //输出:1
    
     if(1){
         let a = 1
     }
     console.log(a);
     //输出:报错
    
     let a = 1
     if(true){
         console.log(a);     //暂时性死区
         let a = 2
     }
     //输出:报错
    

欺骗词法作用域

  1. eval()让原本不属于这里的代码变成就是写在这里的

    如:

     function fd(str){
         eval(str)   //相当于var b = 2
         var a = 1
         console.log(a,b);
     }
     fd('var b = 2')
     //输出:1,2
    
  2. with()可以批量化修改对象的属性 如:

     var obj = {
         a: 1,
         b: 2,
         c: 3,
     }
    
     //我们要把obj里面所有元素都加1
     //直接的写法
     obj.a = 2
     obj.b = 3
     obj.c = 4
    
     //简洁的写法
     with(obj) {
         a = 2,
         b = 3,
         c = 4
     }
    

    但当修改对象中有不存在的属性时,该属性会泄露到全局,变成全局变量

    如:

    function fd(obj) {
    with(obj){
    a = 1
    }\
    }
    var o1 = {
    b: 3
    }
    fd(o1)
    console.log(o1);
    

    输出为{b: 3},但当我们去打印 a 的时候,你会发现竟然能打印出来值,并且值为1,因为这个 a 已经变成了全局变量

最后聊一个热知识,当你声明变量不加关键字时,不管你写在哪都是将他当做全局变量