作用域 (Scope)
1.什么是作用域
- 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到
window对象即被终止,作用域链向下访问变量是不被允许的 - 简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期
function Fun() {
var inVariable = "内部变量";
}
Fun();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
//inVariable是在Fun函数内部被定义的,属于局部变量,在外部无法访问,于是会报错
从存储上来解释的话,作用域本质上是一个对象, 作用域中的变量可以理解为是该对象的成员
总结:作用域就是代码的执行环境,全局作用域就是全局执行环境,局部作用域就是函数的执行环境,它们都是栈内存
2.作用域分类
(1)全局作用域
- 直接写在script标签的JS代码,都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量)。
- 全局变量在全局的任何位置下都可以使用;全局作用域中无法访问到局部作用域的中的变量。
- 全局作用域在页面打开的时候创建,在页面关闭时销毁。
let a = 10
function fn () {
console.log(a)
}
fn() // 10
-
所有 window 对象的属性拥有全局作用域
var和function命令声明的全局变量和函数是window对象的属性和方法
let命令、const命令、class命令声明的全局变量,不属于window对象的属性
这里函数foo()内部并没有声明name变量,但是依然打印了name的值,说明函数内部可以访问到全局作用域,读取name变量。再来一个例子:
hobby='music';
function foo(){
hobby='book';//注释本行,输出结果为music
console.log(hobby);
}
foo();//book
这里全局作用域和函数foo()内部都没有声明hobby这个变量,为什么不会报错呢?这是因为hobby='music';写在了全局作用域,就算没有var,let,const的声明,也会被挂在window对象上,所以函数foo()不仅可以读取,还可以修改值。也就是说hobby='music';等价于window.hobby='music';。
(2)函数体作用域(局部作用域)
函数体的作用域是通过隐藏内部实现的。换句话说,就是我们常说的,内层作用域可以访问外层作用域,但是外层作用域不能访问内层。原因,说到作用域链的时候就迎刃而解了。
function foo(){
var age=19;
console.log(age);
}
console.log(age);//ReferenceError:age is not defined
很明显,全局作用域下并没有age变量,但是函数foo()内部有,但是外部访问不到,自然而然就会报错了,而函数foo()没有调用,也就不会执行。
var a = 10
function fn () {
var b = 20
function fn2 () {
console.log(a + b)
}
fn2()
}
fn() //30
console.log(b) //b is not defined
- 在函数作用域中可以访问全局变量,在函数的外面无法访问函数内的变量。
- 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有就向上一作用域中寻找,直到找到全局作用域,如果全局作用域中仍然没有找到,则会报错。
(3)块级作用域
- ES6之前JavaScript采用的是函数作用域+词法作用域,ES6引入了块级作用域。
- 任何一对花括号{}中的语句集都属于一个块,在块中使用let和const声明的变量,外部是访问不到的,这种作用域的规则就叫块级作用域。
- 通过var声明的变量或者非严格模式下创建的函数声明没有块级作用域。
- 在 Web 浏览器中,全局作用域被认为是
window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。 - 在 Node环境中,全局作用域是
global对象。
function fn () {
let a = 1
{
var b = 1 // 未用let或const,不形成b的块作用域,依然是fn的函数作用域
let c = 1 // c形成块作用域,只可在当前代码块中访问
}
console.log(a) //1
console.log(b) //1
console.log(c) // c is not defined
}
fn()
作用域链
- 当代码在一个环境中创建时,会创建变量对象的一个作用域链(scope chain)来保证对执行环境有权访问的变量和函数。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。如果是函数执行阶段,那么将其activation object(AO)作为作用域链第一个对象,第二个对象是上级函数的执行上下文AO,下一个对象依次类推。
- 当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。