来自JS高设--执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个变量中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。
理解--执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用。执行环境是基于对象来说的,比如 window对象在全局环境中。
执行环境可以分为执行和创建两个阶段。
1.创建阶段(当函数被调用,但未执行任何其内部代码之前),
2.执行阶段
浏览器中的JS解析器被实现为单线程,这意味着同一时间只能发生一件事情,其他行为或事件将会被放在一个叫做执行栈的内存中排队。
当浏览器首次载入脚本代码,它将默认进入全局执行环境。如果在全局代码中调用一个函数,程序的时序将进入被调用的函数,并创建一个新的执行环境并将其压入执行栈的顶部。浏览器总会首先执行位于栈顶的执行环境,运行结束后就从栈顶弹出,把控制权返回给之前的执行环境。类推,堆栈中的执行环境会被依次执行并弹出堆栈,知道回到全局执行环境。
代码执行环境栈表示图 (图片来自笨蛋的座右铭)
活动对象是被激活的变量对象
作用域是什么
作用域分为哪几种
来自JS高设--当代码在一个环境中执行的时候,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的),而作用域链中的下一个变量对象来自包含(外部)环境,再下一个变量对象则来自下一个包含环境。这样一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
理解--根据内部函数可以访问外部函数变量的这种机制,所有可被内部函数访问的变量在被访问时会根据一种链式机制来进行访问,而这种包含且由有权访问的变量组成的链式机制,就称为作用域链。
作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
图解作用域链(此处图片来自JavaScript关于作用域、作用域链和闭包的理解)
由执行环境知道,当某个函数被调用的时候,就会创建一个执行环境以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[scope],然后使用this,argumens和其他命名参数的值来初始化函数的活动对象。当前执行环境的变量始终在作用域链的第0位。
1.
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
上述代码中,当运行fn1( )时的作用域链图解:
由图可知,fn1( )的作用域里并没有scope变量,于是沿着作用域链向上一次查找,结果在全局作用域里找到了变量scope,返回此值,结束查找。
到现在,我们可以很清楚的知道,作用域的一个作用就是完成标识符解析。标识符解析是沿着作用域链一级一级的搜素标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级向后回溯,直到找到标识符未知。如果找不到标识符,通常会导致错误。
2.
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
在outer( )函数内部返回了inner( )函数。所以在调用outer( )函数时,inner( )函数的作用域链就已经被初始化了,即复制父函数的作用域链,再在作用域链最前端插入自己的活动对象,具体如下图
一般来说,当某个执行环境中的所有代码执行完毕后,该执行环境会被弹出环境栈,保存在其中的所有变量和函数也随之销毁。全局执行环境变量会到应用程序推出后销毁,如关闭网页。但针对上面所说的这种情况,当outer( )函数执行后,其执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
像上面这种内部函数的作用域链仍然保持着对父级函数活动对象的引用,就是闭包。
闭包,即 有权访问其他函数作用域内变量的一个函数,本质上是将函数内部和外部连接起来的一座桥梁。
先看一段典型的代码
上面的函数会返回一个函数数组。表面上,似乎每个函数都应该返回自己的索引值,但实际上每个函数都返回10.这是因为每个函数的作用域链中都保存着aArray( )函数的活动对象,所有它们引用的都是同一个变量i。当aArray( )函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。
由此也可以知道,js函数内的变量值不是在编译的时候就确定的,而是等在运行时期才去寻找的。且由于作用域链的本质是一个指向变量对象的指针列表,所以有一个副作用,就是闭包只能取得包含函数中任何变量的最后一个值。
如何让代码达到预期呢?看下面的代码
function aArray() {
var result=new Array;
for(var i=0;i<10;i++){
result[i]= function (num) {
return function (){
return num;
}
}(i);
}
return result;
}
var f=aArray();
console.log(f[0]()); //0
console.log(f[1]()); //1
console.log(f[2]()); //2
在这段代码中,我们没有直接把闭包赋给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个num,也就是最终函数要返回的值。在调用每个匿名函数时,我们传入了变量i,由于函数参数是按值传递的,所以就会将变量i的当前值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。
总结--闭包的两个作用