摘要:保存在堆內存中。因此改變一方,另一方也會發生相應的改變。作用域鏈當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,以保證對執行環境有權訪問的所有變量和函數的有序訪問。
基本類型和引用類型的值
基本類型值:簡單的數據段 ,五種基本類型(Number Boolean String Null Undefined)的值都是基本類型值,基本類型的值在內存中大小固定,因此保存在棧內存中。
引用類型值:可能由多個值構成的對象。不能操作引用類型的內存空間。保存在堆內存中。
我們可以為引用類型的值添加、修改、刪除屬性和方法,比如:
var cat = new Animal(); cat.name = "cat"; cat.speak = function() { alert(this.name); }; cat.speak(); // cat
然而為基本類型的值添加屬性和方法是無效的。
var name = "Sue"; name.age = 18; alert(name.age); //undefined復制變量值
var num1 = 5; var num2 = 5;
num1與num2的內存空間是完全獨立的,對一方的改變不會影響到另一方。
var obj1 = new Object(); var obj2 = obj1; obj2.name = "Sue"; alert(obj1.name); // Sue
當我們將對象obj1復制給obj2時,只是創建了一個指針副本,這個指針副本與obj1指向同一個保存在堆內存中的對象。因此改變一方,另一方也會發生相應的改變。
傳遞參數var num = 2 function add(num1, num2) { return num1 + num2; } add(1, num);
在上述代碼中,add(1, num)傳入的參數是實參,而arguments[]總是獲取由實參串起來的參數值,在函數體中的num1 num2是形參,相當于聲明了兩個局部變量,指向arguments[0] arguments[1]。
ECMAScript 中所有函數的參數都是按值傳遞的,把函數外部的值復制給函數內部的參數,就和把值從一個變量復制到另一個變量一樣(無論是基本類型還是引用類型)。
function changeStuff(num, obj1, obj2) { num = num * 10; obj1.item = "changed"; obj2 = {item: "changed"}; } var num = 10; var obj1 = {item: "unchanged"}; var obj2 = {item: "unchanged"}; changeStuff(num, obj1, obj2); console.log(num); // 10 console.log(obj1.item); // changed console.log(obj2.item); // unchanged
以上的例子是怎么說明ECMAScript中函數的參數都是按值傳遞的呢?
首先基本數據類型,全局變量num復制自身給參數num,二者是完全獨立的,改動不會相互影響。
關于對象,我們似乎看到了函數內部對obj1對象屬性的改動反應到了函數外部,而obj2重新賦值為另一個對象卻沒有反應到外部,這是為什么呢?書中解釋得有點簡單,筆者找了一下資料,原來傳入對象的時候,其實傳入的是對象在內存中的地址,當在函數中改變對象的屬性時,是在同一個區域進行操作,所以會在函數外反映出來,然而,如果對這個局部變量重新賦值,內存中的地址改變,就不會對函數外的對象產生影響了,這種思想稱為 call by sharing。
?However, since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are?mutable, within the function are visible to the caller, which may appear to differ from call by value semantics. Mutations of a mutable object within the function are visible to the caller because the object is not copied or cloned — it is shared. Wikipedia檢測類型
使用typeof可以辨認String Number Undefined Boolean Object還有函數。
typeof("name"); //string typeof(18); //number typeof(undefined); //undefined typeof(null); //object typeof(true); //boolean typeof(new Array()); //object typeof(Array); //function
正則表達式在某些瀏覽器中typeof返回結果為object,某些返回function。
instanceof可以判斷是否是給定類型的實例
var a = new Array; a instanceof Array; //true
使用instanceof測試基本數據類型時,用于返回false。
執行環境和作用域 執行環境(execution context)定義了變量或函數有權訪問的其他數據。每個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的所有變量和函數都保存在這個對象中。
全局執行環境是最外圍的一個執行環境。在Web 瀏覽器中,全局執行環境被認為是window 對象,因此所有全局變量和函數都是作為window 對象的屬性和方法創建的。某個執行環境中的所有代碼執行完畢后,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀(全局執行環境直到應用程序退出,例如關閉網頁或瀏覽器時才會被銷毀)。
當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之后,棧將其環境彈出,將控制器返還給之前的執行環境。
作用域鏈(scope chain)當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,以保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象,對于全局執行環境,就是window對象,對于函數執行環境,就是該函數的活動對象。作用域鏈的后續,是該函數對象的[[scope]]屬性(全局執行環境沒有后續)。
函數對象在一個函數被定義時,會創建這個函數對象的[[scope]]屬性,指向這個函數的外圍。
活動對象在一個函數被調用時,會創建一個活動對象,首先將該函數的形參和實參(arguments)添加進該活動對象,然后添加函數體內聲明的變量和函數(提前聲明,在剛進入該函數執行環境時,值為undefined),這個活動對象將作為該函數執行環境作用域鏈的最前端。
關于JS的提前聲明機制,我們舉個例子證明一下:
function add (num1){ console.log(num2); var num3 = 4; return num1 + num2; } add(1); //undefined 5
上述代碼中,我們在變量聲明前使用它,卻沒有跑出ReferenceError,說明函數執行時,一開始,num2就已經聲明了。
內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。這些環境之間的聯系是線性、有次序的。每個環境都可以向上搜索作用域鏈,以查詢變量和函數名;但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。
我們舉一個例子,順便理解一下前面的概念:
var num = 10; function add (num1, num2) { function preAdd(num) { var pre = 1; return pre + num; num1 = preAdd(num1); var result = num1 + num2 + num; return result; } add(10, 20);
開始執行add()時,首先創建一個執行上下文,然后創建一個活動對象,將arguments num1 num2 pre preAdd() result保存在活動對象中,并將活動對象放在作用域鏈的前端,執行上下文取得add保存的[[scope]],并將其放入作用域鏈的后端,然后執行到preAdd,preAdd創建一個執行上下文,并壓入棧頂,創建一個活動對象,保存arguments num pre ,放在作用域鏈的前端,取得preAdd的[[scope]],放入作用域鏈的后端。當編譯器開始解析pre時,首先從preAdd作用域鏈的前端開始找,找到了立刻停止。當編譯器開始解析result = num1 + num2 + num,由于在add的作用域鏈前端(局部變量)中沒有該變量,因此繼續在作用域后端中尋找,并最終在全局變量中找到了num
延長作用域鏈在塊作用域內,將指定變量放在作用域鏈的前端
創建一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明,將這個對象放在作用域鏈的最前端,catch執行結束后,作用域鏈恢復。
沒有塊級作用域ECMAScript中沒有塊級作用域,因此塊的執行環境與其外部的執行環境相同。
使用var 聲明的變量會自動被添加到最接近的環境中。如果初始化變量時沒有使用var 聲明,該變量會自動被添加到全局環境(嚴格模式下,這樣寫會拋錯)。
當對一個變量進行讀取或修改操作時,我們首先要搜索到它,搜索的順序如圖:
標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標識符為止(如果找不到標識符,通常會導致錯誤發生)。
Javascript具有自動垃圾收集機制,周期性地回收那些不再使用的變量,并釋放其占用的內存。
標記清除(mark-and-sweep)這是Javascript中最常用的垃圾收集方式,當變量進入環境時,將其標記為“進入環境”,離開環境時,標記為“離開環境”。理論上,不可以回收標記為“進入環境”的變量。
可以使用任何方式來標記變量。比如,可以通過翻轉某個特殊的位來記錄一個變量何時進入環境,或者使用一個“進入環境的”變量列表及一個“離開環境的”變量列表來跟蹤哪個變量發生了變化。說到底,如何標記變量其實并不重要,關鍵在于采取什么策略。引用計數(reference counting)
不太常見,跟蹤記錄每個值被引用的次數。
當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含對這個值引用的變量又取得了另外一個值或當它們的生命期結束的時候,要給它們所指向的對象的引用計數減1。當這個值的引用次數變成0 時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間回收回來。
var a = new Cat(); // 1 var b = a; // 2 var c = b; // 3 b = new Dog(); // 2 c = new Fox(); // 1 a = new Object(); // 0
這樣看起來,引用計數法似乎沒什么問題,然而,當遇到循環引用時,就跪了。。。
var a = new Object(); //a指向的Object的引用次數+1 var b = new Object(); //b指向的Object的引用次數+1 a.another = b; //b指向的Object的引用次數+1 b.another = a; //a指向的Object的引用次數+1
此時,兩個對象的引用次數都為2,用于都不會變為0,永遠都不會被GC,浪費內存。
由于引用計數存在上述問題,因此早在Navigator 4.0就放棄了這一策略,但循環引用帶來的麻煩卻依然存在。
IE 中有一部分對象并不是原生JavaScript 對象。例如,BOM 和DOM 中的對象就是使用C++以COM(Component Object Model,組件對象模型)對象的形式實現的,COM的垃圾回收策略是引用計數法,因此只要涉及到COM對象,就會存在循環引用的問題,舉一個例子:
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.someObject = myObject;
IE9 把BOM 和DOM 對象都轉換成了真正的JavaScript 對象。這樣,就避免了
兩種垃圾收集算法并存導致的問題。
由于系統分配給瀏覽器的內存比較?。ū茸烂鎽眯。鴥却嫦拗苿荼貢绊懢W頁性能,因此Javascript中,優化內存占用是一個必要的問題,最佳方式就是只保留必要的數據。局部變量會在離開執行環境后自動解除引用,而后被GC,因此我們只需在不再需要某個全局變量時,將其設為null,來解除它對內存的引用(即解除引用dereferencing),適用于大多數全局變量和全局對象的屬性。
針對上一節的例子,我們可以使用同樣的方法:
myObject.element = null; element.someObject = null
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94769.html
摘要:執行環境的類型有兩種全局全局執行環境局部函數執行環境每個環境都可以向上搜索作用域鏈,以查詢變量和函數名但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。內部可通過作用域鏈訪問外部,外部不能訪問內部。 變量、作用域和內存問題 ECMAScript 數據類型 基本類型(5種): Undefined,Null,Boolean,Number,String typeof() 檢測...
摘要:在操作對象時,實際上是在操作對象的引用而不是實際的對象。為此,引用類型的值是按引用訪問的。標記清除是目前主流的垃圾收集算法,這種算法的思想是給當前不使用的值加上標記,然后再回收其內存 1.在操作對象時,實際上是在操作對象的引用而不是實際的對象。為此,引用類型的值是按引用訪問的。 2.當從一個變量向另一個變量復制引用類型的值時,兩個變量實際上將引用同一個對象,因此,改變其中一個變量,就會...
摘要:全局變量是最外圍的一個執行環境,代碼在環境中執行,會創建一個作用域鏈,用途是保證對執行環境有權訪問所有變量和函數的有序訪問。作用域鏈中最后一個對象始終是全局執行環境。內部環境可以通過作用域鏈訪問所有的外部環境,外部則不能訪問內部。 1、基本類型和引用類型的值 * 基本類型 : 指的是簡單的數據段,五種基本類型是按值訪問的,可以直接操作保存在變量中實際的值。 * 引用類型 : 指那些可能...
摘要:具體來說就是當執行流進入下列任何一個語句時,作用域鏈就會得到加長語句的塊和語句。這兩個語句都會在作用域鏈的前端添加一個變量對象。對來說,會將指定的對象添加到作用域鏈中。 1. 基本類型和引用類型的值 JavaScript變量可以用來保存兩種類型的值:基本類性值和引用類性值?;绢愋椭翟醋砸韵?種基本數據類型:Undefined、Null、Boolean、Number和String。基本...
摘要:注意由于閉包會額外的附帶函數的作用域內部匿名函數攜帶外部函數的作用域,因此,閉包會比其它函數多占用些內存空間,過度的使用可能會導致內存占用的增加。 作用域和作用域鏈是javascript中非常重要的特性,對于他們的理解直接關系到對于整個javascript體系的理解,而閉包又是對作用域的延伸,也是在實際開發中經常使用的一個特性,實際上,不僅僅是javascript,在很多語言中都...
摘要:變量作用域和內存問題基本類型和引用類型的值基本類型就是簡單的數據段種值類型,而引用類型就是對象操控對象的引用。但是不但能訪問自己的變量,也能訪問和全局作用域下的變量。延長作用域鏈相當于創造了一個新的變量對象在當前作用域的上方。 變量作用域和內存問題 1.基本類型和引用類型的值 基本類型就是簡單的數據段(5種值類型),而引用類型就是對象(操控對象的引用)。 1.1復制變量值 引用類型實際...
閱讀 561·2023-04-26 02:58
閱讀 2301·2021-09-27 14:01
閱讀 3605·2021-09-22 15:57
閱讀 1168·2019-08-30 15:56
閱讀 1043·2019-08-30 15:53
閱讀 787·2019-08-30 15:52
閱讀 645·2019-08-26 14:01
閱讀 2157·2019-08-26 13:41