摘要:回調傳遞函數是將函數當做值并作為參數傳遞給函數。這個例子中就是因為事件綁定機制中的傳入了回調函數,產生了閉包,引用著所在的作用域,所以此處的數據無法從內存中釋放。
javascript作用域
一門語言需要一套設計良好的規則來存儲變量,并且之后可以方便的找到這些變量,這逃規則被稱為作用域。
這也意味著當我們訪問一個變量的時候,決定這個變量能否訪問到的依據就是這個作用域。
一、詞法作用域作用域共有兩種主要的工作模型,第一種是最為普通的,被大多數編程語言(包括javascript)采用的詞法作用域,另一種叫做動態作用域。而我們平時所提及的作用域,就是這里所說的詞法作用域。
要了解詞法作用域,必須要了解javascript引擎以及編譯器的大概工作方式。一般程序中的源碼在執行前會進行編譯三步驟。
分詞/語法分析
解析/語法分析
代碼生成
而在分詞/詞法分析這個步驟,就已經確定了詞法作用域。也就說作用域在我們書寫代碼的時候就已經確定了,引用書中的文字
詞法作用域就是定義在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的。
具體結合編譯器、作用域、引擎來講,編譯器在分詞階段,針對特定的環境就會生成一個詞法作用域,然后對源代碼中的var a = 3;類似的聲明進行識別,當遇到var a,編譯器會詢問作用域中是否有a變量,若無,則在作用域中新增一個a變量。編譯完成之后,引擎執行編譯后的代碼,引擎在執行的過程中遇到a變量,會去作用域中查找是否有a變量,若有,則將a賦值2。對于var a = 2;一條語句會在兩個過程中操作,正是變量提升現象的原因。(稍后講到)
那什么時候會生成一個詞法作用域呢?
二、函數作用域這幅圖所展示的三個氣泡,就代表了三個作用域,而編譯器遇到一個函數定義,就會生成一個作用域。例如當編譯器遇到foo函數,會創建一個作用域,再將這個函數內部的標識符(a/b/bar)放到詞法作用域中。這個步驟在編譯階段就完成了。當js引擎執行foo函數的時候,遇到a變量,就會去詢問早就創建好的作用域是否有a變量存在。
在作用域外,是無法訪問作用域內的變量的。
例如
function foo() { var a = 3; } console.log(a); //undefied
正是這個特性,可以被用來實現隱藏內部變量
將重要變量聲明放入一個函數聲明的作用域中,可以防止被作用域外部的語句所引用甚至更改。
根據函數作用域,可以引申出如何判斷一個函數是函數聲明還是一個函數表達式。
最重要的區別是他們的名稱標識符將會綁定在何處。
先聲明一點,任何匿名函數都是可以添加名稱標識符的。例如
setTimeout(function timer() { console.log(1) }, 1000)
對于函數聲明,名稱標識符是綁定在當前作用域上的。即可在函數當前作用域調用這個名稱標識符。
而函數表達式,名稱標識符是綁定在自身的函數作用域中的。
按照這個區別,來看以下幾個函數。
function foo1() {console.log(1)} foo1(); // 1
var bar = function foo2() {console.log(1)} foo2() // undefined
(function foo3() {console.log(1)})() foo3() // undefined
以上的函數就只有foo1是函數聲明。
三、塊作用域在js語言中,除了函數,創建作用域的方式還可以通過塊作用域。對于js而言,循環、ifelse塊并沒有創建塊作用域的功能。
通過ES3規范的try/catch的catch語句可以創建一個塊作用域,其中聲明的變量僅在catch中有效。
而try-catch也正是let關鍵字的向前兼容方。
try { undefined(); // 執行一個非法操作來強制制造一個異常 } catch(err) { console.log(err); } console.log(err); // err not found
ES6引入了let關鍵字,提供了除var以外的另一種變量聲明方式,let為其聲明的變量隱式地劫持了所在的塊作用域。
if (true) { { let bar = 3; bar = someting(bar); console.log(bar) } } console.log(bar) // undefined
作于的一個中括號起到劃分塊作用域的作用,顯示的區別于var等變量。我們可能在之后會修改代碼,看到這個中括號會直白的認識到這個是一個塊作用域。
四、變量提升在第一節我已經提到了,對于var a = 3;這樣一條語句,編譯器通過分詞、解析、最后生成機器可以讀的代碼。
而javascript實際上會將其看成兩個聲明:var a、a = 3。第一個聲明在編譯階段進行,第二個賦值聲明會留在原地等待執行。
所以在引擎工作去執行代碼時,進入到函數作用域內時,首先會執行var a操作,而這個過程就好像變量從原先的位置被移動作用域最上面一樣。
console.log(a); // undefined var a = 3;
相當于
var a; console.log(a); // undefined a = 3;
另外函數聲明也會發生變量提升的現象(連實際函數值也提升,即可以在函數聲明前調用)。而行數表達式var a = function foo1() {}發生提升的是a變量,函數本身不會發生提升。
foo(); // 不是ReferenceError 而是 TypeError var foo = function bar() {}
ReferenceError TypeError
這是兩個錯誤標記,第一個錯誤標記是查詢變量時,若在作用域中查找不到這個變量則發出,第二個標記是能查找到變量(即使是endefined),但是這個變量被錯誤的調用(比如對null,undefined進行調用),發出。
閉包是基于詞法作用域書寫代碼時所產生的自然結果。
基于詞法作用域產生的結果,這有點類似于詞法作用域的產生條件。這也意味著閉包在書寫代碼的時候就已經形成了。
看一個最經典的閉包例子
function foo () { var a = 1; function bar () { console.log(a); //1 } return bar; } var baz = foo(); baz();
基于這個經典的例子,結合書中的話
一個函數在定義時的詞法作用域以外的地方被調用,可以記住并訪問原先所在的詞法作用域時,就產生了閉包。也即被返回出去的函數被調用時依然持有對該作用域的引用。這個引用就是閉包。
先確定一點,javascript中函數是可以作為值被傳遞的。基于這個特性,有多種方法可以行成閉包。只要在一個作用域中,將函數作為值傳遞到另一個詞法作用域中并調用,就會形成閉包。
function foo() { var a = 2; function baz() { console.log(a); } bar(baz); } function bar(fn) { fn(); } // 回調傳遞函數
var fn; function foo() { var a = 2; function baz() { console.log(a); } fn = baz; } function bar() { fn(); } foo(); bar(); //2 // 間接傳遞函數
無論通過何種手段將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。
二、回調 == 閉包再看上一節,回調中傳遞函數的例子。
function foo() { var a = 2; function baz() { console.log(a); } bar(baz); } function bar(fn) { fn(); } // 回調傳遞函數
是將函數當做值并作為參數傳遞給函數。再來看
function wait(message) { setTimeout(function timer () { console.log(message); // hello world }, 1000) } wait("hello world");
setTimeout作為js內置的工具函數,將timer 函數當做值傳進去,在setTimeout定義函數內對傳進來的timer進行了調用。類似于
function setTimeout(fn) { // 延遲多少毫秒 fn(); }
回調函數timer在另一個詞法作用域內調用,但是能訪問原先作用域內的參數(message)。
類似jquery中的事件綁定,涉及到傳遞回調函數,就都有閉包的產生!
三、閉包在循環中的表現最令人困惑的閉包表現就是在循環中了。像我們剛剛提及到的setTimeout、事件綁定等回調函數都會產生閉包。
for(var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i*1000) }
這個循環的本意是想間隔1秒打印1、2、3、4、5,結果卻每隔1秒輸出了5次6!
結合在第二節中對setTimeout函數的解析,這個誤區將很快解開。
首先要明白for循環沒有塊作用域的概念,即在這個循環中5次迭代都是在同一個作用域中進行的。
要清楚timer函數不是在這個作用域中被調用的,它作為參數在其他的作用域中調用。
function timer() { console.log(i); }
這個函數包括其中的形式參數i原原本本的被傳遞,在迭代過程中i不會被賦值。
而五次迭代完成后,共用的作用域中的i的值已經變成了6 。在其他作用域中的timer函數調用過程中需要查詢i,因為產生了閉包,i的值會去原始的作用域中查找,即全是6。
得不到預期效果的錯其實都在于for循環中共用一個作用域。想改進也很簡單,即在迭代的過程中,創建對應的作用域。另外值得注意的一點是需要把每次迭代的i值傳到作用域內。
for(var i = 1; i <= 5; i++) { (function (j) { setTimeout(function timer () { console.log(j) }, j* 1000) })(i) }四、閉包的垃圾回收
本來一個變量被使用完之后就可以利用垃圾回收機制進行垃圾回收,但因為閉包的產生,阻止了這一行為。
function process(data) { // } var someReallyBigData = {}; process( someReallyBigData ); var $btn = $(".j_Btn"); $btn.on("click", function clicker() {});
這個例子中就是因為事件綁定機制中的傳入了clicker回調函數,產生了閉包,引用著clicker所在的作用域,所以此處的someReallyBigData數據無法從內存中釋放。
解決辦法也有,聲明一個塊作用域,讓引擎清楚的知道沒有必要保存someReallyBigData餓了。
function process(data) { // } { let someReallyBigData = {}; process( someReallyBigData ); } var $btn = $(".j_Btn"); $btn.on("click", function clicker() {});
閱讀心得,轉載請注明出處。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78518.html
摘要:基本概念首先,函數不能存儲的值,指向哪里,取決于調用它的對象。如果沒有這個對象,那默認就是調用非嚴格模式下。也就是說是在運行的時候定義的,不是在綁定的時候定義的。 基本概念 首先,函數不能存儲this的值,this指向哪里,取決于調用它的對象。如果沒有這個對象,那默認就是window調用(非嚴格模式下)。也就是說this是在運行的時候定義的,不是在綁定的時候定義的。 funct...
摘要:在我們的程序中有很多變量標識符,我們現在或者將來將使用它。當我們使用時,如果并沒有找到這個變量,在非嚴格模式下,程序會默認幫我們在全局創建一個變量。詞法作用域也就是說,變量的作用域就是他聲明的時候的作用域。 作用域 定義 首先我們來想想作用域是用來干什么的。在我們的程序中有很多變量(標識符identifier),我們現在或者將來將使用它。那么多變量,我咋知道我有沒有聲明或者定義過他呢,...
摘要:為什么會存在跨域問題同源策略由于出于安全考慮,瀏覽器規定不能操作其他域下的頁面,不能接受其他域下的請求不只是,引用非同域下的字體文件,還有引用非同域下的圖片,也被同源策略所約束只要協議域名端口有一者不同,就被視為非同域。 showImg(https://segmentfault.com/img/remote/1460000017093859?w=1115&h=366); Why 為什么...
摘要:運行規則根據的運作原理,我們可以看到,的值和調用棧通過哪些函數的調用運行到調用當前函數的過程以及如何被調用有關。 1. this的誕生 假設我們有一個speak函數,通過this的運行機制,當使用不同的方法調用它時,我們可以靈活的輸出不同的name。 var me = {name: me}; function speak() { console.log(this.name); }...
摘要:一到底是一門什么樣的計算機編程語言表里不一表面上是動態解釋執行的腳本語言,實際上它是一門編譯語言。與眾不同與傳統語言不同的是,它不是提前編譯的,編譯記過也不能在分布式系統中進行移植。千篇一律引擎進行編譯的步驟和傳統的編譯語言非常相似。 一、JavaScript到底是一門什么樣的計算機編程語言? JavaScript表里不一:表面上是動態、解釋執行的腳本語言,實際上它是一門編譯語言。 ...
閱讀 1198·2021-11-10 11:35
閱讀 2925·2021-09-24 10:35
閱讀 2957·2021-09-22 15:38
閱讀 2807·2019-08-30 15:43
閱讀 1338·2019-08-29 18:39
閱讀 2557·2019-08-29 15:22
閱讀 2789·2019-08-28 18:17
閱讀 612·2019-08-26 13:37