JS执行环境和作用域

1,258 阅读3分钟

执行环境和作用域链

执行环境定义了变量或函数有权访问的其他数据,每个执行环境都有与之关联的变量对象,一般情况下我们无法访问变量对象,解析器会在我们访问变量或函数时在后台使用它。
执行环境的所有代码执行完毕后,该环境被销毁,其中的变量和函数定义被销毁。web浏览器的全局执行环境是window对象,所有的全局变量和函数,都是window的属性和方法,全局执行环境会在程序退出时才会销毁,也就是关闭网页或者浏览器关闭的时候。
代码在任何环境执行中都会生成一个作用域链,它可以保证当前环境下变量和函数的有序访问。作用域的前端始终是当前执行环境的变量对象,简单的说,就是变量搜索从当前执行环境向上搜索。
函数的变量对象是他的活动对象,最开始也就是arguments对象。作用域链的下一个变量对象来自外部包含环境,再下一个来自下一个包含环境,知道全局执行环境,也就是说全局执行环境是作用域链的末端。

var color = 'blue'

function changeColor () {
	var anotherColor = 'red'
  function swapColors() {
  	var tColor = anotherColor;
    anotherColor = color;
    color = tColor;
    // 这里可以访问三个变量
  }
  // 这里可以访问color anotherColor
  swapColors()
}
// 这里只能访问color
changeColor()

6121923-5af35d443368219c.png

途中矩形为执行环境,**内部环境可以通过作用域链访问所有的外部环境,反之不行。每个执行环境都向上搜索作用域链来查找变量和函数。**所以swapColors包含三个变量对象,自己的,changeColor的,window的,changeColor只包含两个,自己的和window。
所以,作用域只能按照顺序向上搜索变量。
ps:函数参数也是变量。规则和普通声明变量一致。

延长作用域链

执行环境只用全局和局部两种,但是韩式有办法在作用域前端添加一个变量对象,典型的例子有

  1. try。。。catch。。。语句
  2. with语句
try {
  var a = 123;
	a.b()
} catch (e){
	console.log(a,e)
}
console.log(a) // 123
console.log(e) // 报错,说明访问不到e变量,所以try和catch不在一个作用域。
// 这里的catch语句延长了作用域链,所以catch语句中,向上访问到了a变量。
// 最后的两个打印说明了catch的确是延长了作用域链所以才能访问到a。

function buildUrl () {
	var qs = "?a=123"
  with(location) {
  	var url = href + qs;
  }
  console.log(url) // 有结果
}
// with 会把指定的对象添加到作用域链前端,所以,访问href时,直接就在当前环境中找到。而不会向上了。

js没有块级作用域

也就是{}无法形成封闭的作用域。

if (true) {
	var color = 'blur'
}
console.log(color); // 'blue'

声明变量

如果初始化变量时没有使用var等关键字声明,那么将会是全局变量。

function add(num1, num2) {
	sum = num1 + num2
  return sum
}
add(1,2)
console.log(sum) // 3
// 所以在初始化变量时必须声明。

查询标示符

前面已经说过,变量查询是沿着作用域链向上找,找到的第一个变量作为结果返回。所以作用域链上存在同名变量时会存在变量遮蔽。

var name = 'foo'
function bar () {
	var name = 'bar'
  console.log('bar')
}
bar() // 'bar'

从作用域链看闭包

var name = 'window' 
function foo () {
	var name = 'foo'
  return function () {
  	console.log(name)
  }
}
var bar = foo() // 执行这一句时,因为返回了一个方法,方法是一个引用对象,导致foo方法执行完成后,没办法
// 销毁它自身执行环境中的变量(因为无法回收方法的引用)。
// 既然无法销毁执行环境那么作用域链就不会消失,当执行bar方法时就会沿着最开始的作用域链向上查找。
bar() // 'foo'  可以访问到foo中的变量。(所以这里不是window)