1. 程式人生 > >讀書筆記4:變數、作用域和記憶體問題

讀書筆記4:變數、作用域和記憶體問題

這裡寫圖片描述

基本型別和引用型別的值

(1)概念

基本型別的值指的是簡單的資料段,而引用型別的值指那些可能由多個值構成的物件。 引用型別的值是儲存在記憶體中的物件。JavaScript 不允許直接操作物件的記憶體空間,實際上操作的是物件的引用,而不是實際的物件。引用型別的值是按引用訪問的。 兩種型別的值得在記憶體中的位置是不一樣的,基本型別值在記憶體中佔據固定大小的空間,被儲存在棧記憶體中;引用型別的值時物件,儲存在堆記憶體中。 包含引用型別值得變數實際上包含的並不是物件本身,而是一個指向該物件的指標,也可以理解為該物件在堆記憶體中的地址,而這個變數是儲存在棧記憶體中的。 這裡寫圖片描述 因此,複製引用型別的值時,實際上覆制的是指向儲存在堆中的物件的指標。它們會指向同一個物件,改變其中一個變數會影響到另一個變數。

(2)函式傳遞引數

ECMAScript 中所有函式的引數都是按值傳遞的。

關於按值和按引用傳遞:

  • 按值傳遞(call by value):函式的形參是被呼叫時所傳實參的副本,修改形參的值並不會影響實參,是最常用的求值策略。
  • 按引用傳遞(call by reference)時,函式的形參接收實參的隱式引用,而不再是副本。這意味著函式形參的值如果被修改,實參也會被修改。同時兩者指向相同的值。
  • 按值傳遞由於每次都需要克隆副本,對一些複雜型別,效能較低;按引用傳遞會使函式呼叫的追蹤更加困難,有時也會引起一些微妙的BUG。

JS中,函式引數按值傳遞有什麼特點?

  • 傳遞基本型別的值時,被傳遞的值會被複制給一個區域性變數(即命名引數,或 arguments
    物件的一個元素);
  • 傳遞引用型別的值時,會把這個值在記憶體中的地址(指標)複製給一個區域性變數,因此這個區域性變數的變化會反映在函式的外部。

這部分例子可看大神部落格,其中提到按共享傳遞 call by sharing的思想

例1:向函式引數傳遞引用型別值

var foo = {name:'foo'};
function test(obj){
  obj.name = 'test'; //第一步操作
  obj = {name:'bar'};  //第二步操作
};
test(foo);
console.log(foo); //結果:Object {name: "test"}

首先,這裡 foo

儲存的是物件的指標,不是物件本身,按值傳遞給引數 obj 的也就是這個指標,稱為物件1; 第一步操作,obj 指向的物件仍為物件1,此時修改了物件1的 name 屬性; 第二步操作,給 obj 賦了新值,指向了新的物件,稱為物件2;相當於執行了obj = new Object(); obj = {name:'bar'}; 這裡 obj 獲得一個全新的物件。 在全域性環境下,變數 foo 沒有重新賦值,一直是指向物件1; 所以改變函式內部變數 obj 的值,不會影響外部環境中引數原值 foo這裡寫圖片描述

(3)檢測型別

  • 使用 instanceof 操作符判斷一個例項是否屬於某種型別;
  • instanceof 可以在繼承關係中用來判斷一個例項是否屬於它的父型別,在多層繼承關係中,instanceof 運算子同樣適用;
  • 所有引用型別的值都是 Object 的例項,檢測一個引用型別值和 Object 建構函式時,instanceof 操作符始終返回 true

要深入理解 instanceof 操作符,會涉及到原型繼承機制,後續章節中會提及。此部落格中有一些例子。

執行環境和作用域

(1)概念理清

  • 執行環境定義了變數或函式有權訪問的其他資料,決定了它們各自的行為,有時也被叫做執行上下文。某個執行環境中的所有程式碼執行完畢後,該環境被銷燬,儲存在其中的所有變數和函式也隨之銷燬。執行環境有全域性執行環境函式執行環境之分。
  • 變數物件:環境中定義的所有變數和函式都儲存在這個物件中。
  • 宿主環境:宿主環境一般由外殼程式建立和維護,可以為JavaScript等多種指令碼語言提供服務。宿主環境一般會建立一套公共物件系統,這套物件系統對所有指令碼語言開放,並允許它們自由訪問。同時,宿主環境還會提供公共介面,用來裝載不同的指令碼語言引擎。這樣我們可以在同一個宿主環境中裝載不同的指令碼引擎,並允許它們共享宿主物件。
  • 全域性執行環境:全域性執行環境是最外圍的一個執行環境。在 Web 瀏覽器中,全域性執行環境被認為是 window 物件,因此所有全域性變數和函式都是作為window物件的屬性和方法建立的。程式碼載入瀏覽器時,全域性執行環境被建立;當我們關閉網頁或者瀏覽器時全域性執行環境才被銷燬。
  • 作用域鏈:作用域鏈的用途,是保證對執行環境有權訪問的所有變數和函式的有序訪問。程式碼在一個環境中執行時,會建立變數物件的一個作用域鏈。作用域鏈的前端,始終都是當前執行的程式碼所在環境的變數物件。內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變數和函式。
  • 活動物件:此概念針對函式。在一個函式物件被呼叫的時候,會建立一個活動物件,首先將該函式的每個形參和實參,都新增為該活動物件的屬性和值;將該函式體內顯示宣告的變數和函式,也新增為該活動的的屬性。這個活動物件作為該函式執行環境的作用域鏈的最前端,並將這個函式物件的[[scope]]屬性裡作用域連結入到該函式執行環境作用域鏈的後端。
  • 函式物件:在一個函式定義的時候, 會建立一個這個函式物件的 [[scope]] 屬性,並將這個 [[scope]] 屬性指向定義它的作用域鏈上。

(2)延長作用域鏈

  • 在作用域鏈前端臨時新增一個變數物件,該變數物件會在程式碼執行後被移除。
  • try-catch 語句的 catch 模組:當try程式碼塊中發生錯誤時,執行過程會跳轉到 catch 語句,然後異常物件推入一個可變物件並置於作用域的頭部;在 catch 程式碼塊內部,函式的所有區域性變數將會被放在第二個作用域鏈物件中。
  • with 語句with 語句是物件的快捷應用方式,用來避免書寫重複程式碼。width 語句看上去更高效,實際上產生了效能問題,一般不建議使用。

(3)塊級作用域

塊級作用域和函式作用域 塊級作用域:變數在離開定義的塊級程式碼後立即被回收; 函式作用域:變數在定義的環境中以及巢狀的子函式中處處可見;

ES5 及以前版本 JavaScript 都沒有塊級作用域,只有兩種:函式作用域和全域性作用域。使用 var 宣告的變數會自動被新增到最接近的環境中。 直到 ES6 中定義了塊級作用域,使用 let / const宣告的變數只能在塊級作用域裡訪問。

let 宣告的特點:

  • let 所宣告的變數,只在let命令所在的程式碼塊內有效;
  • 不存在變數提升,變數一定要在聲明後使用,否則報錯;
  • 只要塊級作用域記憶體在let命令,它所宣告的變數就“繫結”(binding)這個區域,不再受外部的影響。在程式碼塊內,使用 let 命令宣告變數之前,該變數都是不可用的,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ);
  • 由於存在“暫時性死區”,在let命令宣告變數之前,typeof 操作符檢查此變數也會報錯;
  • let 不允許在相同作用域內,重複宣告同一個變數。

以上,就是《Javascript高階程式設計》第四章的學習筆記。