JS基礎知識回顧:變數、範圍和記憶體問題

來源:互聯網
上載者:User

標籤:java   ext   com   使用   資料   javascript   

ECMAScript變數可能包含兩種不同資料類型的值:基本類型值和參考型別值。

基本類型值指的是簡單的資料區段,而參考型別值指的是那些可能由多個值構成的對象。

 

參考型別的值是儲存在記憶體中的對象,與其他語言不同,JavaScript不允許直接存取記憶體中的位置,也就是說不能直接操作對象的記憶體空間。

在操作對象時,實際上是在操作對象的引用而不是實際的對象。

在很多語言中,字串以對象的形式來表示,因此被認為是參考型別的,ECMAScript放棄了這一傳統。

 

定義基本類型值和參考型別值的方式是類似的:建立一個變數並為該變數賦值。

但是,當這個值儲存到變數當中之後,對於不同類型的值可以執行的操作卻大相徑庭。

 

對於參考型別的值,我們可以為其添加屬性和方法,也可以改變和刪除其屬性和方法。

例如:var person=new Object();person.name="Name";alert(person.name);//"Name"(如果對象不被銷毀或者這個屬性不被刪除,則這個屬性將一直存在)

但是我們不能為基本類型的值添加屬性,儘管這樣做也不會導致任何錯誤。

例如:var name="Name";name.age=27;alert(name.age);//undefined

 

除了儲存的方式不同之外,在從一個變數向另一個變數複製基本類型值和參考型別值時,也存在不同。

如果從一個變數向另一個變數複製基本類型的值,會在變數對象上建立一個新值,然後把該值複製到新變數分配的位置上。

例如:var num1=5;var num2=num1;//儘管此時num1和num2中儲存的值都是5,但是兩個變數是完全獨立的,接下來可以參與任何操作而不互相影響

如果從一個變數向另一個變數複製參考型別的值,也會將儲存在變數中的值複製一份放到為新變數分配的空間中,只不過這個值的副本實際上是一個指標,複製結束後,兩個變數實際上將引用同一個對象,改變其中一個,就會影響到另外一個。

例如:var obj1=new Object();var obj2=obj1;obj1.name="Name";alert(obj2.name);//"Name"(因為二者指向同一個對象,所以改變一個會影響另外一個)

 

儘管ECMAScript中訪問對象有按值訪問和按引用訪問兩種,但是所有函數的參數都是按值傳遞的。

也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變數複製到另一個變數一樣。

在向參數傳遞基本類型的值時,被傳遞的值會被複製給一個局部變數(即具名引數,或者用ECMAScript的概念來說,就是arguments對象的一個元素)。

在向參數傳遞參考型別的值時,會把這個值在記憶體中的地址複製給一個局部變數,因此這個局部變數的變化會反應在函數的外部。

可以把ECMAScript函數的參數想象成局部變數,很多人錯誤的一位在局部範圍中修改的對象會在全域範圍中反映出來,就說明參數是按引用傳遞的,下面這個例子可以很好的證明這個想法是錯誤的:

function setName(obj){obj.name="Name";obj=new Object();obj.name="Greg";}

var person=new Object();setName(person);alert(person.name);//"Name"

即使在函數內部修改了參數的值,但原始的引用仍然保持不變。

實際上,在函數內部重寫obj時,這個變數引用的就是一個局部對象了,而這個局部對象會在函數執行完畢後立即被銷毀。

 

在檢測基礎資料型別 (Elementary Data Type)時typeof是非常得力的助手,但由於它只能檢測出對象而不能檢測出對象的類型,因此它在檢測參考型別的值是作用不大。

為此ECMAScript提供了instanceof操作符:result=variable instanceof constructor

如果變數是給定參考型別的執行個體,那麼instanceof操作符會返回true。

所有參考型別的值都是Object的執行個體,因此,在檢測一個參考型別值和Object建構函式時,instanceof操作符始終會返回true。

當然,如果使用instanceof操作符檢測基本類型的值,該操作符始終會返回false,因為基本類型不是對象。

 

執行環境(execution context)定義了變數或函數有權訪問的其他資料,決定了它們各自的行為。

每個執行環境都有一個與之相關的變數對象(variable object),環境中定義的所有變數和函數都儲存在這個對象當中。

執行環境分為全域(最外圍的執行環境)和局部(每個函數各自的執行環境)兩種。

當執行流進入一個函數時,函數的環境就會被推入一個環境棧當中,而在函數執行完成後,棧將其環境彈出,把控制權返回給之前的執行環境。

某個執行環境中的所有代碼被執行完成後,該環境被銷毀,儲存在其中的變數和函數定義也隨之銷毀。

當代碼在一個環境中執行時,會建立變數對象的範圍鏈,用來保證執行環境有權訪問的所有變數和函數的有序訪問。

內部環境可以通過範圍鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變數和函數。

這些環境之間的聯絡是線性、有次序的,每個環境都可以向上搜尋範圍鏈,以查詢變數和函數名,但任何環境都不能通過向下搜尋範圍鏈而進入另一個執行環境。

函數的參數也被當做變數來對待,因此其訪問規則與執行環境中的其他變數相同。

 

在執行流進入下列兩種語句中時,範圍鏈會得到加長:try-catch語句的catch塊,with語句。

因為這個種語句都會在範圍鏈的前端添加一個變數對象。

對於with語句來說,會將指定的對象添加到範圍鏈中。

對於catch語句來說,會建立一個新的變數對象,其中包含的是被拋出的錯誤對象的聲明。

在IE8及之前版本的JavaScript實現中,存在一個與標準不一致的地方,即在catch語句中捕獲的錯誤對象會被添加到執行環境的變數對象,而不是catch語句的變數對象中,所以在catch塊的外部也可以訪問到錯誤對象。IE9修複了這個問題。

 

JavaScript中沒有塊級範圍的概念,所以在迴圈語句中定義的變數在迴圈語句的塊級範圍結束之後並不會被銷毀,其中的變數可以在其所屬的函數的執行環境中被訪問。

使用var聲明的變數會自動被添加到最接近的環境中,在函數內部,最近接的環境就是函數的局部環境;

如果初始設定變數時沒有使用var聲明,該變數會自動被添加到全域環境,但由於這種做法可能導致意想不到的錯誤且為調試造成麻煩,所以並不推薦使用。

當在某個環境中讀取或寫入一個標識符時,必須通過搜尋來確定該標識符實際代表什麼。

搜尋過程由範圍鏈的前端開始,向上逐級查詢與給定名字匹配的標識符,如果在局部環境中找到了標識符,搜尋停止變數就緒,如果未找到則會一直向上追溯到全域環境的變數對象,如果在全域環境中仍未找到,則意味著該變數尚未聲明。

在這個搜尋過程中,如果存在一個局部的變數的定義,則搜尋會自動停止,不再進入另一個變數對象,所以,如果局部環境中存在著同名標識符,就不會使用位於父環境中的標識符。

變數查詢也是有代價的,訪問局部變數明顯要比訪問全域變數速度更快,因為不同向上搜尋範圍鏈,不過由於JavaScript引擎在查詢標識符方面一直在不斷的最佳化,這個差別在未來應該就可以忽略不計了。

 

JavaScript具有自動垃圾收集機制,執行環境會負責管理代碼執行過程中使用的記憶體。

具體到瀏覽器中的實現,則通常有標記清除(mark-and-sweep)和引用計數(reference counting)兩種策略。

標記清除是JavaScript中最常用的垃圾收集方式:當變數進入環境時,就將這個變數標記為”進入環境“,而當變數離開環境時,則將其標記為”離開環境“,最後垃圾收集器完成記憶體清除工作,銷毀那些帶有”離開環境“標記的值並收回它們所佔用的記憶體空間。

到2008年為止,IE、Firefox、Opera、Chrome、Safari的JavaScript實現使用的都是標記清除式的垃圾收集策略,只是垃圾收集的事件間隔互不相同。

另外一種不太常見的垃圾收集策略叫做引用計數:當聲明一個變數並講一個參考型別值賦給該變數時,這個值的引用次數被記做1,如果同一個值又被賦給其他變數,則該值的引用次數加1,相反,如果包含對這個值引用的變數又取得了另外一個值,那麼這個值的引用次數減1,當這個值的引用次數變為0時,則說明無法再訪問這個值了,此時就可以將其佔用的記憶體空間收回。

可是在出現循環參考的時候,這種垃圾收集機制顯然就會出現問題,Netscape3是最早引用計數策略的瀏覽器,不過由於這樣的問題它在4.0中放棄了該方式。

另外,IE中有一部分對象並不是原生JavaScript對象,而是用COM對象的形式實現的,而COM對象的垃圾收集機制採用的就是引入計數策略,所以只要IE涉及COM對象就會存在循環參考的問題,為了避免這樣的問題,最好是在不使用它們的時候手動斷開原生JavaScript對象與DOM元素之間的串連(將變數設定為null就意味著切斷變數與它們此前引用的值之間的串連)。

為瞭解決上述問題,IE9把BOM和DOM對象都轉換成了真正的JavaScript對象,這樣就避免了兩種垃圾收集策略並行導致的問題,也消除了常見的記憶體泄露現象。

 

垃圾收集器是周期性啟動並執行,而且如果為變數分配的記憶體數量很可觀,那麼回收工作量也是相當大的。

IE的垃圾收集器是根據記憶體配置量啟動並執行,這種實現方式的問題在於,如果一個指令碼包含一定個數的變數,那麼該指令碼很可能會在其生命週期中一直保有那麼多的變數,這樣一來就迫使垃圾收集器不得不頻繁的運行,由此引發的嚴重性能問題促使IE7重寫了其垃圾收集曆程。

事實上,在有的瀏覽器中可以觸發垃圾收集過程:IE中調用window.CollectGarbage()方法,Opera7及更高版本中調用window.opera.collect()方法,但是並不建議這樣做。

 

使用具備垃圾收集機制的語言編寫程式,一般不必擔心記憶體管理的問題,但是由於分配給WEB瀏覽器的可用記憶體數量通常要比分配給傳統型應用程式的少,所以對程式進行記憶體管理也是必不可少的。

而最佳化記憶體佔用的最佳方式,就是為執行中的代碼只儲存必要的資料,一旦資料不再有用,最好通過將其值設定為null來釋放其引用(解除引用 dereferencing),這一做法適用於大多數全域變數和全域對象的屬性。

不過解除引用並不意味著自動回收該值所佔的記憶體,而是讓值脫離執行環境以便垃圾收集器下次運行時將其回收。

其中分配給WEB瀏覽器的可用記憶體數量較少的原因是要處於安全考慮,防止運行JavaScript的網頁耗盡全部系統記憶體而導致系統崩潰。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.