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