JS作用域详解

123 阅读3分钟

什么是作用域和执行环境

[[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新增了letconst命令,可以用来创建块级作用域,使用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

letconst不能进行重复声明,且不会进行变量提升

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()局部环境。其中,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量或函数,查找到变量之后则不会继续向上一级对象追溯。