作用域就是变量与函数的可访问范围。在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"));