上一篇文章中分析了 JS 中的数据类型和变量。这一篇文章将分析作用域,以及回答上一篇文章中变量提升的原因。
什么是作用域
作用域是一套规则,保存着变量,等待被引擎所查找。
var a = 1;
console.log(a); // => 1
console.log(b); // => ReferenceError
当打印 a 时,引擎就去作用域中查找 a,找到把结果返回。如果查找失败,那么就会报错。
词法作用域
JS 采用的词法作用域,也可以说是静态作用域。简单来说,词法作用域是由写代码时将变量写在哪里决定的。
先看一段代码:
var a = 1;
function fn() {
var a = 2;
return a;
}
fn(); // => 2
当执行函数 fn
时,会返回 2,而不是 1。
作用域查找
JS 引擎会进行两种查找,LHS 和 RHS。怎么理解?L 和 R 可以说代表左和右。什么的左和右?赋值操作的。
这里的赋值操作不一定出现 =
,比如参数传递也是一个赋值操作。
当变量出现在赋值操作的左边时,引擎就会对这个变量进行 LHS 查找;当出现在右边时(这个还可以理解为取得变量的源值),就会进行 RHS 查找。
function foo(a) {
console.log(a);
}
foo(2);
对于变量 a 来说,引擎会进行两次查找,1 次 LHS,1 次 RHS。
调用 foo(),并传入参数 2,这时存在着一个赋值操作即 a = 2
,进行一次 LHS 查找。打印 a 时,需要获取 a 的源值,所以进行一次 RHS 查找。
如果查询失败呢?
对于 LHS 来说,给未声明的赋值就会查询失败。
a = 2;
但是我们知道,上面的代码在非严格模式下并不会报错,而变量 a 会被自动创建。
而对于 RHS 来说,直接使用未声明的变量就会报 ReferenceError。
console.log(a); // => ReferenceError
另外,RHS 虽然查询成功,但是却对查询结果进行非法操作,就会报 TypeError。
var foo = 1;
foo(); // => TypeError
作用域链
前面说,作用域是根据名称查找变量的一套规则。而在实际情况中,经常出现多个作用域嵌套的情况。
function foo(a) {
console.log(a + b);
}
var b = 2;
foo(2); // => 4
当引擎对 b 进行 RHS 查找时,在当前作用域无法找到,引擎就会在外层作用域中查找,直到找到这个变量,或者直到抵达最外层作用域(全局作用域)为止。
LHS 查找也是如此。
把这样一层一层嵌套的作用域,叫做作用域链。
函数作用域
函数作用域是指,属于这个函数的全部变量都可以在这个函数的范围内使用及复用。
function foo() {
var a = 1;
}
console.log(a); // => ReferenceError
也就是说,函数外部将无法访问函数内部的变量。
但是这却是非常有用的。我们可以利用函数隐藏内部实现,使其外部无法访问、修改等。
立即执行函数表达式
利用函数作用域,可以将外部作用域无法访问的内容包装起来。但是,带来了额外的一个问题,函数名本身“污染”了所在的作用域。
这时,就提出了 IIFE(立即执行函数表达式)。
(function foo() {
// ...
}());
即包装了内部函数,又避免了引入函数名。因为这个函数名无法被外部作用域所访问。
IIFE 的进阶用法是给其传入参数:
(function fn(global) {
// ...
})(window);
这样的好处是可以缩短查询时的作用域链。
块作用域
ES6,通过 let 和 const 引入了块作用域。
if (true) {
let a = 1;
}
console.log(a); // => ReferenceError
变量提升
上一篇文章中中提到了变量提升。
在 JS 中,var a = 1;
这行代码其实会被看成 var a
和 a = 2
,并在两个阶段去执行。
在编译阶段,执行声明操作;在执行阶段,执行赋值操作。
所有的变量声明都会被提升到作用域的顶部,这个过程叫做“提升”。
函数声明也会发生提升,并且函数声明会先于变量提升:
var foo = 1;
function foo () {}
typeof foo; // => 'number'
注意,只有函数声明会被提升,而函数表达式不会被提升。
var foo = 1;
var foo = function () {}
typeof foo; // => 'function'
小结
这篇文章梳理了 JavaScript 中作用域的基本知识。
接下来会介绍执行上下文和闭包这两个概念,它们与作用域息息相关。
关于
这是我的公众号,记录着我的前端博客,没事儿也分享一些电影、书籍。
欢迎一起交流学习。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。