1. 程式人生 > >《JavaScript高級程序設計》讀書筆記(四)變量、作用域和內存問題

《JavaScript高級程序設計》讀書筆記(四)變量、作用域和內存問題

att 數量 線程 添加屬性 限制 mil web 全局 正常

內容
---理解基本類型和引用類型的值
---理解執行環境
---理解垃圾收集


--JavaScript變量松散類型的本質
--決定了它只是在特定時間用於保存特定值的一個名字而已
--變量的值及其數據類型可以在腳本的生命周期內改變


基本類型和引用類型的值
--ECMAScript變量可能包含兩種不同數據類型的值
---基本類型值和引用類型值
----基本類型值指簡單的數據段(undefined、null、Boolean、Number、String)
----引用類型值值那些可能由多個值構成的對象(Object、Array)

--在將一個值賦給變量時,解析器必須確定這個值是那種類型
--基本類型按值訪問,可以操作保存在變量中的實際的值
--引用類型的值是保存在內存中的對象
---JavaScript不允許直接訪問內存中的位置,不能直接操作對象的內存空間
---在操作對象時,實際上是在操作對象的引用而不是實際的對象
---引用類型的值是按照引用訪問的

--在很多語言中,字符串以對象的形式表示,也被認為是引用類型
--ECMAScript放棄了這一個傳統


動態的屬性
--定義基本類型值和引用類型值
---創建一個變量並為該變量賦值
--對於引用類型的值,可以添加,修改,刪除其屬性和方法

var person = new Object();
person.name = "luking";
console.log(person.name); //"luking"

--如果對象不被銷毀或者這個屬性不被刪除,則這個屬性一直存在

--不能給基本類型添加屬性,盡管不會報錯


復制變量值
--復制基本類型的值:把該值的副本復制到新的變量中去

var num1 = 5;
var num2 = num1; num2; //5 與num1互相獨立

--引用類型值:也是復制該值的備份,只不過這個值實際上是一個指針

---這個指針指向內存在堆中的一個對象
---因此這兩個變量實際上引用的是同一個對象
---通過一個變量修改對象,另一個變量也會受影響

var obj1 = new Object();
var obj2 = obj1;
obj1.name = "luking";
console.log(obj2.name);  //luking

--因為兩個變量的值是一個指針,指向同一個對象

--所以操作的同一個對象,展示的也是同一個對象


傳遞參數
--ECMAScript中所有函數的參數都是按照值傳遞的
--即,把函數外部的值復制給函數內部的參數
--引用類型值也只是復制的其指針
--所以在函數內部也可以修改這個指針指向的對象
--但是當改變這個值時,不會對原來的引用類型變量產生影響

function f(o) {
    o.name = "luking";
    o = new Object();
    o.name = "alen";
}
var obj = new Object();
f(obj);
obj.name; //"luking"

--可以把ECMAScript函數的參數想想成為局部變量


檢查類型
--typeof 用於檢查是那種基本類型
---typeof null; //object

--instanceof 用於判斷是那種引用類型的對象

result = varible instanceof constructor;

--如果變量是給定引用類型(根據原型鏈識別)的實例返回true

person instanceof Object;     //變量person是不是Object類型
color instanceof Array;        //變量color是不是Array類型
pattern instanceof RegExp;    //變量pattern是不是RegExp類型

--所有引用類型的值都是Object的實例

--基本類型不是對象,返回false


執行環境及作用域
--執行環境定義了變量或者函數有權訪問的其他數據,決定了它們個自的行為
--每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。
--無法訪問,但是解析器在處理數據時會在後臺使用它

--全局執行環境是最外圍的一個執行環境
--根據ECMAScript實現所在的宿主環境不同,表示的執行環境的對象也不一樣
---在web瀏覽器中,全局執行環境被認為是window對象
---因此,所有全局變量和函數都是作為window對象的屬性和方法創建的
--某執行環境中的所有代碼執行完畢後,該環境被銷毀,保存其中的所以變量和函數定義也隨之銷毀
---全局執行環境知道應用成勛退出--例如關閉網頁或者瀏覽器時,才被銷毀

--每個函數都有自己的執行環境
--當執行流進入函數時,函數的環境會被推入一個環境棧中
--當函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境
--ECMAScript程序中的執行流正是由這個方便的機制控制著

--當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈
---作用域鏈,保證對執行環節有權訪問的所有變量和函數的有序訪問
--作用域鏈的前端,始終都是當前執行的代碼所在的環境的變量對象
--如果這個環境是函數,則將其活動對象作為變量對象
---活動對象最開始只包含一個變量,arguments對象(在全局環境中不存在)
--作用域鏈中的下一個變量對象來自包含(外部)環境
--再下一個變量對象則來自下一個包含環境
--一直延續到全局執行環境
--全局執行環境的變量對象始終都是作用域鏈的最後一個對象

--標識符解析是沿著作用域鏈一級一級地搜索標識符的過程
--搜索過程始終從作用域鏈的前端開始,逐漸地向後回溯
--直到找到標識符為止,如果找不到標識符,通常會導致錯誤發生

var color = "blue";
function changeColor() {
    if(color === "blue") {
        color = "red";
    } else {
        color = "blue";
    }
}
changeColor();
color;         //"red"

--函數changeColor()的作用域鏈包含兩個對象

--是它自己的變量對象(其中定義著arguments對象)和 全局執行環境
--可以在函數內部訪問變量color,就是因為可以在這個作用域鏈中找到它

--內部環境可以通過作用域鏈訪問所有的外部環境
--反之則不行
--這些環境之間的聯系是線性,有次序的
--每個環境都可以向上搜索作用域鏈,以查詢變量和函數名
--但是不能向下搜索進入另一個執行環境
--函數參數也被當作變量來對待,因此其訪問規則與執行環境中的其他變量相同


延長作用域鏈
--執行環境的類型只有兩種--全局和局部(函數)
--可以通過其他方法來延長作用域鏈
--也就是在作用域鏈的前端臨時增加一個變量對象
--該變量對象會在代碼執行後移除

--try-catch語句的catch塊
--with語句

--兩個語句都會在作用域鏈的前端添加一個變量對象
--with 會指定的對象添加到作用域鏈中
--catch會創建一個新的變量對象,其中包含被拋出的錯誤對象的聲明

function buildUrl() {
    var qs = "?debug=true"
    
    with(location) {
        var url = href + qs;
    }
    
    return url;
}

--location對象被添加到了作用域鏈的前端

--不是新的執行環境,因為沒有塊級作用域(執行環境)


沒有塊級作用域(執行環境)
--在其他類C語言中,由花括號封閉的代碼都有自己的作用域
---用ECMAScript的話來說,就是他們自己的執行環境
---所以支持根據條件來定義變量

if(rtue) {
    var color = "red"
}
color;        //"red"

--在for循環的初始化變量表達式中定義的變量

---也會存在於循環外部的執行環境中


聲明變量
--使用var聲明的變量會自動被添加到最接近的環境中
--在函數內部,就是函數的局部環境
--在with語句中,就是最接近的函數環境
--如果初始化變量時,沒有使用var聲明
---該變量會自動被添加到全局環境

--不建議,不聲明直接初始化變量
--在嚴格模式下會導致錯誤


查詢標識符
--當在某個環境中為了讀取或者寫入而引用了一個標識符時,
---必須通過搜索來確定標識符實際代表什麽
--搜索過程從作用域鏈的前端開始,向上逐級查詢於給定名字匹配的標識符
--如果在局部環境中找到了,就停止搜索,變量就緒

--如果在局部環境中沒有找到,則沿著作用域鏈向上搜索
--搜索過程一直追溯到全局環境的變量對象
--如果還找不到則以為著該變量尚未聲明(會自動聲明一個全局變量)


垃圾收集
--JavaScript有自動的垃圾收集機制
--執行環境會負責管理代碼執行過程中使用的內存
--原理:找出那些不再繼續使用的變臉,釋放其占用的內存
--垃圾收集器會按照固定的時間間隔(或執行中預定的收集時間)周期性地執行這一操作

--函數中的局部變量的正常生命周期
--局部變量直在函數執行的過程中存在
--在這個過程中,會為局部變量在棧(或堆)內存上分配相應的空間,
--以便存儲他們的值,然後在函數中使用它們,直到函數執行結束
--函數結束,局部變量沒有存在的必要了,釋放掉他們的內存以供將來使用
--並不是所以情況下都這麽容易判斷變量是否有存在的必要
--垃圾收集器必須跟蹤,標記變量,
--用於標識無用變量的策略,因瀏覽器的不同而不同

--大致分兩種
--標記清除 和 引用計數

標記清除
--最常用的垃圾收集方式
--垃圾收集器在運行時,
--會給存儲在內存中的變量都加上標記
--然後,去掉環境中的變量以及被環境中的變量引用的變量 的標記
--這之後再被加上標記的變量視為準備刪除的變量
--因為環境中的變量已經無法訪問到這些變量(沒有引用)
--垃圾收集器會銷毀這些帶標記的值,並回收內存空間,完成內存清除工作


引用計數
--不太常見
--跟蹤記錄每一個值的引用次數
--當聲明了一個變量並將一個引用類型值賦給這個變量時,這個值的引用次數+1
--又被賦給另一個變量再+1
--如果這個變量的值又賦給新的值,則原來的值的引用-1
--當引用變成0時,就可以銷毀,回收內存
--當下一次垃圾收集器運行時,被銷毀,釋放

--存在很嚴重的問題
--循環引用的變量無法被銷毀

function f() {
    var a = new Object(); //1
    var b = new Object(); //1
    a.x = b; //2
    b.y = a; //2
}
f();    //a:1 b:1

--函數執行完,局部變量也不會被釋放


性能問題
--垃圾收集器是周期性運行的,時間間隔很重要
--被觸發運行的條件也很重要
--有的瀏覽器可手動觸發,但是不建議


管理內存
--瀏覽器可用內存數量通常要比分配給桌面應用程序要少
--出於安全角度,防止運行JavaScript的頁面耗盡系統內存而導致系統崩潰
--內存限制問題不僅影響給變量分配的內存,同時還會影響調用棧以及一個線程中能夠同時執行的語句數量
--確保占用最少的內存可以讓頁面獲得更好的性能

--最佳方式,只保存必要的數據
--一旦數據不再用,最好設置為null釋放(解除引用)

var a = new Object();

//do something...

a = null;    //手動解除引用

--解除一個值的引用為了讓值脫離執行環境,以便垃圾收集器下次執行時將其回收

小結

技術分享圖片

技術分享圖片

《JavaScript高級程序設計》讀書筆記(四)變量、作用域和內存問題