在编程语言中,做用域控制着变量与参数的可见性及生命周期,它能减小名称冲突,并且提供了自动内存管理(javascript 语言精粹)javascript
再者,js不像其余的编程语言同样,拥有着块级做用域,就像下面一段代码。前端
function afunction(){ var a = 'sf'; console.log(b); console.log(c); var b = function(){ console.log('这是b中的内容'); } function c(){ console.log('这是c中的内容'); } (function d(){ console.log('这是d中的内容'); })() }
实用var声明的变量和函数声明将会进行声明提早在afunction
函数的执行环境中,故上述代码至关于如下的代码,在一个变量声明提早的时候,其值为undefined
,而函数声明则是将函数体做为值。java
function afunction(){ var a; var b; function c(){ console.log('这是c中的内容'); } a = 'sf'; console.log(b); console.log(c); b = function(){ console.log('这是b中的内容'); } (function d(){ console.log('这是d中的内容'); })() }
将上述的代码稍做改动以下编程
var outer = 'outer'; function afunction(){ function c(){ console.log('这是c中的内容'); } a = 'sf'; console.log(outer); }
咱们在afunction
函数的外部定义了outer
变量,假设这段代码运行在浏览器上,那么变量提早的过程当中outer
变量被声明在了window做用域上,也就是浏览器中的全局做用域上,而函数中的变量则在函数运行时被声明在了afunction
做用域上,这个就是局部做用域,在这个局部做用域中,outer
变量被访问到了,这种跨做用域的读取变量的形式就是根据做用域链来实现的。浏览器
在js中,函数也是对象,函数与其余的对象同样,拥有能够访问的属性,[[Scope]]
就是其中的一个属性,它指明了哪些东西能够被函数访问。
考虑下面的函数闭包
function add(a,b){ var sum = a + b; return sum; }
当函数add
建立时候,add
的[[Scope]]属性会指向做用域链对象,该对象的初始位置指向全局对象,以下图所示。编程语言
var t = add(1,2);
上述语句执行了add
函数,对于函数的每一次执行,浏览器会建立一个执行环境的内部对象,一个执行环境定义了一个函数执行时的环境。函数的每次执行时对应的执行环境都说惟一的。每个执行环境都有本身的做用域链,此对象的局部变量,this
, arguments
等组成活动对象,插入在做用域链对象的最前端,也就是图中所示的0号位置,当运行结束后,执行环境和活动对象都将销毁。
函数的执行过程当中,每遇到一个变量,都会从做用域链的顶部,也就是0号位置查找该变量,若是查找成功则返回,查找失败则按照做用域链查找下一个位置的对象,该例子中也就是1号位置的全局对象。模块化
如上面所讨论的那样,每一次遇到读取变量的时候,都意味着一次搜索做用域链的过程,若是搜索的做用域链的层次越多的话,将严重影响性能。
因此,当在函数中使用全局变量的时候,所产生的代价是最大的,由于全局对象一直处于做用域链的最末位置,读取局部变量是最快的。
因此,一个提升效率的规则是尽量的使用局部变量。以下面的代码所示。函数
function demo(){ var d = document, bd = d.body, div = d.getElementsByTagName('div'); d.getElementById('id1').innerHTML = 'aaa'; //(许多使用document,body和div的操做) }
上面的代码首先将全局的document
对象保存在了局部变量d中,这样当下次频繁的使用document
对象时,仅仅须要从局部变量中便可得到。性能
js中实用的是静态做用域,做用域链通常不可改变,可是with
和try-catch
能够改变做用域链,发生在函数的执行时候
function withTest(){ var foo = 'sf'; var obj = {foo:'abc'}; with(obj){ function f(){ alert(foo); } (function(){ alert(foo); })(); f(); } } withTest();
在函数声明的时候,做用域链没有考虑with
的状况,当函数执行的时候,动态生成with
的对象,推入在做用域链的首位,这就意味着函数的局部变量存在做用域链的第二个位置,访问的代价提升了,虽然访问with
对象的代价下降了,彻底能够将with
对象保存在局部变量中,故with
语句不推荐使用。
try{ anErrorFunction(); }catch(e){ errorHandler(e); }
因为catch
语句中只有一条语句,将error传递给errorHandler
函数,因此运行时做用域链的改变不会影响性能。
闭包是容许函数访问局部做用域以外的数据。即便外部函数已经退出,外部函数的变量仍能够被内部函数访问到。
所以闭包的实现须要三个条件:
内部函数实用了外部函数的变量
外部函数已经退出
内部函数能够访问
function a(){ var x = 0; return function(y){ x = x + y; return x; } } var b = a(); b(1);
上述代码在执行的时候,b获得的是闭包对象的引用,虽然a执行完毕后,可是a的活动对象因为闭包的存在并无被销毁,在执行b(1)
的时候,仍然访问到了x变量,并将其加1,若在此执行b(1)
,则x是2,由于闭包的引用b并无消除。
//ul下面有3个li,实现点击每一个li,弹出li的序号 for(var i = 0,len = lis.length;i < len; i++){ lis[i].onclick = function(i){ return function(){ alert(i); } }(i); }
在这里,没有把闭包直接给onclick
事件,而是先定义了一个自执行函数,该函数中包含着闭包的函数,i的值被保存在自执行的函数中,当闭包函数执行后,会从自执行函数中查找i,达到“保存”变量的目的。
注:匿名函数中的this
指向的是window,故在匿名闭包函数使用父函数的this
指针时,须要将其存储下来,如 var that = this;
模块化代码
私有成员
避免全局变量的污染
但愿一个变量长期驻扎在内存中
如上面的描述,当执行闭包函数后,父函数所保留下来的活动对象并非在闭包函数的做用域链的首位(首位存放的是闭包的活动对象),当频繁的访问跨做用域的标识符时候,每次都会形成性能的损失,咱们仍然能够将经常使用的跨做用域变量存储在局部变量中,直接访问该局部变量
IE9及如下的版本使用的是引用计数的内存回收机制,当引用计数为0的时候将会回收,但有一种循环引用的状况
window.onload = function(){ var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
这段代码执行时,将匿名函数对象赋值给el
的onclick
属性;而后匿名函数内部又引用了el
对象,存在循环引用,因此不能被回收;
(javascript 高级程序设计(第三版))
解决方法:
window.onload = function(){ var el = document.getElementById("id"); var id = el.id; //解除了循环引用 el.onclick = function(){ alert(id); //并无出现循环引用 } el = null; // 将闭包引用的外部活动对象清除 }