重讀JS(四)數據類型、作用域和內存問題


本章內容

  • 理解基本類型和引用類型的值

  • 理解執行環境

  • 理解垃圾收集

JavaScript的變量與其他語言的變量有很大區別。JavaScript變量松散類型的本質,決定了它只是特定時間用於保存特定值的一個名字而已。由於不存在定義某個變量必須要保存何種數據類型值的規則,變量的值及其數據類型可以在腳本的聲明周期內改變。盡管從某種角度看,這可能是一個既有趣又強大,同時又容易出問題的特性,但JavaScript變量實際的復雜程度還遠不如此。

一、基本類型和引用類型的值

基本類型值指的是簡單的數據段,而引用類型值值那些可能由多個值構成的對象。

5種基本數據類型:undefinednullbooleannumberstring

引用數據類型:Object(ArrayDateRegExpFunction)

補充:
1.為了和下一章的基本包裝類型區分(new Blooean()、new Number()、new String()),這里的基本數據類型一律小寫,看到很多地方大家都大寫,這個問題困惑我許久,直到看到基本包裝類型才恍然大悟。
2.Object是一個基礎類型,其他所有類型都從Object繼承了基本的行為。Array,Date,RegExp,Function會在下一章內容詳細介紹。

區別

基本數據類型值在內存中占據固定大小的空間,因此保存在棧內存中,且不能為其添加屬性值。

引用數據類型保存在堆內存中,然后在棧內存中保存了一個對堆內存中實際對象的引用,即數據在堆內存中的地址。

JS對引用數據類型的操作都是操作對象的引用而不是實際的對象,如果obj1拷貝了obj2,那么這兩個引用數據類型就指向了同一個堆內存對象,具體操作是obj1將棧內存的引用地址復制了一份給obj2,因而它們共同指向了一個堆內存對象;

為什么基本數據類型保存在棧中,而引用數據類型保存在堆中?1)堆比棧大,棧比堆速度快;2)基本數據類型比較穩定,而且相對來說占用的內存小;3)引用數據類型大小是動態的,而且是無限的,引用值的大小會改變,不能把它放在棧中,否則會降低變量查找的速度,因此放在變量棧空間的值是該對象存儲在堆中的地址,地址的大小是固定的,所以把它存儲在棧中對變量性能無任何負面影響;4)堆內存是無序存儲,可以根據引用直接獲取;

按引用訪問:js不允許直接訪問保存在堆內存中的對象,所以在訪問一個對象時,首先得到的是這個對象在堆內存中的地址,然后再按照這個地址去獲得這個對象中的值;

傳遞參數

ECMAScript中所有函數的參數都是按值來傳遞的,

對於基本數據類型值,只是把變量里的值傳遞給參數,之后參數和這個變量互不影響,相當於復制。

對於引用值,對象變量里面的值是這個對象在堆內存中的內存地址,因此它傳遞的值也就是這個內存地址,這也就是為什么函數內部對這個參數的修改會體現在外部的原因,因為它們都指向同一個對象;

function setName(obj){
    obj.name = "Nicholas"
}

var person = new Object();
setName(person);
alert(person.name);  //"Nicholas"
function setName(obj) {
    obj.name = "Nicholas";
    var newobj = new Object();    
    obj = newobj;   //指針指向改變了
    obj.name = "Adagio";
}

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

檢測類型

typeof

基本數據類型使用typeof可以返回其基本數據類型,但是null類型會返回object,因為null值表示一個空對象指針;

instanceof

雖然在檢測基本數據類型時typeof是非常得力的助手,但在檢測引用類型的值時,用處不大。通常我們更想知道的是這個對象是什么類型的。

根據規定,所有引用類型的值都是Object的實例。因此,在檢測一個引用類型值和Object構造函數時,instanceof操作符始終會返回true。

alert(person instanceof Object);
alert(colors instanceof Array);
alert(pattern instanceof RefExp);
alert(Array instanceof Object);  //true

使用typeof操作符檢測函數時,返回'function'。在Safari5及之前版本和Chrome7及之前版本中使用typeof檢測正則表達式時,也返回'function'。ECMA-262規定任何在內部實現[[Call]]方法的對象都應該應用typeof操作符返回'function'。上述瀏覽器中的正則使用了。在IE和Firefox中正則表達式使用typeof返回'object'

二、執行環境及作用域

執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理時會在后台使用它。

全局和局部執行環境

在Web瀏覽器中,是window對象,因此所有全局變量和函數都是作為window對象的屬性和方法創建的。某個執行環境中的所有代碼執行完畢后,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀。(全局執行環境直到應用程序退出-例如關閉網頁或瀏覽器時被銷毀)。

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

作用域鏈

當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain),保證對執行環境有權訪問的所有變量和函數的有序訪問。

作用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。如果這個環境是函數,則將其活動對象作為變量對象。活動對象在最開始時質保函一個變量,即arguments對象(這個對象在全局環境中是不存在的)。作用域鏈的下一個變量對象來自包含(外部)環境,而在下一個對象則來自下一個包含環境,這樣,一直延續到全局執行環境(作用域鏈中的最后一個對象)標識符解析是沿着作用域鏈一級一級地搜索標識符的過程,過程始終從作用域鏈的前端開始,然后逐級地向后回溯,之道找到標識符為止。例:

var color = "blue";

function changeColor(){
    var anotherColor = "red";

    function swapColors(){
        var tempColor = anotherColor;
        anotherColor = tempColor;
        //這里可以訪問tempColor、anotherColor和tempColor
    }

    // 這里可以訪問color和anotherColor,但不能訪問tempColor
    swapColors();
}

// 這里只能訪問color
changeColor();

作用域鏈(swapColors()->changeColor()->window)

延長作用域鏈

try-catch語句的catch塊

with語句

這兩個語句都會在作用域鏈的前端添加一個變量對象。對於with來說,會將指定的對象添加到作用域鏈中。對於catch語句來說,會創建一個新的變量對象,其中包含的是被刨除的錯誤對象的聲明。例子:

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

在此,with語句接受的是loaction對象,因此其變量對象中就包含了location對象的所有屬性和方法,而這個變量對象就被添加到了作用域鏈的前端。當在with語句中引用變量href時(實際引用的是location.href),可以在當前執行環境的變量對象中找到。

至於with語句內部定義的url變量,是函數執行環境的一部分,自然可以作為函數的值被返回。

⭐沒有塊級作用域

在其他類C的語言中,由花括號封閉的代碼塊都有自己的作用域,但JavaScript不一定。

if語句中的變量聲明會將變量添加到當前的執行環境。

for語句創建的變量i即使在for循環執行結束后,也依舊會存在於循環外部的執行環境中。

1.聲明變量

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

2.查詢標識符

訪問局部變量(color)和訪問全局變量(window.color)更快

三、圾收集

JavaScript具有自動垃圾收集機制,執行環境會負責管理代碼執行過程中使用的內存。所需內存的分配和無用內存的回收實現了自動管理。

原理:找出那些不再使用的變量,然后釋放其占用的內存。垃圾收集器會按固定的時間間隔(或代碼執行中預定的手機時間)周期性地執行這一操作。

垃圾收集策略

  • 標記清除

給當前不使用的值加上標記,然后再回收其內存。

  • 引用計數(不再用)

跟蹤記錄所有值被引用的次熟。

性能問題

隨着IE7的發布,其JavaScript引擎的垃圾收集例程改變工作方式:觸發垃圾收集的變量分配、字面量和(或)數組元素的臨界值被動態調整為動態修正。如果垃圾收集例程回收的內存分配量低於15%,則臨界值加倍。如果回收了85%的內存分配量,則將臨界值重置回默認值。

在有的瀏覽器中可以觸發垃圾收集過程,比如IE:window.CollectGarbage()方法,Opera:window.opera.collect()。但不建議這樣做

管理內存

雖然存在垃圾收集機制,但系統分配給Web瀏覽器的可用內存數量通常比桌面應用程序少,目的是防止運行JavaScript的網頁耗盡全部系統內存而導致系統奔潰。因此,確保占用最少的內存可以讓頁面獲得更好的性能。最佳方式就是為執行中的代碼保存必要的數據,一旦不再用,最好通過將其值設為null來釋放內存(解除引用)。

這一做法適用於大多數全局變量和全局對象的屬性,局部變量會在它們離開執行環境時自動解除引用。

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

var globalPerson = creatPerson("Nicholas")

// ....
// 手動解除globalPerson的引用
globalPerson = null;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM