作用域:
首先,在javascript中的每个函数都是对象,是Funtion对象的一个实例,而Funtion中有一系列仅供javascript引擎存取的内部属性,其中一个便是[[scope]],它包含了一个函数被创建的作用域中对象的集合,这个集合就是函数的作用域链。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:
function add(num1,num2){
var sum = num1+num2;
return sum;
}
在函数add创建时,它的作用域链中会填入一个单独的可变对象,即全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):
函数add的作用域将在执行时候用到,假设var total = add(5,10);执行此函数会创建一个称为执行上下文的内部对象,一个执行上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用统一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。每个执行下文都有自己的作用域链,用于标识符解析,当执行上下文被创建时,它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,活动对象作为函数运行期的可变对象,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,即它通过函数的arguments属性初始化,arguments属性的值是Arguments对象:
【Arguments对象是活动对象的一个属性,它包括如下属性:
这个共享其实不是真正的共享一个内存地址,而是2个不同的内存地址,使用JavaScript引擎来保证2个值是随时一样的,当然这也有一个前提,那就是这个索引值要小于你传入的参数个数,也就是说如果你只传入2个参数,而还继续使用arguments[2]赋值的话,就会不一致,如:
function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2);
这时候因为没传递第三个参数a,所以赋值10以后,alert(a)的结果依然是undefined,而不是10,但如下代码弹出的结果依然是10,因为和a没有关系。
function b(x, y, a) {
arguments[2] = 10;
alert(arguments[2]);
}
b(1, 2);
】然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。注意:如果名字相同的两个变量存储在作用域链的不同部分,那么标识符就是遍历作用域时最先找到的那一个。
闭包:
闭包,简单点说就是函数里面嵌套函数如:
function a(){
var name,age;
function b(){
console.log(name);
console.log(age);
}
}
例子中的b函数就是一层闭包,闭包的强大之处在于它允许函数访问作用域之外的数据,如例子中在b函数内部访问a函数中的变量,在看一个例子:
funtion assignEvents(){
var id = "xdi9592";
document.getElementById("save-btn").onclick = function(event){
saveDocument(id);
}
}
assignEvents函数中的事件处理器就是一个闭包,他在assignEvents执行时被创建,并且能访问所属作用域的id变量。当assignEvents()被执行时,一个包含了变量id和其他一些数据的活动对象被创建,即为运行期上下文作用域中的一个对象,当闭包被创建时,它的[[scope]]属性被初始化为这些对象,如图:
因为闭包的[[scope]]属性包含了与运行期上下文作用域链相同的对象的引用,因此,当外层函数执行完毕时,由于引用仍然存在闭包的[[scope]]属性中,故活动对象无法被销毁,会使内存消耗大,即造成内存泄漏。
当闭包被执行的时候,一个执行上下文又被创建,它的作用域链与属性[[scope]]中引用的两个相同的作用域链对象同时被初始化,然后一个活动对象被闭包自身所创建,如图: