Javascript的作用域

125 阅读2分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

什么是作用域

作用域是指的是变量或者成员的可见性。而这个可见性的区间(即 “域”),被称为作用域(scope)。这句话不太好理解,其实作用域就是执行环境

红宝书上的解释:执行环境(execution content,为简单起见,有时也称为“环境”),是 JavaScript 中最为重要的概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理时会在后台使用它。

在ES6之前Javascript只有 全局作用域函数作用域,ES6中新增了 块级作用域

全局作用域与函数作用域

  • 全局作用域就是在代码中的任何地方都能被访问到的作用域,在最外层。在Web浏览器中,全局作用域就是 window 对象,因此所有的全局变量和函数都是作为 window 对象的属性和方法创建的。

  • 每个函数都有自己的作用域,函数作用域是指在函数内部声明的变量和函数,只能在函数内部访问。

var color = 'blue'

function changeColor() {
  var anotherColor = 'red'

  function swapColors() {
    var tmpColor = anotherColor
    anotherColor = color
    color = tmpColor
    // 这里可以访问 color, anotherColor, tmpColor
  }
  // 这里可以访问 color, anotherColor,不能访问 tmpColor
  swapColors()
}
// 这里只能访问 color
changeColor()

console.log('color is now ' + color) // 'color is now red'

根据上述例子,可以确定作用域是分层的,内部作用域可以访问外层作用域的变量和方法,反之则不行。

块级作用域

由于ES6之前JS不存在块级作用域,比如下面这个例子,在全局作用域中定义了一个for循环语句,其中定义的变量 i 最终会被暴露到全局,这可能造成异常的错误

for(var i = 0; i < 10; i++) {
  console.log(i)
}

console.log(i) // 10
  • 在ES6中新增了 letconst 声明,这两个声明的变量在指定作用域外无法被访问,有暂时死区的特性(在未声明前不可使用),如下情况都是块级作用域
  • 由一对{}包裹的代码区域
  • if 语句和 for 语句

因此在之前的例子中如果把 var 改成 let, 语句中的变量就不会暴露到全局

for(let j = 0; j < 10; i++) {
  console.log(j)
}

console.log(j) // j is not defined

作用域链

  • 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是确保对执行环境有权访问的所有变量和函数的有序访问。
  • 作用域链的前端,始终都是当前执行的代码所在作用域的变量对象。如果当前作用域中找不到,则会一层一层向上寻找,直到找到全局作用域为止

扩展:静态作用域(词法作用域)与动态作用域

  • Javascript采用的是静态作用域(词法作用域),因此函数的作用域是在定义的时候(也就是书写的时候)就决定的
  • 与静态作用域相对的是动态作用域,它的函数的作用域是在函数调用时才决定的

一个例子

var value = 1

function foo(){
  console.log(value)
}

function bar(){
  var value = 2
  foo()
}

bar()
  • 在Javascript中执行 bar(),会执行到内部的 foo(),foo 函数内部查找是否有局部变量 value 没有找到,就根据书写的位置,查找上面一层的代码,也就是在全局作用域中定义的 var value = 1,所以最终输出 1
  • 如果Javascript采用的是动态作用域,执行 bar(),依然会执行到内部的 foo(),foo 函数内部查找是否有局部变量 value 没有找到,此时会在当前调用函数 bar 的作用域中查找也就是 var value = 2,最终输出 2