Skip to content

作用域和作用域链

作用域概念

提示

作用域是当前的执行上下文,值和表达式在其中可见或可被访问。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。

JavaScript 的作用域分以下三种:

  • 全局作用域:脚本模式运行所有代码的默认作用域
  • 函数作用域:由函数创建的作用域
  • 块级作用域:用一对花括号(一个代码块)创建出来的作用域

全局作用域

在程序顶部或函数外部声明的变量被认为是全局范围变量。全局作用域中声明的变量与函数,可以在代码的任何地方被访问。

一般来说,以下三种情形属于全局作用域。

全局对象下的属性与方法

js
window.name;

window.location;

window.top;

在最外层声明的变量与方法

js
let name = "jimmy";

function greet() {
  console.log(name);
}
greet(); // jimmy

在非严格模式下,函数作用域中未定义但直接赋值的变量与方法

在非严格模式下,这样的变量自动变成全局对象 window 的属性。因此他们也是属于全局作用域。

js
function foo() {
  bar = 20;
}

function fn() {
  foo();

  return bar + 30;
}

fn(); // 50

需要注意的是,从一个完整的大型应用的角度来考虑,我们应该尽量少的将变量或者方法定义为全局。

我们可能会无意间修改全局变量的值,但是其他场景并不知道

js
// 定义全局变量

const foo = { m: 200 };

function setM() {
  // 轻易的被修改

  foo.m = 300;
}

setM();

危险

避免使用全局变量是一个很好的做法,因为全局变量的值可能会在程序的不同区域发生变化。它可以在程序中引入未知的结果。因此,对于团队项目管理来说,每一个全局变量的使用,都应该引起足够的重视以防止影响到别的代码逻辑。

函数作用域

每一个花括号 {} 都是一个代码块。但需要注意的是,并不是所有花括号,都能够具备自己的作用域。函数声明或者函数表达式,能够让花括号具备作用域,我们称之为函数作用域。函数作用域中声明的变量与方法,只能被下层子作用域访问,不能被其他不相关的作用域访问。

js
let a = "hello";

function greet() {
  let b = "World";
  console.log(a + b);
}

greet();
console.log(a + b); // error

块级作用域

块级作用域由最近的一对包含花括号 {} 界定。换句话说, if 块、while 块、function 块,单独的块级作用域

letcosnt 实际上为 JavaScript 新增了块级作用域。

js
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。

js
// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

作用域链

提示

当所需要的变量在所在的作用域中查找不到的时候,它会一层一层向上查找,直到找到全局作用域还没有找到的时候,就会放弃查找。这种一层一层的关系,就是作用域链。