js作用域和作用域链

434 阅读3分钟

作用域

  • 可访问变量,对象,函数的集合
  • 块级作用域(let\const)
  • 全局作用域(变量在函数外定义)
  • 函数作用域(局部作用域,只能在函数内部访问)

作用域链

从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。

变量生命周期

变量生命周期在它声明时初始化。

局部变量在函数执行完毕后销毁。

全局变量在页面关闭后销毁。

作用域是分层的

//内层作用域可以访问外层作用域的变量,反之则不行
var a=1
function aa(){
    var b=2
    console.log(a)
}
console.log(b)
输出1 b报错未定义

块语句{}

没有作用域

if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域

if (true) {
    // 'if' 条件语句块不会创建一个新的作用域
    var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // logs 'Hammad'

var let const

var在全局范围内都有效,每一次循环,变量的值都会发生改变,里面的i指向的就是全局的i,结果都是10

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]() // 10

变量是let声明,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,就会循环输出

var a = []
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]() // 6
//JavaScript 引擎内部会记住上一轮循环的值,
//初始化本轮的变量i时,
//就在上一轮循环的基础上进行计算。

for循环

还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc

这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域

作用域与执行上下文

JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别

执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变