【閱讀筆記】JavaScript 高階程式設計(四)
基本型別和引用型別的值
ECMAScript 變數可能包含兩種不同資料型別的值:基本型別值和引用型別值。
基本型別值是指簡單的資料段,而引用型別值指那些可能由多個值構成的物件。
JavaScript 的基本資料型別都是按值訪問的,因為可以操作儲存在變數中的實際的值。
引用型別的值是儲存在記憶體中的物件。 JavaScript 不允許直接操作物件的記憶體空間。在操作物件時,實際上是在操作物件的引用而不是實際的物件。因此,引用型別的值時按引用訪問的。
動態的屬性
定義基本型別值和引用型別值方式是類似的:建立一個變數併為該變數賦值。
對於引用型別的值,是可以為其新增屬性和方法,當然也可以改變和刪除其屬性和方法
var person = new Object();
person.name = 'Tom';
person.sayHello = function(){
alert(`Hello ! My name's ${this.name}`);
}
複製變數值
如果從一個變數向另一個變數複製基本型別的值,會在變數物件上建立一個新值,然後把該值複製到為新變數分配的位置上,此時兩個變數完全獨立,它們可以參與任何操作而不會相互影響,如下圖:
當從一個變數向另一個變數複製引用型別的值時,同樣也會將儲存在變數物件中的值複製一份放到為新變數分配的空間中。不同的是,這個值的副本實際上是一個指標,而這個指標指向儲存在堆中的一個物件。兩個變數實際上將引用同一個物件。因此,改變其中一個變數,就會影響另一個變數
傳遞引數
ECMAScript 中所有函式的引數都是按值傳遞的。
在向引數傳遞基本型別的值時,被傳遞的值會被複制給一個區域性變數(即命名引數,或是 ECMAScript 中的 arguments
物件中的一個元素)。
在向引數傳遞引用型別的值時,會把這個值的記憶體地址複製給一個區域性變數,因此這個區域性變數的變化會反應在函式的外部。
檢測型別
在檢測一個引用型別值和 object
建構函式時,instanceof
操作符始終會返回 true
,如果檢測基本型別的值時,則始終返回 false
,因為基本型別不是物件。
執行環境及作用域
執行環境
- 全域性執行環境:是最外圍的一個執行環境,根據 ECMAScript 實現所在的宿主環境不同,表示執行環境的物件也不一樣。
在 Web 瀏覽器中,全域性環境被認為是 window 物件,因此所有全域性變數和函式都是作為 window 物件的屬性和方法建立的。
- 函式執行環境:當執行流進入一個函式時,函式的環境變數就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。
某個執行環境中的所有程式碼執行完畢後,該環境被銷燬,儲存在其中的所有變數和函式定義也隨之銷燬(全域性執行環境直到應用程式退出時才被銷燬)。
每個執行環境都有一個與之關聯的變數物件(variable object),執行環境中定義的所有變數和函式都儲存在這個物件中,除了我們無法對其訪問外,它與普通物件並無差別。
當代碼在一個執行環境中執行時,會建立變數物件的作用域鏈(scope chain),它是用於保證對執行環境有權訪問的所有變數和函式的有序訪問。
作用域鏈(scope chain)是一個由變數物件帶頭組成的節點單向連結串列,如果當前執行環境為函式,則將其活動物件(activation object)作為變數物件,活動物件在最開始只包含一個變數,即 argments 物件(這個物件在全域性環境作用域中是不存在的)
作用域鏈的下一個變數物件來自包含(外部)環境。以此類推,直到全域性執行環境。即全域性執行環境的變數物件始終是作用域鏈的最後一個物件。
延長作用域鏈
我們可以通過 try-catch
語句的 catch
塊或 with
語句去延長作用域鏈。它們會在作用域鏈的前端增加一個變數物件,該變數物件會在程式碼執行後被移除。
with
來說將指定物件新增到作用域鏈中。
catch
會建立一個新變數物件,其中包含的是被丟擲的錯誤物件宣告。
沒有塊級作用域
JavaScript 中是沒有塊級作用域。
當使用 var
宣告變數時,該變數會自動新增到所在作用域的變數物件中。
當省略關鍵字 var
宣告變數,如下:
function foo(a,b){
sum = a + b;
}
foo(10,20);
console.log(sum); // 30
當函式 foo
執行完畢後,其內部未使用 var
光劍子宣告的 sum
變數會被新增至全域性變數中。在嚴格模式下,這樣宣告會報錯。
小結
JavaScript 變數可以用來儲存兩種型別的值:基本值和引用型別。基本型別的值源自於以下 5 個基本資料型別:Undefined
、Null
、Boolean
、Number
和String
。基本型別值和引用型別值具有以下特點:
- 基本資料值在記憶體中佔據固定大小的空間,因此被儲存在棧記憶體中;
- 從一個變數向另一個變數複製基本型別的值,會建立這個值的一個副本;
- 引用型別的值時物件,儲存在堆記憶體中;
- 從一個變數向另一個變數複製引用型別的值,複製的其實是指標,因此兩個變數最終都指向同一個物件;
- 確定一個值是哪種基本型別可以使用
typeof
操作符,而確定一個值是哪種引用型別可以使用instanceof
操作符。
所有變數都存在於一個執行環境(也稱作用域)當中,這個執行環境決定了變數的宣告週期,以及哪一部分程式碼可以訪問其中的變數:
- 執行環境有全域性執行環境和函式執行環境之分;
- 每次進入一個新執行環境,都會建立一個用於搜尋變數和函式的作用域鏈;
- 函式的區域性環境不僅有權訪問函式作用域中的變數,而且有權訪問其包含環境,乃至全域性環境;
- 全域性環境只能訪問在全域性環境中的變數和函式,而不能直接訪問區域性環境中的任何資料;
- 變數的執行環境有助於確定應該何時釋放記憶體。
JavaScript 是一門具有自動垃圾收集機制的程式語言。開發人員不必關心記憶體分配和回收問題:
- 離開作用域的值將被自動標記為可以回收,因此將在來及收集期間被刪除。
- “標記清除”是目前主流的垃圾收集演算法,這種演算法的思想是給當前不使用的值加上標記,然後再回收其記憶體。
- 另一種垃圾收集演算法是“引用計數”,這種演算法的思想是跟蹤記錄所有值被引用的次數。JavaScript 引擎目前都不再使用這種演算法;但在 IE 中訪問非原生 JavaScript 物件時,這種演算法仍然可能導致問題。
- 當代碼中存在迴圈引用現象時,“引用計數”演算法就會導致問題。
- 解除變數的引用不僅有助於消除迴圈引用現象,而且對垃圾收集也有好處。為了確保有效地回收記憶體,應該及時解除不再使用的全域性物件、全域性物件屬相以迴圈引用變數的引用。