ITEEDU

javascript作用域链和闭包

作用域

作用域就是变量与函数的可访问范围。在javascript中分为全局作用域和局部作用域。

全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域。在Web浏览器中,他指的就是window对象,该环境直到应用程序退出才被摧毁。

局部作用局:和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到(最常见的例如函数内部),该环境中的代码被执行完毕后,该环境被摧毁。

作用域链

函数对象有一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链(Scope chain)。

当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象所填充。例如:

function add (num1,num2){
     var sum = num1 + num2;
     return sum;
}

当函数add()创建后,它的作用域链中填入了一个单独的对象,这个对象表示所有范围定义的变量。该全局对象包括诸如window,navigator和document等等。如图说明了他们的关系:

函数add的作用域会在被执行时用到:

var total = add (5,10);

执行这个函数时会创建一个称为"运行期上下文(execution context)"的内部对象。一个运行期上下文定义了一个函数值执行时的环境,他是独一无二的,当函数运行完毕,它就会被摧毁。

如果对运行期上下文这个对象还是不明白看下面这个图:

下面说下图中运行的过程:(对应图中的1,2,3)

1.当运行期被创建时,它的作用域链初始化为当前运行函数的[[Scope]]属性中所包含的对象。

2.这些值按照他们出现在函数中的顺序,被复制到作用域链中。

3.上面2过程一旦完成,一个被称为"活动对象"的新对象就会被创建,他被推入到作用域链的前端。

4. 当1(运行期上下文)被摧毁,3(活动对象)也随之被摧毁。

作用域链例子

var num = 1;
function total(){
    alert(num);
    var num = 2;
}
var num = 10;
total();

最终结果是undefined,因为局部作用域中有num变量,但在调用后定义,所以调用时就undefined了。

闭包

用简单的语句来描述JavaScript中的闭包的概念:由于JavaScript中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内定义函数成为理所当然,如果在函数func内部声明函数inner,然后在函数外部调用inner,这个过程即产生了一个闭包。 闭包的效果可以简单描述为“使函数外部可以读取函数内部的局部变量”。

闭包的一切效果都基于外部调用inner函数会导致func的上下文不会被摧毁。

     function f1(){
         var n=999;
         nAdd=function(){
             n+=1
         }
         function f2(){
       alert(n);
     }
         return f2;
    }
     var result=f1();
     result();  // 999   输出这个f2的返回值
     nAdd();  //调用nAdd函数
     result();  // 1000

因为f2的上下文依赖于f1的上下文所以在f1运行完后其上下文不会被摧毁,其中的值一直都存在着。

闭包的用途

对象的封闭

封装的效果为未经授权的客户代码无法访问到我们不公开的数据。看这个例子:

function Person(name) {
	// private variable
	var address = "The Earth";
	// public method
	this.getAddress = function() {
		return address;
	}
	// public variable
	this.name = name;
}
// public
Person.prototype.getName = function() {
	return this.name;
}
// public
Person.prototype.setName = function(name) {
	this.name = name;
}
 

每一次new Person()时都会创建一个上下文,其中局部变量address就在其中。新创建的对象中会有一个方法getAddress()可以调用上下文中的address,这就形成闭包,使上下文的生命周期和创建的对象一样。每一个Person都会关联一个上下文,这样就可以实现属性的封闭了。

缓存

设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

var CachedSearchBox = (function() {
	var cache = {}, count = [];
	return {
		attachSearchBox : function(dsid) {
			if (dsid in cache) {// 如果结果在缓存中
				return cache[dsid];// 直接返回缓存中的对象
			}
			var fsb = new uikit.webctrl.SearchBox(dsid);// 新建
			cache[dsid] = fsb;// 更新缓存
			if (count.length > 100) {// 保正缓存的大小<=100
				delete cache[count.shift()];
			}
			return fsb;
		},
		clearSearchBox : function(dsid) {
			if (dsid in cache) {
				cache[dsid].clearSelection();
			}
		}
	};
})();
CachedSearchBox.attachSearchBox("input1");

这样,当我们第二次调用CachedSearchBox.attachSerachBox(“input1”)的时候,我们就可以从缓存中取道该对象,而不用再去创建一个新的searchbox对象。

柯里化

柯里化就是预先将函数的某些参数传入,得到一个简单的函数,但是预先传入的参数被保存在闭包中,因此会有一些奇特的特性。

var adder = function(num){
    return function(y){
        return num + y;
    }
}
var inc = adder(1);
var dec = adder(-1);

这里的inc/dec两个变量事实上是两个新的函数,可以通过括号来调用,比如下例中的用法:

//inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1
alert(inc(99));//100
alert (dec(101));//100
alert (adder(100)(2));//102
alert (adder(2)(100));//102

柯里化可以创建有状态的函数,函数的状态保存在其关联的上下文中。再看一个例子:

//update会返回一个函数,这个函数可以设置id属性为item的web元素的内容
function update(item) {
	return function(text) {
		$("div#" + item).html(text);
	};
}
// Ajax请求,当成功是调用参数callback
function refresh(url, callback) {
	var params = {
		type : "echo",
		data : ""
	};
	$.ajax({
		type : "post",
		url : url,
		cache : false,
		async : true,
		dataType : "json",
		data : params,
		// 当异步请求成功时调用
		success : function(data, status) {
			callback(data);
		},
		// 当请求出现错误时调用
		error : function(err) {
			alert("error : " + err);
		}
	});
}
refresh("action.do?target=news", update("newsPanel"));
refresh("action.do?target=articles", update("articlePanel"));
refresh("action.do?target=pictures", update("picturePanel"));