js核心系列(四) —— 作用域和作用域链
作用域(scope)
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
在上面的程序中,变量 a 是全局变量,变量 b 是局部变量。变量 b 只能在函数 hello 中访问。因此,当我们尝试访问函数外部的变量 b 时,会发生一个错误。
块级作用域
块级作用域由最近的一对包含花括号{} 界定。换句话说, if 块、while 块、function块,单独的块块级作用域。
let
和cosnt
实际上为 JavaScript 新增了块级作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函数有两个代码块,都声明了变量n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var
定义变量n
,最后输出的值才是 10。
ES6 允许块级作用域的任意嵌套。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。
内层作用域可以定义外层作用域的同名变量。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
作用域链(scope chain)
我们用一张图来理解
- 蓝色框是全局作用域,定义了a变量,以及里面的所有函数。
- 红色框是first函数的作用域,它定义了变量 b ,以及second函数。
- 绿色框是第second的作用域。log语句用于输出变量 a、 b 和 c。
当代码执行道second函数是,打印变量a,b,c,但是变量 a 和 b 没有在second函数中定义,只定义了c。这时就会往上层作用域查找,于是从first函数找到了变量 b = 'Hello'。这时还没用找到a,所有再继续往上层作用域查找,然后找到了a = 'Hey',这样一层一层往上查找的过程,就被成为作用域链
。
当 JS 引擎无法在作用域链中找到变量时,它就会停止执行并抛出错误。
转载自:https://juejin.cn/post/7224501858411282487