首頁 > web前端 > js教程 > 談JavaScript中的函數與閉包_基礎知識

談JavaScript中的函數與閉包_基礎知識

WBOY
發布: 2016-05-16 17:37:09
原創
892 人瀏覽過

闭包这东西,说难也难,说不难也不难,下面我就以自己的理解来说一下闭包

一、闭包的解释说明

对于函数式语言来说,函数可以保存内部的数据状态。对于像C#这种编译型命令式语言来说,由于代码总是在代码段中执行,而代码段是只读的,因此函数中的数据只能是静态数据。函数内部的局部变量存放在栈上,在函数执行结束以后,所占用的栈被释放,因此局部变量是不能保存的。

Javascript采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在定义函数时确定的。因此Javascript中函数对象不仅保存代码逻辑,还必须引用当前的作用域链。Javascript中函数内部的局部变量可以被修改,而且当再次进入到函数内部的时候,上次被修改的状态仍然持续。这是因为因为局部变量并不保存在栈上,而是通过一个对象来保存。

决定使用哪个变量是由作用域链决定的,每次生成函数实例时,都会为之创建一个对象用来保存局部变量,并且把这个用于保存局部变量的对象加入作用域链中。不同函数对象可以通过作用域链关联起来。Javascript中所有函数都是闭包,我们不能避免“产生”闭包。

引用一张《Javascript高级程序设计》中的图来说明,虽然这张图并不完全说明所有情况。图中的activation object就是用于保存变量的对象。

 


简而言之,在Javascript中:

闭包:函数实例保存着在执行时所需要的变量的引用,而不会复制保存当时变量的值。(在Object C的实现中,我们可以选择保存当时的值或者是引用)

作用域链:解析变量时查找变量所在的方式,以var作为终止符号,如果链上一直没有var,则一直追溯到全局对象为止。

C#中的闭包特性是由编译器把局部变量转换成引用类型的对象成员实现的。

二、闭包的使用
 
下面通过一些具体例子来说明如何利用闭包这一特性:

1.闭包是在定义的时候产生的

function Foo(){ function A(){} function B(){} function C(){}}
我们每次执行Foo()的时候,都有有A,B,C这三个函数实例(闭包)产生,当Foo执行完毕,生成的实例没有其他引用,因此会被当成垃圾随之销毁(不一定是马上销毁)。
我们来证实一下作用域链是在函数定义时确定的,所以这里显示的应该是'local scope'

var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f;}checkscope()()


同样道理:

(function(){ function A(){} function B(){} function C(){}}())
上面的表达式执行完后也会有A,B,C这三个函数实例(闭包)产生,因为这是一个立即执行的匿名函数,这三个闭包只能产生一次。生成的闭包没有其他引用,因此会被当成垃圾随之销毁(不一定是马上销毁)。

我们之所以这么写,目地有两个

1.避免污染全局对象

2.避免多次产生相同的函数实例

 

对比下面两个例子,闭包是如何保存作用域链的:

 function A(){} //比较省内存的写法,创建对象速度快,开销小 (function(prototype){ var name = "a"; function sayName () { alert(name); } function ChangeName() { name += "_changed" } prototype.sayName = sayName;//引用通过执行匿名函数产生的闭包,闭包只会产生一次 prototype.changeName = ChangeName; }(A.prototype)) var a1 = new A(); var a2 = new A();
 a1.sayName(); a1.changeName(); a2.sayName();


--------------------------------------------------------------------------------

 function B(){ //原型鏈比較短的做法,找到方法的速度快,但是比較耗內存,每次new 呼叫構造器都有2個函數實例和1個變數產生。 var name = "b"; function sayName () { alert(name); } function changeName() { name = "_changed"; } this.sayName = sayName;//引用閉包,每次呼叫函數B都會產生新的閉包this.changeName = changeName; }//如果函數呼叫之前帶有new關鍵字,則函數作為建構器使用。 //本質上來說作為建構器和作為普通函數呼叫沒區別。如果直接呼叫B(),那麼this物件會綁定到全域對象,新產生的閉包會取代舊的閉包賦給全域物件的changeName和sayName屬性上,因此舊的閉包會被當成垃圾回收。 //如果作為建構器使用,new 關鍵字會產生一個新的物件(this指向這個新物件)並初始化這個新物件的sayName和changeName屬性,因此每次產生的閉包都會因為有引用而保留下來。 var b1 = new B(); b1.sayName(); b1.changeName(); b1.sayName(); var b2 = new B(); b2.sayName(); b1.sayName();


三、洩漏問題:在編譯語言中,函數體總是在檔案的程式碼段中,並在運行期被裝入標誌為可執行的記憶體區。事實上我們不認為函數本身會有生命週期。我們在大多數情況下會認為「引用類型的資料結構」具有生存週期和洩漏的問題,如指標、物件等。

JavaScript中記憶體的洩漏本質上就是定義函數時產生的保存局部變數的物件因為存在引用而不被當成垃圾被回收。

1.存在循環引用

2.有些物件總不能銷毀,如IE6在DOM中的記憶體洩漏,或是在銷毀時不能通知到Javascript引擎,因此也就有些Javascript閉包總不能被銷毀。這些情況通常是發生在Javascript宿主物件和Javascript中原生物件溝通不良導致。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板