摘要:閉包會在父函數外部,改變父函數內部變量的值。立即執行函數立即執行函數,顧名思義,立即會執行的函數,即當讀取到該函數,會立即執行。特性使用語句聲明一個變量,該變量的范圍限于聲明它的塊中。使用聲明的變量,在聲明前無法使用,否則將會導致錯誤。
let和閉包
之前一直模模糊糊記得,let解決了某個閉包問題,想用時又不敢肯定,今天終于遇到這個問題了,那我們就一起來分析一下,什么是let,let有什么作用,以及,他是如何解決閉包的,當然,也順便好好聊聊閉包。1、閉包 1.1 閉包的定義
閉包的定義是這樣的:內部函數被保存到了外部,即為閉包
先來看一個簡單的例子:
//我們聲明一個函數test(),這個函數返回了一個function function test(){ var i = 0; return function(){ console.log(i++) } }; //把test()的返回值賦給a和b變量,所以其實這時候的a/b=function(){console.log(i++);} var a = test(); var b = test(); //依次執行a,a,b,控制臺會輸出什么呢? a();a();b();
先思考一下,然后去瀏覽器驗證一下
答案是:0,1,0
這是因為,a/b=test()時,a/b各自保留了test的AO,所以各自上面均有一個i=0;
1.2 實現共有變量這樣其實是實現了一個共有變量,比如我們把上面的代碼稍稍調整一下,就實現了一個累加計數器;
//累加器 function add(){ var count = 0; function demo(){ count++; console.log(count); } return demo; } var counter = add(); counter();
這也是閉包的第一個功能,實現共有變量;
1.3 可以做緩存閉包的第二個功能是可以用作緩存,比如下面這個例子,我們用push把準備用到的東西放進去,當eat調用時使用:
//隱式緩存應用 function eater(){ var food = ""; var obj = { eat: function(){ console.log("I"m eating " + food); food = ""; }, push: function(myFood){ food = myFood; } } return obj; } var eater1 = eater(); eater1.push("banana"); eater1.eat();1.4 可以實現封裝,屬性私有化
這個典型的例子是圣杯繼承實現的雅虎的寫法
雅虎寫法 var inherit = (function(){ var F = function(){}; return function(Target,Origin){ F.prototype = Origin.prototype; Target.prototype = new F(); Target.prototype.constructor = Target; //超類 Target.prototype.uber = Origin.prototype; } }()) //理解,return時保留了F變量,閉包私有化變量1.5 模塊化開發,防止污染全局變量
利用閉包變量私有化,避免命名空間的問題 var name = "heh"; var init = (function(){ var name = "zhangsan"; function callName(){ console.log(name); } return function (){ callName(); } }()) init();1.6 閉包的危害
以上四點,其實都是閉包的好處,善加利用是能夠幫助到我們的,所以大家不要先入為主覺得閉包是不好的,閉包其實是我們解決很多問題的一種思路,但當然,閉包確實有它危害的方面:
閉包會導致原有作用域鏈不釋放,造成內存泄漏,即占用導致剩下內存變少
1.如何清除閉包:閉包函數=null; 如第一個例子:a = null;
否則閉包會一直占用內存,直到瀏覽器進程結束。
2.閉包會在父函數外部,改變父函數內部變量的值。如1.3的例子,我們修改了內部的food值,所以,對于閉包,一定要小心使用。
3.還有一個最常見的情況是for循環中的閉包:我們寫一個ul列表,當點擊時輸出對應的i;
這和我們之前事件委托的例子很像,但是這里我們輸出的不是對應的this對象,而是函數所在作用域的i值,可以看到,我們輸出的都是4,而我們的i應該是從0到1、2、3,加到4的時候已經不滿足條件了,不會進入循環。
這到底是怎么回事呢?
這同樣形成了一個閉包,內部的函數console.log(i)被保存到了外部的items[i].onclick()之中,所以我們有一個外部的AO,里面保存了一個i,但是這個i是for循環執行完之后的i,當我們執行點擊函數時,始終用到的就是這個i,但這明顯和我們要的不一樣,我們希望每一個執行點擊時輸出的都是for循環時對應的那個i;
這時候的閉包,是存在一定問題的,利用立即執行函數可以解決這個問題。
2、立即執行函數立即執行函數,顧名思義,立即會執行的函數(Immediately-Invoked Function Expression),即當js讀取到該函數,會立即執行。
我們1.4、1.5對應的例子中就用到了這個方法。
用法如下:
每次到了立即執行函數時,都會把當前的i賦值給index保存起來,并返回帶有這個值的函數。
2.1 立即執行函數的寫法官方的兩種寫法 (function (){}());//w3c建議第一種 (function (){})();2.2 立即執行函數用于初始化
var num = ( function (b) { var a = 123; console.log(a,b); d = a + b; return d; }(2) )2.3 常見寫法的注意事項
//1.只有表達式才能被執行符號執行,會忽略表達式的名字 //2.我們正常函數執行的寫法如下 function test(){ }; test(); // 但是直接在函數聲明后接執行符號,是不可以的,會報語法錯誤 function test(){ }(); //3.凡是能變成表達式就能被執行 var test = function () { }(); // 執行一次后被永久銷毀 // 表達式部分 = function(){ // }() //4.最先識別哪個括號 (--這種寫法最外面先 function test(){}() --); (--這種寫法最前面先 function test(){} --) (); //可以沒有test名稱 //5.當有參數時,不報錯,但也不執行 function test(a,b,c,d){ console.log(a+b+c+d); }(1,2,3,4); 實際分成了兩個部分 function test(a,b,c,d){ console.log(a+b+c+d);} 和 (1,2,3,4);//4,輸出個數2.4 作用
通過定義一個匿名函數,創建了一個新的函數作用域,相當于創建了一個“私有”的命名空間,該命名空間的變量和方法,不會破壞污染全局的命名空間。此時若是想訪問外部對象,將外部對象以參數形式傳進去即可。
3、let還是上面那個問題,我們看看下面的代碼
// let for (let i = 0; i < len; i++) { items[i].onclick = function () { console.log(i); } }
和我們第一個版本一樣,只是把var聲明的i換成了let的聲明方式,但是結果已經沒有任何問題了。
為什么僅僅改動了這一點,就解決了我們之前的問題呢?
其實這個問題的本質原因,還是var帶來的作用域的問題,接下來,我們來看一看,let,到底是什么?
let語句,聲明一個塊范圍變量。
let是ES6中新增關鍵字。它的作用類似于var,用來聲明變量,用法也類似,但是let是存在塊級作用域的。
let variable1 = value1;3.2 特性
1.使用 let 語句聲明一個變量,該變量的范圍限于聲明它的塊中。不能在外部訪問該變量,可以在聲明變量時為變量賦值,也可以稍后在腳本中給變量賦值。
var l = 10; {//注意這里僅僅是一個塊級作用域 let l = 2; console.log(l);// 這里 l = 2. let m = 4; console.log(m);// 這里 m = 4. } console.log(l);// 這里 l = 10. console.log(m);//報錯
相反,對于var,最小級別是函數作用域的。
所以在for循環中,var聲明的i是所在函數的作用域,而let則是for循環及循環體內的作用域,所以里面的語句可以訪問到對應的i。
2.使用 let 聲明的變量,在聲明前無法使用,否則將會導致錯誤。(不存在變量提升了)
console.log(index); let index;
3.如果未在 let 語句中初始化您的變量,則將自動為其分配 JavaScript 值 undefined。
let index; console.log(index);3.3 解讀for循環中的let
for (let i = 0; i < len; i++) { items[i].onclick = function () { console.log(i); } }
對于var來說,形成的閉包始終獲取到的都是循環完成后被改變的最終的i
而對于let,每一個i都是獨立在當前塊級作用域的,當前的i只在本輪循環有效,所以每一次循環的i其實都是一個新的變量。而JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。
其實,所謂的for循環帶來的閉包問題,其實就是變量作用域的問題,解決方式很多種,基本上可以用立即執行函數和let變量聲明來解決,其次,具體情具體分析。
推薦閱讀http://web.jobbole.com/82520/
https://www.sogou.com/link?ur...
http://hao.jser.com/archive/5...
https://msdn.microsoft.com/li...
http://www.jb51.net/article/2...
https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93491.html
摘要:進擊的巨人第三篇,本篇就作用域作用域鏈閉包等知識點,一一擊破。在此我們遵照的方式,暫且稱是閉包。所以,一名合格的前端,除了會用閉包,還要正確的解除閉包引用。 進擊的巨人第三篇,本篇就作用域、作用域鏈、閉包等知識點,一一擊破。 showImg(https://segmentfault.com/img/bVburWd?w=1280&h=854); 作用域 作用域:負責收集并維護由所有聲明的...
摘要:模塊化是隨著前端技術的發展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調也不等同于異步。將會討論安全的類型檢測惰性載入函數凍結對象定時器等話題。 Vue.js 前后端同構方案之準備篇——代碼優化 目前 Vue.js 的火爆不亞于當初的 React,本人對寫代碼有潔癖,代碼也是藝術。此篇是準備篇,工欲善其事,必先利其器。我們先在代...
摘要:插件開發前端掘金作者原文地址譯者插件是為應用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內優雅的實現文件分片斷點續傳。 Vue.js 插件開發 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應用添加全局功能的一種強大而且簡單的方式。插....
摘要:針對于面向對象編程的。因為面向對象就是針對對象例子中的守候來進行執行某些動作。這就是閉包的用途之一延續變量周期。把變量放在閉包里面和放在全局變量里面,影響是一致的。 1.前言 這段時間,金三銀四,很多人面試,很多人分享面試題。在前段時間,我也臨時擔任面試官,為了大概了解面試者的水平,我也寫了一份題目,面試了幾個前端開發者。在這段時間里面,我在學,在寫設計模式的一些知識,想不到的設計模式...
摘要:我的理解就是還處于被引用狀態。內存機制的內存空間分為棧堆其中棧存放變量,堆存放復雜對象。對堆內數據進行復制修改時理解閉包有了前面的鋪墊,我們再來看看閉包是怎么回事。這種反常的現象我們就叫它,中文名閉包。這就是閉包形成的原因了。 知識小儲備 ECMAScript 的數據有兩種類型:基本類型值和引用類型值,基本類型指的是簡單的數據段,引用類型指的是可能由多個值構成的對象。Undefined...
閱讀 8893·2021-11-18 10:02
閱讀 2578·2019-08-30 15:43
閱讀 2652·2019-08-30 13:50
閱讀 1364·2019-08-30 11:20
閱讀 2702·2019-08-29 15:03
閱讀 3624·2019-08-29 12:36
閱讀 927·2019-08-23 17:04
閱讀 614·2019-08-23 14:18