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