什么是作用域和执行环境
[[scope]]指的就是我们所说的作用域,其中存储了执行期上下文或者是执行环境的集合,作用域分为全局作用域、局部作用域、块级作用域。
执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
作用域
- 全局作用域
最外层的函数和最外层函数外部声明的变量就是全局变量 ,在整个程序中一直存在,任何地方都可以读取
var a = 1;
function f(){
console.log(a);
}
f() // 1
- 局部作用域
和全局作用域相反,局部作用域一般只能在固定代码片段内可以访问到,最常见的就是函数内部。内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数,函数内部定义的变量,会在该作用域内覆盖同名全局变量。函数内部未声明的变量为全局变量。
var b = 2;
function f(){
a = 1; //全局变量
var b = 3;
var c = 4;
console.log(b);
}
f(); //3
console.log(a) //1
console.log(c) //c is not defined
- 函数本身的作用域
函数的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
function f() {
console.log(a)
}
function f1() {
var a = 2;
f();
}
f1(); //1
- 块级作用域
ES6新增了let和const命令,可以用来创建块级作用域,使用let命令声明的变量只在let命令所在代码块内有效,无法影响到上一级的代码块。
function f() {
let a = 1;
if (true) {
let a = 2;
}
console.log(a);
}
f() // 1
for循环使用var声明,循环结束后变量i没有结束,泄漏成了全局变量,如果使用let,声明的变量仅在块级作用域内有效,块级作用域的外部无法访问
for(var i = 0 ; i<10; i++){
console.log(i)
}
console.log(i) //10
for(let i = 0 ; i<10; i++){
console.log(i)
}
console.log(i) //i is not defined
let和const不能进行重复声明,且不会进行变量提升
function f() {
console.log(a);
let a = 1;
}
f(); // Cannot access 'a' before initialization
function f() {
let a = 1;
let a = 2;
}
f(); // Identifier 'a' has already been declared
作用域链
当代码在一个环境中执行时,[[scope]]中所存储的执行环境的对象呈链式集合形成作用域链。作用域链中 的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境,一直延续到全局执行环境。查找某一变量的值时,会先从作用域链的前端对象开始,然后逐级回溯,直到找到该变量,全局执行环境的变量对象始终都是作用域链中的最后一个对象。
var a = 1;
function f() {
var a = 0;
var b = 2;
function f1() {
var b = 4;
var c = 3;
console.log(a) //0
console.log(b) //4
console.log(c) //3
}
f1();
console.log(a) //0
console.log(b) //2
console.log(c) //undefined
}
f();
console.log(a); //1
console.log(b); //undefined
console.log(c); //undefined
以上代码有三个执行环境,全局环境、f()局部环境、f1()局部环境。其中,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量或函数,查找到变量之后则不会继续向上一级对象追溯。