1. 程式人生 > >JavaScript高級程序設計學習(三)之變量、作用域和內存問題

JavaScript高級程序設計學習(三)之變量、作用域和內存問題

則表達式 基本數據類型 處理 文章 fir 數據類型 進入 AR 特點

這次講的主要是變量,作用域和內存問題。

任何一門編程語言,都涉及這三個。

變量,比如全局變量,局部變量等,作用域,也分全局作用域和方法作用域,內存問題,在java中就涉及到一個垃圾回收的問題,由於java中涉及到jvm,因此可以自動垃圾回收和內存分配,而不需要手動。

一、變量

每個變量都有其類型,數據類型。在java中分基本數據類型和引用數據類型,js同樣如此。

面試題:java的基本數據類型有哪些,及其所占字節?引用類型有哪些?

java基本數據類型分別為int(4),float(4),double(8),char(2),boolean(1),long(8),byte(2),short(2)

引用類型例如String或對象,枚舉等,當然也可以回答說除了上述的基本數據類型外,都是引用數據類型。

題外分享到此為止,下面進入正題:

(1)js的基本數據類型有哪些?

昨天的基本概念就說了,不過今天是系統的說,

基本數據類型分別為String,Number,null,Boolean,undefined

引用數據類型分別為Object,Array等

基本類型值指的是 簡單的數據段,而引用類型值指那些可能由多個值構成的對象。 在將一個值賦給變量時,解析器必須確定這個值是基本類型值還是引用類型。

引用類型的值是保存在內存中的對象。與其他語言不同,JavaScript不允許直接訪問內存中的位置, 也就是說不能直接操作對象的內存空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。 為此,引用類型的值是按引用訪問的。

<html>
<head>
<script>
var person  = new Object()
person.name="hello";
alert(person.name)
</script>
</head>
<body>
</body>
</html>

以上代碼創建了一個對象,並將其保存在變量person中,然而我們為該對象添加一個name屬性,並將字符串"hello"賦給了該屬性。緊接著,我們通過alert函數訪問該屬性,如果該屬性不被銷毀或不被刪除,將會一直存在。

註意,不能給基本數據類型添加屬性,盡管這樣不會導致報錯可以正常跑起來,但是下圖所示:

<html>
<head>
<script>
var person  = "hello";
person.name="world";
alert(person.name) //會導致值為undefined
</script>
</head>
<body>
</body>
</html>

技術分享圖片

(2)關於復制變量值

var num1=5;
var num2=num1;

雖然這兩個變量值是相同的,但是卻是完全獨立的兩個變量。

當從一個變量向另一個變量復制引用類型的值時,同樣也會將存儲在變量對象中的值復制一份放到 為新變量分配的空間中。不同的是,這個值的副本實際上是一個指針,而這個指針指向存儲在堆中的一 個對象。復制操作結束後,兩個變量實際上將引用同一個對象。因此,改變其中一個變量,就會影響另 一個變量。

下圖所示代碼:

<html>
<head>
<script>

var obj1 = new Object();
obj1.name="hello";
var obj2 = obj1;
alert(obj2.name)

</script>
</head>
<body>
</body>
</html>

(3)傳遞參數

<html>
<head>
<script>

function add(num){

num+=10;
return num;

}


var count=20;
var result=add(count);
alert(count)
alert(result)


</script>
</head>
<body>
</body>
</html>

這裏的函數 add()有一個參數 num,而參數實際上是函數的局部變量。在調用這個函數時,變 量count作為參數被傳遞給函數,這個變量的值是20。於是,數值20被復制給參數num以便在add() 中使用。在函數內部,參數 num 的值被加上了 10,但這一變化不會影響函數外部的 count 變量。參數 num 與變量 count 互不相識,它們僅僅是具有相同的值。假如 num 是按引用傳遞的話,那麽變量 count 的值也將變成 30,從而反映函數內部的修改。當然,使用數值等基本類型值來說明按值傳遞參數比較簡 單,但如果使用對象,那問題就不怎麽好理解了

(4)檢測類型

檢測變量的數據類型,typeof操作符最有效,但是只是針對基本數據類型,不能針對引用數據類型。

針對引用數據類型可以使用instanceof操作符

 alert(person instanceof Object); // 變量 person 是 Object 嗎?
 alert(colors instanceof Array);  // 變量 colors 是 Array 嗎?
 alert(pattern instanceof RegExp);  // 變量 pattern 是 RegExp 嗎? 

註意:

使用 typeof 操作符檢測函數時,該操作符會返回"function"。在 Safari 5及 之前版本和 Chrome 7及之前版本中使用 typeof 檢測正則表達式時,由於規範的原 因,這個操作符也返回"function"。ECMA-262規定任何在內部實現[[Call]]方法 的對象都應該在應用 typeof 操作符時返回"function"。由於上述瀏覽器中的正則 表達式也實現了這個方法,因此對正則表達式應用 typeof 會返回"function"。在 IE和 Firefox中,對正則表達式應用 typeof 會返回"object"。

二、執行環境和作用域

執行環境(execution context,為簡單起見,有時也稱為“環境”)是 JavaScript中最為重要的一個概 念。執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個 與之關聯的變量對象(variable object),環境中定義的所有變量和函數都保存在這個對象中。雖然我們 編寫的代碼無法訪問這個對象,但解析器在處理數據時會在後臺使用它。 全局執行環境是最外圍的一個執行環境。根據 ECMAScript實現所在的宿主環境不同,表示執行環 境的對象也不一樣。在 Web 瀏覽器中,全局執行環境被認為是 window 對象,因 此所有全局變量和函數都是作為 window 對象的屬性和方法創建的。某個執行環境中的所有代碼執行完 畢後,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀(全局執行環境直到應用程序退 出——例如關閉網頁或瀏覽器——時才會被銷毀)。 每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。 而在函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。ECMAScript 程序中的執行流 正是由這個方便的機制控制著。 當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途,是 保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所 在環境的變量對象。如果這個環境是函數,則將其活動對象(activation object)作為變量對象。活動對 象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。作用域鏈中 的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延 續到全局執行環境;全局執行環境的變量對象始終都是作用域鏈中的最後一個對象。 標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始, 然後逐級地向後回溯,直至找到標識符為止(如果找不到標識符,通常會導致錯誤發生)。

示例代碼:

<html>
<head>
<script>

var color = "blue"; 
 
function changeColor(){    
 if (color === "blue"){   
    color = "red";  
 } else {       
 color = "blue";   
 } 
 
 } 
 
changeColor(); 
 
alert("Color is now " + color); 

</script>
</head>
<body>
</body>
</html>

以下代碼共涉及 3 個執行環境:全局環境、changeColor()的局部環境和 swapColors()的局部 環境。全局環境中有一個變量 color 和一個函數 changeColor()。changeColor()的局部環境中有 一個名為 anotherColor 的變量和一個名為 swapColors()的函數,但它也可以訪問全局環境中的變 量 color。swapColors()的局部環境中有一個變量 tempColor,該變量只能在這個環境中訪問到。 無論全局環境還是 changeColor()的局部環境都無權訪問 tempColor。然而,在 swapColors()內部 則可以訪問其他兩個環境中的所有變量,因為那兩個環境是它的父執行環境。

示例:

<html>
<meta charset="utf-8">
<head>
<script>

var color = "blue"; 
 
function changeColor(){  
   var anotherColor = "red"; 
 
    function swapColors(){     
        var tempColor = anotherColor;      
        anotherColor = color;        
        color = tempColor;            // 這裏可以訪問 color、anotherColor 和 tempColor   
        } 
 
    // 這裏可以訪問 color 和 anotherColor,但不能訪問 tempColor       
    swapColors();
    } 
 
// 這裏只能訪問 color 
changeColor(); 
</script>
</head>
<body>
</body>
</html>

三、垃圾收集器

JavaScript 具有自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程中使用的內存。 而在 C和 C++之類的語言中,開發人員的一項基本任務就是手工跟蹤內存的使用情況,這是造成許多問 題的一個根源。在編寫 JavaScript 程序時,開發人員不用再關心內存使用問題,所需內存的分配以及無 用內存的回收完全實現了自動管理。這種垃圾收集機制的原理其實很簡單:找出那些不再繼續使用的變 量,然後釋放其占用的內存。為此,垃圾收集器會按照固定的時間間隔(或代碼執行中預定的收集時間), 周期性地執行這一操作。 下面我們來分析一下函數中局部變量的正常生命周期。局部變量只在函數執行的過程中存在。而在 這個過程中,會為局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。然後在函數中使 用這些變量,直至函數執行結束。此時,局部變量就沒有存在的必要了,因此可以釋放它們的內存以供 將來使用。在這種情況下,很容易判斷變量是否還有存在的必要;但並非所有情況下都這麽容易就能得 出結論。垃圾收集器必須跟蹤哪個變量有用哪個變量沒用,對於不再有用的變量打上標記,以備將來收 回其占用的內存。用於標識無用變量的策略可能會因實現而異,但具體到瀏覽器中的實現,則通常有兩 個策略。

(1) 標記清除

JavaScript 中最常用的垃圾收集方式是標記清除(mark-and-sweep)。當變量進入環境(例如,在函 數中聲明一個變量)時,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變
量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其 標記為“離開環境”。 可以使用任何方式來標記變量。比如,可以通過翻轉某個特殊的位來記錄一個變量何時進入環境, 或者使用一個“進入環境的”變量列表及一個“離開環境的”變量列表來跟蹤哪個變量發生了變化。說 到底,如何標記變量其實並不重要,關鍵在於采取什麽策略。 垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方 式)。然後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此之後再被加上標記 的變量將被視為準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最後,垃圾收集器 完成內存清除工作,銷毀那些帶標記的值並回收它們所占用的內存空間。

(2)引用計數

另一種不太常見的垃圾收集策略叫做引用計數(reference counting)。引用計數的含義是跟蹤記錄每 個值被引用的次數。當聲明了一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是 1。 如果同一個值又被賦給另一個變量,則該值的引用次數加 1。相反,如果包含對這個值引用的變量又取 得了另外一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0時,則說明沒有辦法再訪問這 個值了,因而就可以將其占用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那 些引用次數為零的值所占用的內存。

小結:

聲明:該篇文章有部分概念相關的,我引用了《JavaScript高級程序設計》這本書,因為既然是系統學習,少不了要引用一些概念。概念雖說枯燥無味,但是不知道點也還是不行的。

個人體會:作為一名web開發人員,寫前臺也寫後臺,前臺的話,除了變量的作用域和函數執行順序或者參數傳遞這些用的比較多,同時也考慮的比較多,像內存管理這個,接觸很少。垃圾回收,後臺java有自己的垃圾回收機制,不用我們java開發人員考慮,js的內存管理,在我看來我真不知道哪裏用到了,對於js的垃圾回收和內存管理這方面知識我幾乎不知道有這麽個玩意,讀了這本書,才知道原來js同java一樣也有自己的垃圾回收機制,不需要我們使用人員考慮太多。這次系統學習也算了解了下。

我個人在後續開發中,js性能方面也是特別關註的,為此我參考了許多csdn或者博客園及其相關書籍對網站進行性能優化,特別是關於書籍,我個人上傳了一個文件夾,裏面包含網站性能優化的十四條建議。大家可以參考,雖然說不是那麽仔細,但是我想應該比較全面。該十四條建議,是參讀了《高性能網站建設指南》這本書的。這本書web開發者,我覺得還是有必要讀讀的。

本篇知識小結,可分為如下:

JavaScript變量可以用來保存兩種類型的值:基本類型值和引用類型值。

基本類型的值源自以下 5 種基本數據類型:Undefined、Null、Boolean、Number 和 String。

基本類型值和引用類型值具 有以下特點:

? 基本類型值在內存中占據固定大小的空間,因此被保存在棧內存中;

? 從一個變量向另一個變量復制基本類型的值,會創建這個值的一個副本;

? 引用類型的值是對象,保存在堆內存中;

? 包含引用類型值的變量實際上包含的並不是對象本身,而是一個指向該對象的指針;

? 從一個變量向另一個變量復制引用類型的值,復制的其實是指針,因此兩個變量最終都指向同 一個對象;

? 確定一個值是哪種基本類型可以使用 typeof 操作符,而確定一個值是哪種引用類型可以使用 instanceof 操作符;

所有變量(包括基本類型和引用類型)都存在於一個執行環境(也稱為作用域)當中,這個執 行環境決定了變量的生命周期,以及哪一部分代碼可以訪問其中的變量。以下是關於執行環境的幾 點總結:

? 執行環境有全局執行環境(也稱為全局環境)和函數執行環境之分;

? 每次進入一個新執行環境,都會創建一個用於搜索變量和函數的作用域鏈;

? 函數的局部環境不僅有權訪問函數作用域中的變量,而且有權訪問其包含(父)環境,乃至全 局環境;

? 全局環境只能訪問在全局環境中定義的變量和函數,而不能直接訪問局部環境中的任何數據;

? 變量的執行環境有助於確定應該何時釋放內存。

JavaScript 是一門具有自動垃圾收集機制的編程語言,開發人員不必關心內存分配和回收問題。可 以對 JavaScript的垃圾收集例程作如下總結。

? 離開作用域的值將被自動標記為可以回收,因此將在垃圾收集期間被刪除。

? “標記清除”是目前主流的垃圾收集算法,這種算法的思想是給當前不使用的值加上標記,然 後再回收其內存。

? 另一種垃圾收集算法是“引用計數”,這種算法的思想是跟蹤記錄所有值被引用的次數。JavaScript 引擎目前都不再使用這種算法;但在 IE中訪問非原生 JavaScript對象(如 DOM元素)時,這種 算法仍然可能會導致問題。

? 當代碼中存在循環引用現象時,“引用計數”算法就會導致問題。

? 解除變量的引用不僅有助於消除循環引用現象,而且對垃圾收集也有好處。為了確保有效地回 收內存,應該及時解除不再使用的全局對象、全局對象屬性以及循環引用變量的引用。

JavaScript高級程序設計學習(三)之變量、作用域和內存問題