作用域和作用域链
作用域概念
提示
作用域是当前的执行上下文,值和表达式在其中可见或可被访问。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。
JavaScript
的作用域分以下三种:
- 全局作用域:脚本模式运行所有代码的默认作用域
- 函数作用域:由函数创建的作用域
- 块级作用域:用一对花括号(一个代码块)创建出来的作用域
全局作用域
在程序顶部或函数外部声明的变量被认为是全局范围变量。全局作用域中声明的变量与函数,可以在代码的任何地方被访问。
一般来说,以下三种情形属于全局作用域。
全局对象下的属性与方法
window.name;
window.location;
window.top;
在最外层声明的变量与方法
let name = "jimmy";
function greet() {
console.log(name);
}
greet(); // jimmy
在非严格模式下,函数作用域中未定义但直接赋值的变量与方法
在非严格模式下,这样的变量自动变成全局对象 window 的属性。因此他们也是属于全局作用域。
function foo() {
bar = 20;
}
function fn() {
foo();
return bar + 30;
}
fn(); // 50
需要注意的是,从一个完整的大型应用的角度来考虑,我们应该尽量少的将变量或者方法定义为全局。
我们可能会无意间修改全局变量的值,但是其他场景并不知道
// 定义全局变量
const foo = { m: 200 };
function setM() {
// 轻易的被修改
foo.m = 300;
}
setM();
危险
避免使用全局变量是一个很好的做法,因为全局变量的值可能会在程序的不同区域发生变化。它可以在程序中引入未知的结果。因此,对于团队项目管理来说,每一个全局变量的使用,都应该引起足够的重视以防止影响到别的代码逻辑。
函数作用域
每一个花括号 {}
都是一个代码块。但需要注意的是,并不是所有花括号,都能够具备自己的作用域。函数声明或者函数表达式,能够让花括号具备作用域,我们称之为函数作用域。函数作用域中声明的变量与方法,只能被下层子作用域访问,不能被其他不相关的作用域访问。
let a = "hello";
function greet() {
let b = "World";
console.log(a + b);
}
greet();
console.log(a + b); // error
块级作用域
块级作用域由最近的一对包含花括号 {}
界定。换句话说, if
块、while
块、function
块,单独的块级作用域
let
和 cosnt
实际上为 JavaScript
新增了块级作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE
)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
作用域链
提示
当所需要的变量在所在的作用域中查找不到的时候,它会一层一层向上查找,直到找到全局作用域还没有找到的时候,就会放弃查找。这种一层一层的关系,就是作用域链。