摘要:該對(duì)象包含了函數(shù)的所有局部變量命名參數(shù)參數(shù)集合以及,然后此對(duì)象會(huì)被推入作用域鏈的前端。如果整個(gè)作用域鏈上都無(wú)法找到,則返回。此時(shí)的作用域鏈包含了兩個(gè)對(duì)象的活動(dòng)對(duì)象和對(duì)象。
前端學(xué)習(xí):教程&開發(fā)模塊化/規(guī)范化/工程化/優(yōu)化&工具/調(diào)試&值得關(guān)注的博客/Git&面試-前端資源匯總
歡迎提issues斧正:閉包
JavaScript-閉包閉包(closure)是一個(gè)讓人又愛又恨的something,它可以實(shí)現(xiàn)很多高級(jí)功能和應(yīng)用,同時(shí)在理解和應(yīng)用上有很多難點(diǎn)和需要小心注意的地方。
閉包的定義閉包,官方對(duì)閉包的解釋是:一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分。
簡(jiǎn)單來(lái)說(shuō),閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。在Javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取函數(shù)的局部變量,所以,可以把閉包理解成:定義在一個(gè)函數(shù)內(nèi)部的函數(shù),也就是函數(shù)嵌套函數(shù),給函數(shù)內(nèi)部和函數(shù)外部搭建起一座橋梁。
定義在一個(gè)函數(shù)內(nèi)部的函數(shù)。
函數(shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量。
作為一個(gè)函數(shù)變量的一個(gè)引用,當(dāng)函數(shù)返回時(shí),其處于激活狀態(tài)。
當(dāng)一個(gè)函數(shù)返回時(shí),一個(gè)閉包就是一個(gè)沒(méi)有釋放資源的棧區(qū)。函數(shù)的參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收。
閉包的形成Javascript允許使用內(nèi)部函數(shù),可以將函數(shù)定義和函數(shù)表達(dá)式放在另一個(gè)函數(shù)的函數(shù)體內(nèi)。而且,內(nèi)部函數(shù)可以訪問(wèn)它所在的外部函數(shù)聲明的局部變量、參數(shù)以及聲明的其他內(nèi)部函數(shù)。當(dāng)其中一個(gè)這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時(shí),就會(huì)形成閉包。
function a() { var i = 0; function b() { console.log(i++); } return b; } var c = a(); c();閉包的缺點(diǎn)
1.由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大。所以在閉包不用之后,將不使用的局部變量刪除,使其被回收。在IE中可能導(dǎo)致內(nèi)存泄露,即無(wú)法回收駐留在內(nèi)存中的元素,這時(shí)候需要手動(dòng)釋放。
function a() { var i = 1; function b() { console.log(i++); } return b; } var c = a(); c(); //1 c(); //2 c(); //3 i不被回收 c = null; //i被回收
2.閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。如果你把父函數(shù)當(dāng)作對(duì)象使用,把閉包當(dāng)作它的公用方法,把內(nèi)部變量當(dāng)作它的私有屬性,要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
var Xzavier = { ten:10, addTen: function(num) { return this.ten + num; //給一個(gè)數(shù)加10 } } console.log(Xzavier.addTen(15)); //25 Xzavier.ten = 20; console.log(Xzavier.addTen(15)); //35內(nèi)存泄露
內(nèi)存泄漏指一塊被分配的內(nèi)存既不能使用,又不能回收,直到瀏覽器進(jìn)程結(jié)束。
出現(xiàn)原因:
1.循環(huán)引用:含有DOM對(duì)象的循環(huán)引用將導(dǎo)致大部分當(dāng)前主流瀏覽器內(nèi)存泄露。循環(huán) 引用,簡(jiǎn)單來(lái)說(shuō)假如a引用了b,b又引用了a,a和b就構(gòu)成了循環(huán)引用。 2.JS閉包:閉包,函數(shù)返回了內(nèi)部函數(shù)還可以繼續(xù)訪問(wèn)外部方法中定義的私有變量。 3.Dom泄露,當(dāng)原有的DOM被移除時(shí),子結(jié)點(diǎn)引用沒(méi)有被移除則無(wú)法回收。JavaScript垃圾回收機(jī)制
Javascript中,如果一個(gè)對(duì)象不再被引用,那么這個(gè)對(duì)象就會(huì)被GC(garbage collection)回收。如果兩個(gè)對(duì)象互相引用,而不再被第3者所引用,那么這兩個(gè)互相引用的對(duì)象也會(huì)被回收。垃圾回收不是時(shí)時(shí)的,因?yàn)槠溟_銷比較大,所以垃圾回收器會(huì)按照固定的時(shí)間間隔周期性的執(zhí)行。
函數(shù)a被b引用,b又被a外的c引用,這就是為什么函數(shù)a執(zhí)行后不會(huì)被回收的原因。
垃圾回收的兩個(gè)方法:
標(biāo)記清除法:
1.垃圾回收機(jī)制給存儲(chǔ)在內(nèi)存中的所有變量加上標(biāo)記,然后去掉環(huán)境中的變量以及被環(huán)境中變量所引用的變量(閉包)。 2.操作1之后內(nèi)存中仍存在標(biāo)記的變量就是要?jiǎng)h除的變量,垃圾回收機(jī)制將這些帶有標(biāo)記的變量回收。
引用計(jì)數(shù)法:
1.垃圾回收機(jī)制給一個(gè)變量一個(gè)引用次數(shù),當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型賦值給該變量的時(shí)候這個(gè)值的引用次數(shù)就加1。 2.當(dāng)該變量的值變成了另外一個(gè)值,則這個(gè)值得引用次數(shù)減1。 3.當(dāng)這個(gè)值的引用次數(shù)變?yōu)?的時(shí)候,說(shuō)明沒(méi)有變量在使用,垃圾回收機(jī)制會(huì)在運(yùn)行的時(shí)候清理掉引用次數(shù)為0的值占用的空間。閉包的應(yīng)用 1.維護(hù)函數(shù)內(nèi)的變量安全,避免全局變量的污染。
函數(shù)a中i只有函數(shù)b才能訪問(wèn),而無(wú)法通過(guò)其他途徑訪問(wèn)到。
function xzavier(){ var i = 1; i++; console.log(i); } xzavier(); //2 console.log(x); // x is not defined xzavier(); //22.維持一個(gè)變量不被回收。
由于閉包,函數(shù)a中i的一直存在于內(nèi)存中,因此每次執(zhí)行c(),都會(huì)給i自加1,且i不被垃圾回收機(jī)制回收。
function a() { var i = 1; function b() { console.log(i++); } return b; } var c = a(); c(); //1 c(); //2 c(); //33.通過(guò)第1點(diǎn)的特性設(shè)計(jì)私有的方法和屬性。
var xzavier = (function(){ var i = 1; var s = "xzavier"; function f(){ i++; console.log(i); } return { i:i, s:s, f:f } })(); xzavier.s; //"xzavier" xzavier.s; //1 xzavier.f() //24.操作DOM獲取目標(biāo)元素
方法2即使用了閉包的方法,當(dāng)然操作DOM還是有別的方法的,比如事件委托就比較好用。
ul id="test">
邏輯隨業(yè)務(wù)復(fù)雜而復(fù)雜O(∩_∩)O~
var Xzavier = function(){ var name = "xzavier"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }(); console.log(person.name); //undefined,變量作用域?yàn)楹瘮?shù)內(nèi)部,外部無(wú)法訪問(wèn) console.log(person.getName()); // "xzavier" person.setName("xz"); console.log(person.getName()); //"xz"6.實(shí)現(xiàn)類和繼承
function Xzavier(){ var name = "xzavier"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } } var xz = new Xzavier(); //Xzavier就是一個(gè)類,可以實(shí)例化 console.log(xz.getName()); // "xzavier"
這里是原型繼承,我會(huì)在下一篇文章講一講原型繼承。
var X = function(){}; X.prototype = new Xzavier(); X.prototype.sports = function(){ console.log("basketball"); }; var x = new X(); x.setName("xz"); x.sports(); //"basketball" console.log(x.getName()); //"xz"JavaScript作用域鏈
JavaScript作用域
作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。 在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
JavaScript作用域鏈
JavaScript函數(shù)對(duì)象擁有可以通過(guò)代碼訪問(wèn)的屬性和一系列僅供JavaScript引擎訪問(wèn)的內(nèi)部屬性。 其中一個(gè)內(nèi)部屬性是[[Scope]],該內(nèi)部屬性包含了函數(shù)被創(chuàng)建的作用域中對(duì)象的集合。 這個(gè)集合被稱為函數(shù)的作用域鏈。
執(zhí)行上下文
當(dāng)函數(shù)執(zhí)行時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行上下文(execution context),執(zhí)行上下文是一個(gè)內(nèi)部對(duì)象,定義了函數(shù)執(zhí)行時(shí)的環(huán)境。 每個(gè)執(zhí)行上下文都有自己的作用域鏈,用于標(biāo)識(shí)符解析。 當(dāng)執(zhí)行上下文被創(chuàng)建時(shí),而它的作用域鏈初始化為當(dāng)前運(yùn)行函數(shù)的[[Scope]]包含的對(duì)象。
活動(dòng)對(duì)象
這些值按照它們出現(xiàn)在函數(shù)中的順序被復(fù)制到執(zhí)行上下文的作用域鏈中。 它們共同組成了一個(gè)新的對(duì)象,活動(dòng)對(duì)象(activation object)。 該對(duì)象包含了函數(shù)的所有局部變量、命名參數(shù)、參數(shù)集合以及this,然后此對(duì)象會(huì)被推入作用域鏈的前端。 當(dāng)執(zhí)行上下文被銷毀,活動(dòng)對(duì)象也隨之銷毀。 活動(dòng)對(duì)象是一個(gè)擁有屬性的對(duì)象,但它不具有原型而且不能通過(guò)JavaScript代碼直接訪問(wèn)。
查找機(jī)制:
1.當(dāng)函數(shù)訪問(wèn)一個(gè)變量時(shí),先搜索自身的活動(dòng)對(duì)象,如果存在則返回,如果不存在將繼續(xù)搜索函數(shù)父函數(shù)的活動(dòng)對(duì)象,依次查找,直到找到為止。 2.如果函數(shù)存在prototype原型對(duì)象,則在查找完自身的活動(dòng)對(duì)象后先查找自身的原型對(duì)象,再繼續(xù)查找。 3.如果整個(gè)作用域鏈上都無(wú)法找到,則返回undefined。
在執(zhí)行上下文的作用域鏈中,標(biāo)識(shí)符所在的位置越深,讀寫速度就會(huì)越慢。全局變量總是存在于執(zhí)行上下文作用域鏈的最末端,因此在標(biāo)識(shí)符解析的時(shí)候,查找全局變量是最慢的。
so
在編寫代碼的時(shí)候應(yīng)盡量少使用全局變量,盡可能使用局部變量。 我們經(jīng)常使用局部變量先保存一個(gè)多次使用的需要跨作用取的值再使用。再析閉包
function a() { var i = 1; function b() { console.log(i++); } return b; } var c = a(); c(); 1.當(dāng)定義函數(shù)a,js解釋器將函數(shù)a的作用域鏈設(shè)置為定義a時(shí)a所在的環(huán)境。 2.執(zhí)行函數(shù)a的時(shí)候,a會(huì)進(jìn)入相應(yīng)的執(zhí)行上下文。 3.在創(chuàng)建執(zhí)行上下文的過(guò)程中,首先會(huì)為a添加一個(gè)scope屬性,即a的作用域,其值就為a的作用域鏈。 4.然后執(zhí)行上下文會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象。 5.創(chuàng)建完活動(dòng)對(duì)象后,把活動(dòng)對(duì)象添加到a的作用域鏈的最頂端。此時(shí)a的作用域鏈包含了兩個(gè)對(duì)象:a的活動(dòng)對(duì)象和window對(duì)象。 6.接著在活動(dòng)對(duì)象上添加一個(gè)arguments屬性,它保存著調(diào)用函數(shù)a時(shí)所傳遞的參數(shù)。 7.最后把所有函數(shù)a的形參和內(nèi)部的函數(shù)b的引用也添加到a的活動(dòng)對(duì)象上。 在這一步中,完成了函數(shù)b的的定義(如同a),函數(shù)b的作用域鏈被設(shè)置為b所被定義的環(huán)境,即a的作用域。 8.整個(gè)函數(shù)a從定義到執(zhí)行的步驟完成。
a返回函數(shù)b的引用給c,因?yàn)楹瘮?shù)b的作用域鏈包含了對(duì)函數(shù)a的活動(dòng)對(duì)象的引用,也就是說(shuō)b可以訪問(wèn)到a中定義的所有變量和函數(shù)。函數(shù)b被c引用,函數(shù)b又依賴函數(shù)a,因此函數(shù)a在返回后不會(huì)被GC回收,所以形成了閉包。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90921.html
一、我們先說(shuō)說(shuō)javascript的作用域 ?、偃肿兞?函數(shù)體外部進(jìn)行聲明 ?、诰植孔兞?函數(shù)體內(nèi)部進(jìn)行聲明 1)函數(shù)級(jí)作用域 javascript語(yǔ)言中局部變量不同于C#、Java等高級(jí)語(yǔ)言,在這些高級(jí)語(yǔ)言內(nèi)部,采用的塊級(jí)作用域中會(huì)聲明新的變量,這些變量不會(huì)影響到外部作用域。 而javascript則采用的是函數(shù)級(jí)作用域,也就是說(shuō)js創(chuàng)建作用域的單位是函數(shù)。 例如: 在C#當(dāng)中我...
摘要:內(nèi)存回收內(nèi)存泄漏前言最近在細(xì)讀高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書中很多地方一筆帶過(guò),所以用自己所理解的,嘗試細(xì)致解讀下。內(nèi)存回收在談內(nèi)存泄漏之前,首先,先了解下的內(nèi)存回收機(jī)制。 內(nèi)存回收 && 內(nèi)存泄漏 前言:最近在細(xì)讀Javascript高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書中很多地方一筆帶過(guò),所以用自己所理解的,嘗試細(xì)致解讀下。如有紕漏或錯(cuò)誤,會(huì)非常感謝您的指出。文中絕大部分內(nèi)容...
JavaScript在創(chuàng)建變量(數(shù)組、字符串、對(duì)象等)是自動(dòng)進(jìn)行了分配內(nèi)存,而且當(dāng)它沒(méi)有被使用的狀態(tài)下,會(huì)自動(dòng)的釋放分配的內(nèi)容;其實(shí)這樣基層語(yǔ)言,如C語(yǔ)言,他們提供了內(nèi)存管理的接口,比如malloc()用于分配所需的內(nèi)存空間、free()釋放之前所分配的內(nèi)存空間?! ♂尫艃?nèi)存的過(guò)程稱為垃圾回收,例如avaScript這類高級(jí)語(yǔ)言可以提供了內(nèi)存自動(dòng)分配和自動(dòng)回收,其實(shí)這個(gè)自動(dòng)儲(chǔ)存不會(huì)占用太多空間...
說(shuō)道JavaScript的代碼優(yōu)化,就先要做的是準(zhǔn)確的測(cè)試JavaScript的代碼執(zhí)行時(shí)間。簡(jiǎn)單來(lái)說(shuō)就是采集大量的執(zhí)行樣本進(jìn)行數(shù)學(xué)統(tǒng)計(jì)和分析,這里我們使用的是benchmark.js來(lái)檢測(cè)代碼的執(zhí)行情況?! ∈紫任覀冃枰陧?xiàng)目中安裝依賴,代碼如下: yarnaddbenchmark--save #或者 npmibenchmark--save 然后我們寫一個(gè)測(cè)試代碼,如下所示: ...
摘要:前端芝士樹中的閉包是怎么一回事筆試問(wèn)題集錦為什么會(huì)有閉包的出現(xiàn)這涉及到作為變量聲明的關(guān)鍵詞時(shí)所出現(xiàn)的一些問(wèn)題。另一方面,在函數(shù)外部自然無(wú)法讀取函數(shù)內(nèi)的局部變量。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。 【前端芝士樹】Js中的閉包是怎么一回事 && 筆試問(wèn)題集錦 為什么會(huì)有閉包的出現(xiàn)? 這涉及到var作為變量聲明的關(guān)鍵詞時(shí)所出現(xiàn)的一些問(wèn)題。比如,var 的 變量提升 以及...
閱讀 2490·2023-04-25 21:41
閱讀 1659·2021-09-22 15:17
閱讀 1931·2021-09-22 10:02
閱讀 2447·2021-09-10 11:21
閱讀 2586·2019-08-30 15:53
閱讀 1006·2019-08-30 15:44
閱讀 959·2019-08-30 13:46
閱讀 1149·2019-08-29 18:36