摘要:保存在堆內(nèi)存中。因此改變一方,另一方也會發(fā)生相應(yīng)的改變。作用域鏈當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會創(chuàng)建變量對象的一個(gè)作用域鏈,以保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。
基本類型和引用類型的值
基本類型值:簡單的數(shù)據(jù)段 ,五種基本類型(Number Boolean String Null Undefined)的值都是基本類型值,基本類型的值在內(nèi)存中大小固定,因此保存在棧內(nèi)存中。
引用類型值:可能由多個(gè)值構(gòu)成的對象。不能操作引用類型的內(nèi)存空間。保存在堆內(nèi)存中。
我們可以為引用類型的值添加、修改、刪除屬性和方法,比如:
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復(fù)制變量值
var num1 = 5; var num2 = 5;
num1與num2的內(nèi)存空間是完全獨(dú)立的,對一方的改變不會影響到另一方。
var obj1 = new Object(); var obj2 = obj1; obj2.name = "Sue"; alert(obj1.name); // Sue
當(dāng)我們將對象obj1復(fù)制給obj2時(shí),只是創(chuàng)建了一個(gè)指針副本,這個(gè)指針副本與obj1指向同一個(gè)保存在堆內(nèi)存中的對象。因此改變一方,另一方也會發(fā)生相應(yīng)的改變。
傳遞參數(shù)var num = 2 function add(num1, num2) { return num1 + num2; } add(1, num);
在上述代碼中,add(1, num)傳入的參數(shù)是實(shí)參,而arguments[]總是獲取由實(shí)參串起來的參數(shù)值,在函數(shù)體中的num1 num2是形參,相當(dāng)于聲明了兩個(gè)局部變量,指向arguments[0] arguments[1]。
ECMAScript 中所有函數(shù)的參數(shù)都是按值傳遞的,把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣(無論是基本類型還是引用類型)。
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中函數(shù)的參數(shù)都是按值傳遞的呢?
首先基本數(shù)據(jù)類型,全局變量num復(fù)制自身給參數(shù)num,二者是完全獨(dú)立的,改動(dòng)不會相互影響。
關(guān)于對象,我們似乎看到了函數(shù)內(nèi)部對obj1對象屬性的改動(dòng)反應(yīng)到了函數(shù)外部,而obj2重新賦值為另一個(gè)對象卻沒有反應(yīng)到外部,這是為什么呢?書中解釋得有點(diǎn)簡單,筆者找了一下資料,原來傳入對象的時(shí)候,其實(shí)傳入的是對象在內(nèi)存中的地址,當(dāng)在函數(shù)中改變對象的屬性時(shí),是在同一個(gè)區(qū)域進(jìn)行操作,所以會在函數(shù)外反映出來,然而,如果對這個(gè)局部變量重新賦值,內(nèi)存中的地址改變,就不會對函數(shù)外的對象產(chǎn)生影響了,這種思想稱為 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可以辨認(rèn)String Number Undefined Boolean Object還有函數(shù)。
typeof("name"); //string typeof(18); //number typeof(undefined); //undefined typeof(null); //object typeof(true); //boolean typeof(new Array()); //object typeof(Array); //function
正則表達(dá)式在某些瀏覽器中typeof返回結(jié)果為object,某些返回function。
instanceof可以判斷是否是給定類型的實(shí)例
var a = new Array; a instanceof Array; //true
使用instanceof測試基本數(shù)據(jù)類型時(shí),用于返回false。
執(zhí)行環(huán)境和作用域 執(zhí)行環(huán)境(execution context)定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對象中。
全局執(zhí)行環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境。在Web 瀏覽器中,全局執(zhí)行環(huán)境被認(rèn)為是window 對象,因此所有全局變量和函數(shù)都是作為window 對象的屬性和方法創(chuàng)建的。某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀(全局執(zhí)行環(huán)境直到應(yīng)用程序退出,例如關(guān)閉網(wǎng)頁或?yàn)g覽器時(shí)才會被銷毀)。
當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會被推入一個(gè)環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧將其環(huán)境彈出,將控制器返還給之前的執(zhí)行環(huán)境。
作用域鏈(scope chain)當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會創(chuàng)建變量對象的一個(gè)作用域鏈,以保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對象,對于全局執(zhí)行環(huán)境,就是window對象,對于函數(shù)執(zhí)行環(huán)境,就是該函數(shù)的活動(dòng)對象。作用域鏈的后續(xù),是該函數(shù)對象的[[scope]]屬性(全局執(zhí)行環(huán)境沒有后續(xù))。
函數(shù)對象在一個(gè)函數(shù)被定義時(shí),會創(chuàng)建這個(gè)函數(shù)對象的[[scope]]屬性,指向這個(gè)函數(shù)的外圍。
活動(dòng)對象在一個(gè)函數(shù)被調(diào)用時(shí),會創(chuàng)建一個(gè)活動(dòng)對象,首先將該函數(shù)的形參和實(shí)參(arguments)添加進(jìn)該活動(dòng)對象,然后添加函數(shù)體內(nèi)聲明的變量和函數(shù)(提前聲明,在剛進(jìn)入該函數(shù)執(zhí)行環(huán)境時(shí),值為undefined),這個(gè)活動(dòng)對象將作為該函數(shù)執(zhí)行環(huán)境作用域鏈的最前端。
關(guān)于JS的提前聲明機(jī)制,我們舉個(gè)例子證明一下:
function add (num1){ console.log(num2); var num3 = 4; return num1 + num2; } add(1); //undefined 5
上述代碼中,我們在變量聲明前使用它,卻沒有跑出ReferenceError,說明函數(shù)執(zhí)行時(shí),一開始,num2就已經(jīng)聲明了。
內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但外部環(huán)境不能訪問內(nèi)部環(huán)境中的任何變量和函數(shù)。這些環(huán)境之間的聯(lián)系是線性、有次序的。每個(gè)環(huán)境都可以向上搜索作用域鏈,以查詢變量和函數(shù)名;但任何環(huán)境都不能通過向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。
我們舉一個(gè)例子,順便理解一下前面的概念:
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);
開始執(zhí)行add()時(shí),首先創(chuàng)建一個(gè)執(zhí)行上下文,然后創(chuàng)建一個(gè)活動(dòng)對象,將arguments num1 num2 pre preAdd() result保存在活動(dòng)對象中,并將活動(dòng)對象放在作用域鏈的前端,執(zhí)行上下文取得add保存的[[scope]],并將其放入作用域鏈的后端,然后執(zhí)行到preAdd,preAdd創(chuàng)建一個(gè)執(zhí)行上下文,并壓入棧頂,創(chuàng)建一個(gè)活動(dòng)對象,保存arguments num pre ,放在作用域鏈的前端,取得preAdd的[[scope]],放入作用域鏈的后端。當(dāng)編譯器開始解析pre時(shí),首先從preAdd作用域鏈的前端開始找,找到了立刻停止。當(dāng)編譯器開始解析result = num1 + num2 + num,由于在add的作用域鏈前端(局部變量)中沒有該變量,因此繼續(xù)在作用域后端中尋找,并最終在全局變量中找到了num
延長作用域鏈在塊作用域內(nèi),將指定變量放在作用域鏈的前端
創(chuàng)建一個(gè)新的變量對象,其中包含的是被拋出的錯(cuò)誤對象的聲明,將這個(gè)對象放在作用域鏈的最前端,catch執(zhí)行結(jié)束后,作用域鏈恢復(fù)。
沒有塊級作用域ECMAScript中沒有塊級作用域,因此塊的執(zhí)行環(huán)境與其外部的執(zhí)行環(huán)境相同。
使用var 聲明的變量會自動(dòng)被添加到最接近的環(huán)境中。如果初始化變量時(shí)沒有使用var 聲明,該變量會自動(dòng)被添加到全局環(huán)境(嚴(yán)格模式下,這樣寫會拋錯(cuò))。
當(dāng)對一個(gè)變量進(jìn)行讀取或修改操作時(shí),我們首先要搜索到它,搜索的順序如圖:
標(biāo)識符解析是沿著作用域鏈一級一級地搜索標(biāo)識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標(biāo)識符為止(如果找不到標(biāo)識符,通常會導(dǎo)致錯(cuò)誤發(fā)生)。
Javascript具有自動(dòng)垃圾收集機(jī)制,周期性地回收那些不再使用的變量,并釋放其占用的內(nèi)存。
標(biāo)記清除(mark-and-sweep)這是Javascript中最常用的垃圾收集方式,當(dāng)變量進(jìn)入環(huán)境時(shí),將其標(biāo)記為“進(jìn)入環(huán)境”,離開環(huán)境時(shí),標(biāo)記為“離開環(huán)境”。理論上,不可以回收標(biāo)記為“進(jìn)入環(huán)境”的變量。
可以使用任何方式來標(biāo)記變量。比如,可以通過翻轉(zhuǎn)某個(gè)特殊的位來記錄一個(gè)變量何時(shí)進(jìn)入環(huán)境,或者使用一個(gè)“進(jìn)入環(huán)境的”變量列表及一個(gè)“離開環(huán)境的”變量列表來跟蹤哪個(gè)變量發(fā)生了變化。說到底,如何標(biāo)記變量其實(shí)并不重要,關(guān)鍵在于采取什么策略。引用計(jì)數(shù)(reference counting)
不太常見,跟蹤記錄每個(gè)值被引用的次數(shù)。
當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型值賦給該變量時(shí),則這個(gè)值的引用次數(shù)就是1。如果同一個(gè)值又被賦給另一個(gè)變量,則該值的引用次數(shù)加1。相反,如果包含對這個(gè)值引用的變量又取得了另外一個(gè)值或當(dāng)它們的生命期結(jié)束的時(shí)候,要給它們所指向的對象的引用計(jì)數(shù)減1。當(dāng)這個(gè)值的引用次數(shù)變成0 時(shí),則說明沒有辦法再訪問這個(gè)值了,因而就可以將其占用的內(nèi)存空間回收回來。
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
這樣看起來,引用計(jì)數(shù)法似乎沒什么問題,然而,當(dāng)遇到循環(huán)引用時(shí),就跪了。。。
var a = new Object(); //a指向的Object的引用次數(shù)+1 var b = new Object(); //b指向的Object的引用次數(shù)+1 a.another = b; //b指向的Object的引用次數(shù)+1 b.another = a; //a指向的Object的引用次數(shù)+1
此時(shí),兩個(gè)對象的引用次數(shù)都為2,用于都不會變?yōu)?,永遠(yuǎn)都不會被GC,浪費(fèi)內(nèi)存。
由于引用計(jì)數(shù)存在上述問題,因此早在Navigator 4.0就放棄了這一策略,但循環(huán)引用帶來的麻煩卻依然存在。
IE 中有一部分對象并不是原生JavaScript 對象。例如,BOM 和DOM 中的對象就是使用C++以COM(Component Object Model,組件對象模型)對象的形式實(shí)現(xiàn)的,COM的垃圾回收策略是引用計(jì)數(shù)法,因此只要涉及到COM對象,就會存在循環(huán)引用的問題,舉一個(gè)例子:
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.someObject = myObject;
IE9 把BOM 和DOM 對象都轉(zhuǎn)換成了真正的JavaScript 對象。這樣,就避免了
兩種垃圾收集算法并存導(dǎo)致的問題。
由于系統(tǒng)分配給瀏覽器的內(nèi)存比較?。ū茸烂鎽?yīng)用小),而內(nèi)存限制勢必會影響網(wǎng)頁性能,因此Javascript中,優(yōu)化內(nèi)存占用是一個(gè)必要的問題,最佳方式就是只保留必要的數(shù)據(jù)。局部變量會在離開執(zhí)行環(huán)境后自動(dòng)解除引用,而后被GC,因此我們只需在不再需要某個(gè)全局變量時(shí),將其設(shè)為null,來解除它對內(nèi)存的引用(即解除引用dereferencing),適用于大多數(shù)全局變量和全局對象的屬性。
針對上一節(jié)的例子,我們可以使用同樣的方法:
myObject.element = null; element.someObject = null
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/94769.html
摘要:執(zhí)行環(huán)境的類型有兩種全局全局執(zhí)行環(huán)境局部函數(shù)執(zhí)行環(huán)境每個(gè)環(huán)境都可以向上搜索作用域鏈,以查詢變量和函數(shù)名但任何環(huán)境都不能通過向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。內(nèi)部可通過作用域鏈訪問外部,外部不能訪問內(nèi)部。 變量、作用域和內(nèi)存問題 ECMAScript 數(shù)據(jù)類型 基本類型(5種): Undefined,Null,Boolean,Number,String typeof() 檢測...
摘要:在操作對象時(shí),實(shí)際上是在操作對象的引用而不是實(shí)際的對象。為此,引用類型的值是按引用訪問的。標(biāo)記清除是目前主流的垃圾收集算法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然后再回收其內(nèi)存 1.在操作對象時(shí),實(shí)際上是在操作對象的引用而不是實(shí)際的對象。為此,引用類型的值是按引用訪問的。 2.當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),兩個(gè)變量實(shí)際上將引用同一個(gè)對象,因此,改變其中一個(gè)變量,就會...
摘要:全局變量是最外圍的一個(gè)執(zhí)行環(huán)境,代碼在環(huán)境中執(zhí)行,會創(chuàng)建一個(gè)作用域鏈,用途是保證對執(zhí)行環(huán)境有權(quán)訪問所有變量和函數(shù)的有序訪問。作用域鏈中最后一個(gè)對象始終是全局執(zhí)行環(huán)境。內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,外部則不能訪問內(nèi)部。 1、基本類型和引用類型的值 * 基本類型 : 指的是簡單的數(shù)據(jù)段,五種基本類型是按值訪問的,可以直接操作保存在變量中實(shí)際的值。 * 引用類型 : 指那些可能...
摘要:具體來說就是當(dāng)執(zhí)行流進(jìn)入下列任何一個(gè)語句時(shí),作用域鏈就會得到加長語句的塊和語句。這兩個(gè)語句都會在作用域鏈的前端添加一個(gè)變量對象。對來說,會將指定的對象添加到作用域鏈中。 1. 基本類型和引用類型的值 JavaScript變量可以用來保存兩種類型的值:基本類性值和引用類性值?;绢愋椭翟醋砸韵?種基本數(shù)據(jù)類型:Undefined、Null、Boolean、Number和String。基本...
摘要:注意由于閉包會額外的附帶函數(shù)的作用域內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域,因此,閉包會比其它函數(shù)多占用些內(nèi)存空間,過度的使用可能會導(dǎo)致內(nèi)存占用的增加。 作用域和作用域鏈?zhǔn)莏avascript中非常重要的特性,對于他們的理解直接關(guān)系到對于整個(gè)javascript體系的理解,而閉包又是對作用域的延伸,也是在實(shí)際開發(fā)中經(jīng)常使用的一個(gè)特性,實(shí)際上,不僅僅是javascript,在很多語言中都...
摘要:變量作用域和內(nèi)存問題基本類型和引用類型的值基本類型就是簡單的數(shù)據(jù)段種值類型,而引用類型就是對象操控對象的引用。但是不但能訪問自己的變量,也能訪問和全局作用域下的變量。延長作用域鏈相當(dāng)于創(chuàng)造了一個(gè)新的變量對象在當(dāng)前作用域的上方。 變量作用域和內(nèi)存問題 1.基本類型和引用類型的值 基本類型就是簡單的數(shù)據(jù)段(5種值類型),而引用類型就是對象(操控對象的引用)。 1.1復(fù)制變量值 引用類型實(shí)際...
閱讀 574·2023-04-26 02:58
閱讀 2314·2021-09-27 14:01
閱讀 3620·2021-09-22 15:57
閱讀 1182·2019-08-30 15:56
閱讀 1052·2019-08-30 15:53
閱讀 801·2019-08-30 15:52
閱讀 655·2019-08-26 14:01
閱讀 2173·2019-08-26 13:41