你真的了解 词法作用域,全局作用域,函数作用域以及块级作用域吗

1,510 阅读5分钟

前言

  • 网上关于各种作用域的描述说法云云,于是我翻阅了一些书籍,加上自己的理解,整理总结了一篇关于作用域的文章,详细介绍了JS中的各种作用域。

1. 什么是作用域

  • 作用域可以视为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域根据标识符名称进行变量查找。

  • 简单来说作用域就是变量的有效范围。在一定的空间里可以对变量数据进行读写操作,这个空间就是变量的作用域。。

  • 作用域共有两种主要的工作模型,js等大多数都采用的是词法作用域,另一种动态作用域(比较少的语言在用)。

2. 全局作用域

  • 直接写在script标签的JS代码,都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量)。
  • 全局变量在全局的任何位置下都可以使用;全局作用域中无法访问到局部作用域的中的变量。
  • 全局作用域在页面打开的时候创建,在页面关闭时销毁。
let a = 10 
function fn () {
  console.log(a)
 }
fn() //  10 
  • 所有 window 对象的属性拥有全局作用域

    var和function命令声明的全局变量和函数是window对象的属性和方法

    let命令、const命令、class命令声明的全局变量,不属于window对象的属性

 let a = 10
 var b = 10
 function fn () {}
 console.log(window.a) //undefined
 console.log(window.b) //10
 console.log(window.fn) //fn()

3. 函数作用域(局部作用域)

  • 调用函数时会创建函数作用域,函数执行完毕以后,作用域销毁。每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的。
  • 在函数作用域中可以访问全局变量,在函数的外面无法访问函数内的变量。
  • 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有就向上一作用域中寻找,直到找到全局作用域,如果全局作用域中仍然没有找到,则会报错。
      var a = 10
      function fn () {
        var b = 20
        function fn2 () {
          console.log(a + b)
        }
        fn2()
      }
      fn() //30
      console.log(b) //b is not defined
  • 在ES6之前,只有函数可以划分变量的作用域,这带来很多不合理的场景。
      if (true) {
        var a = 10
      }
      console.log(a) //10     
      for (var i = 1; i < 5; i++) {}
      console.log(i) //5      
      var b = 10
      function fn () {
        console.log(b)
        if (false) {
          var b = 20
        }
      }
      fn() //undefined
  • 虽然try/catch的catch分句会划分一个作用域,但这个作用域只对error有效
      try {
        throw undefined
      } catch (error) {
        console.log(error)
        var a = 10
      }
      console.log(a) //10
      console.log(error) // error is not defined

4. 块级作用域

  • ES6之前JavaScript采用的是函数作用域+词法作用域,ES6引入了块级作用域。
  • 任何一对花括号{}中的语句集都属于一个块,在块中使用let和const声明的变量,外部是访问不到的,这种作用域的规则就叫块级作用域。
  • 通过var声明的变量或者非严格模式下创建的函数声明没有块级作用域。
      function fn () {
        let a = 1
        {
          var b = 1 // 未用let或const,不形成b的块作用域,依然是fn的函数作用域
          let c = 1 // c形成块作用域,只可在当前代码块中访问
        }
        console.log(a) //1
        console.log(b) //1
        console.log(c) // c is not defined
      }
      fn()
      //  严格模式
      'use strict'
      if (true) {
        function fn () {
          console.log(1)
        }
      }
      fn() // fn is not defined
  • {}语法与立即执行函数的作用类似
      // 立即执行函数 写法
      ;(function () {
        var a = 10
        console.log(a + 10)
      })()

      // 块级作用域写法
      {
        let a = 10
        console.log(a + 10)
      }

5. 词法作用域

  • 词法作用域是静态的作用域,无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
  • 编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过中如何对它们进行查找。
  • 换句话说,词法作用域就是你在写代码的时候就已经决定了变量的作用域。

5.2 与之相对应的就是动态作用域

  • 动态作用域是在运行时确定的。
  • 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
      function foo () {
        console.log(a, b) // a:2
      }
      function bar () {
        let a = 3
        let b = 2
        foo()
      }
      let a = 2
      bar() // b is not defined
  • 动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。因此,如果 JavaScript 具有动态作用域,理论上,上面代码中的 foo() 在执行时将会输出 3。
  • 词法作用域让 foo() 中的 a 引用到了全局作用域中的 a,但是不会引用bar()函数里面的b变量。
  • this 机制某种程度上很像动态作用域。

5.3 词法作用域与块级作用域的区别

  • 词法作用域与块级作用域,他们从两个角度描述了作用域的规则。
  • 简单来说,块级作用域定义了什么语法可以划分变量的作用域。词法作用域定义了变量的查找规则。

写在最后

  • 码字不易,如果这篇文章给你带来了帮助,希望能给个赞。有说的不对的地方,欢迎指正。