作用域
作用域规定了变量的可访问性和生命周期,帮助我们组织和管理代码。在 JavaScript 中,作用域可以分为全局作用域、局部作用域和块级作用域。
变量声明关键词
了解作用域之前,我们先了解一下变量的声明。在 JavaScript 中,有多种声明变量的方式:
- 使用
var
关键字声明的变量存在变量提升,并且具有函数作用域或全局作用域。在浏览器中var
声明的变量会成为全局对象window
的属性。 - 使用
let
和const
关键字声明的变量存在块级作用域,并且不会被提升。 let
声明的变量可以被重新赋值,而const
声明的变量是常量,不可被重新赋值。const
声明的同时需要赋值,而let
,var
可以不赋值。
关键词 | 作用域 | 声明时必须赋值 | 变量提升 | 重新赋值 | 重复声明 |
---|---|---|---|---|---|
var | 全局作用域/函数作用域 | 否 | 是 | 是 | 是 |
let | 块级作用域 | 否 | 否 | 是 | 否 |
const | 块级作用域 | 是 | 否 | 否 | 否 |
var
声明的变量会成为全局对象window
的属性。
var
声明的变量存在变量提升,编译时会提升到全局作用域定顶端(或函数作用域顶端),而let
、const
不可以
var
可重复声明
下面来看一些简单的例题
eg1:
const funList = [];
for (var i = 0; i< 10; i++) {
funList[i] = function() {
console.log(i);
}
}
console.log(i);
funList[0]();
funList[9]();
首先,我们知道var
只有全局作用域和函数作用域,显然这里展示的是全局作用域,所以var i
会被提到作用域顶端,当执行完for
循环之后,此时i=10
,console.log(i)、funList[0]()、funList[9]()
均输出10。
eg2:
const funList = [];
for (let i = 0; i< 10; i++) {
funList[i] = function() {
console.log(i);
}
}
funList[0]();
funList[9]();
console.log(i);
let
块级作用域,所以变量i,只在for循环内起作用,因此执行到console.log(i)时(访问全局作用域),会报错"i is not defined"
,funList里中的函数各自生成了一块自己的作用域,因此它们分别输出了循环时i的值。
eg3:
for (var i = 0; i< 10; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
for (let j = 0; j< 10; j++) {
setTimeout(() => {
console.log(j)
}, 0)
}
因为var
声明的是全局变量,而setTimeout
是异步的,当主线程for执行完时i=10
,然后再执行setTimeout
,这是访问的是全局变量i
,所以打印出来的都是10
。而第二个for
使用let声明的,它会形成各自的块级作用域,所以当执行setTimeout
时,它们打印出来的是各自的j
的值,也就是0~9
。
eg4:
const globalVal = 'globalVal';
function test() {
var funVar = 'funVar';
console.log(globalVal) // globalVal
console.log(funVar) // funVar
}
test()
console.log(funVar)
这里的funVar
属于函数作用域,所以在外部是无法访问funVar
变量的,因此console.log(funVar)
会报错"funVar is not defined"
全局作用域(Global Scope)
全局作用域是整个程序范围内可访问的作用域。在全局作用域中声明的变量可以被代码中的任何地方访问到。全局作用域的变量在程序执行过程中始终存在。
局部作用域(Global Scope)
局部作用域是在函数内部声明的作用域,只能在该函数内部访问。当函数执行结束时,局部作用域中的变量会被销毁,不再可访问。这种作用域可以帮助我们避免变量名冲突,提高代码的可维护性。
块级作用域(Block Scope)
ES6 引入了 let
和 const
关键字,使得 JavaScript 具备了块级作用域的能力。块级作用域由一对花括号 {}
创建,例如 if
语句、for
循环、with
、try/catch
等。在块级作用域中声明的变量只在该块内部可访问,超出该块则不可访问。
作用域链(Scope Chain)
JavaScript 中的作用域链是一种嵌套结构,它决定了变量的查找顺序。当在一个作用域内访问变量时,如果找不到该变量,JavaScript 引擎会逐级向外查找,直到找到匹配的变量或者到达全局作用域。
下面通过简单理解说明全局、局部、块级作用域以及作用域链
代码中,globalVar、globalLet、globalConst、i
均为全局变量,在这一代码块中,均能被访问到,所以它们属于全局作用域。
模块1
,属于全局作用域,模块2
,属于块级作用域,因为第一个for
是使用var声明的变量,所以不存在块级作用域,它属于全局作用域,因为var(只有全局作用域/函数作用域)存在变量提升。模块3
属于局部作用域,声明的变量只在函数内起作用。当test()
函数执行到console.log(globalVar)
时,首先会在函数内选找globalVar
变量,找不到,然后会一直向上级作用域寻找,它的上级作用域为模块1
,所以在这过程,就形成了一条作用域链。