作用域
作用域是什么
作用域确定了代码运行时的某些特定部分的变量、函数、对象的可访问性,它负责维护和收集所有声明的标识符,已确定当前执行的代码对这些标识符的访问权限。
在JavaScript中共有三种作用域,分别是全局作用域、函数作用域及块级作用域(ES6)。
全局作用域
- 在最外层声明的函数或变量拥有全局作用域;
- 在非严格模式下,所有未声明直接使用的变量自动声明为全局作用域;
- 全局作用域下声明的变量或函数为全局对象window的属性或方法,window对象的属性拥有全局作用域。
let a = 1 // 最外层变量,拥有全局作用域
function foo() { // 最外层函数,拥有全局作用域
let b = 2 // 内层变量
function bar() { // 内层函数
console.log(b) //2
}
}
函数作用域
- 函数作用域即函数内部的作用域。在函数内部声明的变量或函数,就拥有当前所处的函数作用域。
- 作用域有层级之分,内层作用于可以访问外层作用域,反之不行。
还是上面那个例子:
let a = 1
function foo() {
let b = 2 // 内层变量
function bar() { // 内层函数
console.log(b) // 2
}
console.log(b) // 2, 可以访问到内层变量b
}
console.log(b) // b is not defined
console.log(bar) // bar is not defined
变量b和函数bar就位于函数foo的函数作用域中,foo函数内部的代码可以访问到属于foo函数内部作用域的变量b,而在foo函数外就无法访问。
块级作用域
- 块级作用域是ES6新增的,使用ES6中的let、const指令就可以创建块级作用域,块级作用域是由
{}包裹的代码片段。
在循环中使用let创建块级作用域:
for(let i = 0; i < 3; i++) {
// ...
}
console.log(i) // ReferenceError: i is not defined
通过使用let创建变量生成块级作用域,变量i就被限制在for循环块中,在快外面引用该变量会报引用错误。
作用域链
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套,这时会形成一条作用域链。当在当前作用于中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量或抵达最外层作用域(也就是全局作用域)为止,这一层层的关系就是作用域链。我们看下面例子:
let a = 3
function foo() {
let b = 4
function bar() {
console.log(a, b)
}
bar() // 3 4
}
在上面的bar函数中要求输出a和b的值,但bar函数中并没有a、b变量,因此它到外层作用域中去查找,在foo函数作用域中找到了变量b,在全局作用域中找到了全局变量a。
作用域链的作用是保证对执行环境中有权访问的所有函数和变量进行有序访问,通过作用域链可以访问到外层环境的函数和变量。
作用域链的本质是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的最前端永远是当前执行上下文的变量对象,最后端永远是全局执行上下文的变量对象。
作用域在定义时就确定。我们看下面例子:
let x = 10
function fn() {
console.log(x)
}
function show(f) {
let x = 20
(function() {
f()
})()
}
show(fn) // 10
在上面这个例子中,输出的结果为10,而不是20。因为作用域在定义时就确定,并且不会改变,所以无论函数在哪里调用,他的作用域始终为定义时所处的作用域。