搜尋
首頁web前端js教程js中關於閉包的詳細介紹

js中關於閉包的詳細介紹

Jun 29, 2017 am 11:35 AM
作用域記憶體回收

1. 什麼是閉包?

來看一些關於閉包的定義:

  1. #閉包是指有權存取另一個函數作用域中變數的函數- -《JS高階程式設計第三版》 p178

  2. 函數物件可以透過作用域鏈相關聯起來,函數體內部的變數都可以保存在函數作用域內,這種特性稱為'閉包' 。 --《JS權威指南》 p183

  3. 內部函數可以存取定義它們的外部函數的參數和變數(除了thisarguments )。 --《JS語言精粹》 p36

來個定義總結

  1. 可以存取外部函數作用域中變量的函數

  2. 被內部函數存取的外部函數的變數可以保存在外部函數作用域內而不被回收---這是核心,後面我們遇到閉包都要想到,我們要專注在被閉包引用的這個變數。

來建立一個簡單的閉包

var sayName = function(){var name = 'jozo';return function(){
        alert(name);
    }
};var say = sayName(); 
say();

來解讀後面兩個語句:

  • var say = sayName() :返回了一個匿名的內部函數保存在變數say中,並且引用了外部函數的變數name,由於垃圾回收機制,sayName函數執行完畢後,變數name並沒有被銷毀。

  • say() :執行傳回的內部函數,仍能存取變數name,輸出'jozo' .

2. 閉包中的作用域鏈

理解作用域鏈對理解閉包也很有幫助。

變數在作用域中的找出方式應該都很熟悉了,其實這就是順著作用域鏈往上找的。

當函數被呼叫時:

  1. 先建立一個執行環境(execution context),及對應的作用域鏈;

  2. 將arguments和其他命名參數的值加入函數的活動物件(activation object)

作用域鏈:目前函數的活動物件優先權最高,外部函數的活動物件次之,外部函數的外部函數的活動物件依次遞減,直至作用域鏈的末端--全域作用域。優先順序就是變數尋找的順序;

先來看個普通的作用域鏈:

function sayName(name){return name;
}var say = sayName('jozo');

這段程式碼包含兩個作用域: a.全域作用域;b.sayName函數的作用域,也就是只有兩個變數對象,當執行到對應的執行環境時,變數物件會變成活動對象,並被推入到執行環境作用域鏈的前端,也就是成為優先順序最高的那個。 看圖片說話:

這圖在JS高階程式設計書上也有,我重新繪了遍。

在創建sayName()函數時,會建立一個預先包含變數物件的作用域鏈,也就是圖中索引為1的作用域鏈,並且被儲存到內部的[[Scope]]屬性中,當呼叫sayName()函數的時候,會建立一個執行環境,然後透過複製函數的[[Scope]]屬性中的物件建立起作用域鏈,此後,又有一個活動物件(圖中索引為0 )被創建,並被推入執行環境作用域鏈的前端。

一般來說,當函數執行完畢後,局部活動物件就會被銷毀,記憶體中只保存全域作用域。但是,閉包的情況又有所不同:

再來看看看閉包的作用域鏈:

function sayName(name){return function(){return name;
    }
}var say = sayName('jozo');

這個閉包實例比上一個例子多了一個匿名函數的作用域:

在匿名函式從sayName()函式中被傳回後,它的作用域鏈被初始化為包含sayName()函數的活動物件和全域變數物件。這樣,匿名函數就可以存取在sayName()中定義的所有變數和參數,更為重要的是,sayName()函數在執行完畢後,其活動物件也不會被銷毀,因為匿名函數的作用域鏈依然在引用這個活動對象,換句話說,sayName()函數執行完後,其執行環境的作用域鏈會被銷毀,但他的活動對象會留在記憶體中,知道匿名函數會銷毀。這個也是後面要講到的記憶體外洩的問題。

作用域鏈問題不寫那麼多了,寫書上的東西也很累o(╯□╰)o

3. 閉包的實例

實例1:實現累加

// 方式1var a = 0;var add = function(){
    a++;
    console.log(a)
}add();add();//方式2 :闭包var add = (function(){
    var  a = 0;
    return function(){
        a++;
        console.log(a);
    }
})();
console.log(a); //undefinedadd();add();

相比之下方式2更加优雅,也减少全局变量,将变量私有化

實例2 :給每個li添加點擊事件

 var oli = document.getElementsByTagName(&#39;li&#39;); var i; for(i = 0;i < 5;i++){
     oli[i].onclick = function(){
         alert(i);
     }
 } console.log(i); // 5 //执行匿名函数
 (function(){
    alert(i);  //5
 }());

上面是一個經典的例子,我們都知道執行結果是都彈出5,也知道可以用閉包解決這個問題,但是我剛開始始終不能明白為什麼每次彈出都是5,為什麼閉包可以解決這個問題。後來捋一捋還是把它弄清晰了:

a. 先来分析没用闭包前的情况:for循环中,我们给每个li点击事件绑定了一个匿名函数,匿名函数中返回了变量i的值,当循环结束后,变量i的值变为5,此时我们再去点击每个li,也就是执行相应的匿名函数(看上面的代码),这是变量i已经是5了,所以每个点击弹出5. 因为这里返回的每个匿名函数都是引用了同一个变量i,如果我们新建一个变量保存循环执行时当前的i的值,然后再让匿名函数应用这个变量,最后再返回这个匿名函数,这样就可以达到我们的目的了,这就是运用闭包来实现的!

b. 再来分析下运用闭包时的情况:

     var oli = document.getElementsByTagName(&#39;li&#39;);     var i;     for(i = 0;i < 5;i++){
         oli[i].onclick = (function(num){             var a = num; // 为了说明问题             return function(){
                 alert(a);
             }
         })(i)
     }     console.log(i); // 5

这里for循环执行时,给点击事件绑定的匿名函数传递i后立即执行返回一个内部的匿名函数,因为参数是按值传递的,所以此时形参num保存的就是当前i的值,然后赋值给局部变量 a,然后这个内部的匿名函数一直保存着a的引用,也就是一直保存着当前i的值。 所以循环执行完毕后点击每个li,返回的匿名函数执行弹出各自保存的 a 的引用的值。

4. 闭包的运用

我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。

1. 匿名自执行函数

我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:

//将全部li字体变为红色
(function(){    var els = document.getElementsByTagName(&#39;li&#39;);for(var i = 0,lng = els.length;i < lng;i++){
        els[i].style.color = &#39;red&#39;;
    }    
})();

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,
因此els,i,lng这些局部变量在执行完后很快就会被释放,节省内存!
关键是这种机制不会污染全局对象。

2. 实现封装/模块化代码

var person= function(){    //变量作用域为函数内部,外部无法访问    var name = "default";       return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();console.log(person.name);//直接访问,结果为undefined    
console.log(person.getName());  //default 
person.setName("jozo");    
console.log(person.getName());  //jozo

3. 实现面向对象中的对象
这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。还是以上边的例子来讲:

function Person(){    var name = "default";       return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
};    


var person1= Person();    
print(person1.getName());    
john.setName("person1");    
print(person1.getName());  // person1  

var person2= Person();    
print(person2.getName());    
jack.setName("erson2");    
print(erson2.getName());  //person2

Person的两个实例person1 和 person2 互不干扰!因为这两个实例对name这个成员的访问是独立的 。

5. 内存泄露及解决方案

垃圾回收机制

说到内存管理,自然离不开JS中的垃圾回收机制,有两种策略来实现垃圾回收:标记清除 和 引用计数;

标记清除:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量的标记和被环境中的变量引用的变量的标记,此后,如果变量再被标记则表示此变量准备被删除。 2008年为止,IE,Firefox,opera,chrome,Safari的javascript都用使用了该方式;

引用计数:跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。

这个方式存在一个比较大的问题就是循环引用,就是说A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 。早期的IE版本里(ie4-ie6)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。

我们知道,IE中有一部分对象并不是原生额javascript对象,例如,BOM和DOM中的对象就是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数。因此,虽然IE的javascript引擎采用的是标记清除策略,但是访问COM对象依然是基于引用计数的,因此只要在IE中设计COM对象就会存在循环引用的问题!

举个栗子:

window.onload = function(){var el = document.getElementById("id");
    el.onclick = function(){
        alert(el.id);
    }
}

这段代码为什么会造成内存泄露?

el.onclick= function () {
    alert(el.id);
};

执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被回收;

解决方法:

window.onload = function(){var el = document.getElementById("id");var id = el.id; //解除循环引用
    el.onclick = function(){
        alert(id); 
    }
    el = null; // 将闭包引用的外部函数中活动对象清除
}

6. 总结闭包的优缺点

优点:

  • 可以让一个变量常驻内存 (如果用的多了就成了缺点

  • 避免全局变量的污染

  • 私有化变量

缺点

  • 因為閉包會攜帶包含它的函數的作用域,因此會比其他函數佔用更多的記憶體

  • ##會造成記憶體外洩

  • #

以上是js中關於閉包的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。