細(xì)說(shuō)JavaScript中的變量,作用域和垃圾回收
在 JavaScript 中,數(shù)據(jù)類型可分為基本類型和引用類型,
基本類型有六種:Null,Undefined,String,Boolean,Number,Symbol;
而引用類型就是傳說(shuō)中的 Object 了。
其中基本類型是按值傳遞,而引用類型的值是按引用訪問(wèn)的,所以在操作對(duì)象時(shí),實(shí)際上是在操作對(duì)象的引用而不是實(shí)際的對(duì)象 ( ps:在為對(duì)象添加屬性時(shí),操作的是實(shí)際的對(duì)象 )。
關(guān)于基本類型和引用類型的不同,大概有以下幾點(diǎn):
1、引用類型是動(dòng)態(tài)的屬性,而基本類型不是。
對(duì)于引用類型,我們可以為其添加、刪除屬性和方法,但不能給基本類型的值添加屬性:
// 基本類型 var name = 'Fly_001'; name.age = 22; alert(name.age); // undefined; // 引用類型 var person = new Object(); person.name = 'Fly_001'; alert(person.name); // 'Fly_001';
2、復(fù)制的方式不同。
如果從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值,會(huì)將值復(fù)制到為新變量分配的位置上:
var num1 = 5; var num2 = num1;
當(dāng)使用 num1 的值來(lái)初始化 num2 時(shí),num2 中也保存了值5,但該值只是 num1 中 5 的一個(gè)副本,兩個(gè)變量不會(huì)互相影響。
當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),傳遞的是一個(gè)指針,其指向存儲(chǔ)在堆中的一個(gè)對(duì)象,在復(fù)制結(jié)束后,兩個(gè)變量實(shí)際上將引用同一個(gè)對(duì)象,改變其中一個(gè)變量就會(huì)影響另一個(gè)變量:
var obj1 = new Object(); var obj2 = obj1; obj1.name = 'Fly_001'; alert(obj2.name); // 'Fly_001';
3、傳遞參數(shù)的特點(diǎn)。
這是一個(gè)容易困惑的點(diǎn) 。
ECMAScript 中所有函數(shù)的參數(shù)都是按值傳遞的。也就是說(shuō),把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。基本類型值的傳遞如同基本類型變量的復(fù)制一樣,而引用類型的傳遞,則如同引用類型變量的復(fù)制一樣,這一點(diǎn)確實(shí)會(huì)引起很多小伙伴的爭(zhēng)議,歡迎討論~
- 在向參數(shù)傳遞基本類型的值時(shí),被傳遞的值會(huì)被復(fù)制給一個(gè)局部變量( 即 arguments 對(duì)象中的一個(gè)元素 )。
- 在向參數(shù)傳遞引用類型的值時(shí),會(huì)把這個(gè)值在內(nèi)存中的地址復(fù)制給一個(gè)局部變量,因此該局部變量的變化會(huì)反映到函數(shù)的外部:
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); // 20,木有變化;
alert(result); // 30
function setNmae(obj) {
obj.name = 'Fly_001';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';在上面代碼中我們創(chuàng)建了一個(gè)對(duì)象,并將其保存在了變量 person 中。然后,這個(gè)對(duì)象被傳遞到 setName () 函數(shù)中就被復(fù)制給了 obj,在這個(gè)函數(shù)內(nèi)部,obj 和 person 引用的是同一個(gè)對(duì)象。
很多小伙伴會(huì)認(rèn)為該參數(shù)是按引用傳遞的,為了證明對(duì)象是按值傳遞的,再看下這個(gè)修改過(guò)的代碼:
function setName(obj) {
obj.name = 'Fly_001';
obj = new Object();
obj.name = 'juejin';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';如果 person 是按引用傳遞的,那么 person 就會(huì)自動(dòng)被修改為指向其 name 屬性為 ‘juejin’ 的新對(duì)象。但接下來(lái)再訪問(wèn) person.name 時(shí)仍然顯示 ‘Fly_001’,這說(shuō)明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍保持不變。( 實(shí)際上,當(dāng)在函數(shù)內(nèi)部重寫 obj 時(shí),這個(gè)變量引用的就是一個(gè)局部對(duì)象了,其將在函數(shù)執(zhí)行完畢后立即被銷毀。)
4、檢測(cè)類型的操作符不同。
檢測(cè)基本類型適宜用 typeof 操作符
alert(typeof 'Fly_001'); // 'string'; alert(typeof []); // 'object';
因?yàn)?typeof 操作符的返回值為 'undefined','string','boolean','number','symbol','object','function' 其中之一。
它可以很友好地指出某一具體基本類型,而對(duì)于引用類型則籠統(tǒng)地返回 'object'( typeof 對(duì) 數(shù)組、正則、null 都會(huì)返回 'object' )。
在檢測(cè)引用類型時(shí)更適合用 instanceof 操作符:
result = varible instanceof constructor;
如果變量是給定引用類型的實(shí)例( 根據(jù)它的原型鏈來(lái)識(shí)別 ),那 instanceof 操作符將會(huì)返回 true。
執(zhí)行環(huán)境及作用域
下面聊下 JavaScript 中很重要的一個(gè)概念 —— 執(zhí)行環(huán)境。
JS 中每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象,在 Web 瀏覽器中,全局執(zhí)行環(huán)境是 window 對(duì)象,因此所有全局變量和函數(shù)都是作為 window 對(duì)象的屬性和方法創(chuàng)建的。
某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境將會(huì)被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀,全局執(zhí)行環(huán)境直至網(wǎng)頁(yè)或?yàn)g覽器關(guān)閉時(shí)才被銷毀( 如果存在閉包,情況又有所不同,會(huì)在后面幾篇提到 ,多謝 吳hr 指正)。
每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧會(huì)將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 這里可以訪問(wèn) color、anotherColor 和 tempColor;
}
swapColors();
// 這里可以訪問(wèn) color 和 anotherColor,但不能訪問(wèn) tempColor;
}
changeColor();
// 這里只能訪問(wèn) color;以上代碼共涉及 3 個(gè)執(zhí)行環(huán)境:全局環(huán)境、changeColor() 的局部環(huán)境和 swapColor() 局部環(huán)境。其中,內(nèi)部環(huán)境可以通過(guò)作用域鏈訪問(wèn)所有的外部環(huán)境,但外部環(huán)境不能訪問(wèn)內(nèi)部環(huán)境中的任何變量和函數(shù)。 這些環(huán)境之間的聯(lián)系是線性的、有次序的。每個(gè)環(huán)境可以向上搜索作用域鏈 ,以查詢變量和函數(shù)名;但任何環(huán)境都不能通過(guò)向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。
延長(zhǎng)作用域鏈
雖然執(zhí)行環(huán)境的類型總共只有兩種 —— 全局和局部 (函數(shù)),但還是兩種辦法來(lái)延長(zhǎng)作用域鏈~ 就是通過(guò) try-catch 語(yǔ)句的 catch 塊和 with 語(yǔ)句。
這兩個(gè)語(yǔ)句都會(huì)在作用域鏈的前端添加一個(gè)變量對(duì)象。對(duì) with 語(yǔ)句來(lái)說(shuō),會(huì)將指定的對(duì)象添加到作用域鏈中;對(duì)于 catch 語(yǔ)句來(lái)說(shuō),會(huì)創(chuàng)建一個(gè)新的變量對(duì)象,其中包含的是被拋出的錯(cuò)誤對(duì)象的聲明。
沒(méi)有塊級(jí)作用域
JavaScript 沒(méi)有塊級(jí)作用域經(jīng)常會(huì)導(dǎo)致理解上的困惑 。在其它類 C 的語(yǔ)言中,由花括號(hào)封閉的代碼塊都有自己的作用域,即執(zhí)行環(huán)境,但在 JavaScript 中卻不是這樣:
if (true) {
var color = 'blue';
}
alert(color); // 'blue';
for (var i = 0; i < 10; i ++) {
// dosomething
}
alert(i); // 10;使用 var 聲明的變量會(huì)自動(dòng)被添加到最接近的環(huán)境中。在函數(shù)內(nèi)部,最接近的環(huán)境就是函數(shù)的局部環(huán)境,若初始化變量時(shí)沒(méi)有使用 var 聲明,該變量會(huì)自動(dòng)被添加到全局環(huán)境。( 創(chuàng)建塊范圍局部變量使用 let 關(guān)鍵字更方便 ):
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); // 30;
alert(sum); // 'sum is not defined';在上面代碼中,雖然 sum 從函數(shù)中返回了,但在函數(shù)外部是訪問(wèn)不到的。如果省略 var 關(guān)鍵字,這時(shí) sum 是可以訪問(wèn)到的( 不過(guò)在嚴(yán)格模式下,初始化未聲明的變量會(huì)報(bào) 'xxx is not defined' 錯(cuò) )。
模仿塊級(jí)作用域
雖然 js 沒(méi)有塊級(jí)作用域,但我們可以用匿名函數(shù)來(lái)模仿塊級(jí)作用域~,語(yǔ)法格式如下:
(function() {
// 這里是塊級(jí)作用域;
}) ();將函數(shù)聲明包含在一對(duì)圓括號(hào)里,表示它實(shí)際上是一個(gè)函數(shù)表達(dá)式,而緊隨其后的圓括號(hào)會(huì)立即調(diào)用這個(gè)函數(shù)。實(shí)際上就相當(dāng)于:
var someFunction() {
// 這里是塊級(jí)作用域;
};
someFunction();同時(shí)因?yàn)?JavaScript 將 function 關(guān)鍵字當(dāng)作一個(gè)函數(shù)聲明的開(kāi)始,后面不能直接跟圓括號(hào),而函數(shù)表達(dá)式后面可以跟圓括號(hào),所以將函數(shù)聲明加上圓括號(hào)轉(zhuǎn)換成函數(shù)表達(dá)式。
無(wú)論在什么地方,只要臨時(shí)需要一些變量,就可以使用私有作用域:
function outputNumbers(count) {
(function () {
for (var i = 0; i < count; i ++) {
alert(i);
}
}) ();
alert(i); // 會(huì)導(dǎo)致錯(cuò)誤,讀取不到 i;
}因?yàn)樵谀涿瘮?shù)中定義的任何變量,都會(huì)在執(zhí)行結(jié)束時(shí)立即銷毀,所以變量 i 只能在循環(huán)中使用。
查詢標(biāo)識(shí)符
當(dāng)在某個(gè)環(huán)境中為了讀取或?qū)懭攵靡粋€(gè)變量或函數(shù)名 ( 標(biāo)識(shí)符 ),必須通過(guò)搜索來(lái)確定該它實(shí)際代表什么。
搜索過(guò)程從作用域的前端開(kāi)始,向上逐級(jí)查找,如果存在一個(gè)局部的變量的定義,則停止搜索,即同名局部變量將覆蓋同名全局變量:
var color = 'blue';
function getColor() {
var color = 'red'; // 局部變量;
return color;
}
alert(getColor()); // 'red';
alert(window.color); // 'blue';垃圾收集
JavaScript 具有自動(dòng)垃圾收集機(jī)制,所以開(kāi)發(fā)人員不必?fù)?dān)心內(nèi)存使用問(wèn)題,是不是很開(kāi)森 ,但最好還是了解下 。
首先我們來(lái)分析函數(shù)中局部變量的正常生命周期:局部變量只在函數(shù)執(zhí)行的過(guò)程中存在,函數(shù)執(zhí)行結(jié)束后就會(huì)釋放掉它們的內(nèi)存以供將來(lái)使用。所以 垃圾收集器必須跟蹤哪些變量有用、哪些變量沒(méi)用,具體到瀏覽器的實(shí)現(xiàn)有兩個(gè)策略:標(biāo)記清除和引用計(jì)數(shù)
標(biāo)記清除
此乃 JavaScript 中最常用的垃圾收集機(jī)制。
垃圾收集器在運(yùn)行的時(shí)候會(huì)把存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記,然后去掉環(huán)境中的變量及被環(huán)境中的變量引用的變量的標(biāo)記,
在此之后還有標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,因?yàn)榄h(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
引用計(jì)數(shù)
另一種出鏡率不高的垃圾收集策略是引用計(jì)數(shù)。
它主要跟蹤記錄每個(gè)值被引用的次數(shù),當(dāng)某個(gè)值的引用次數(shù)為 0 時(shí),則說(shuō)明沒(méi)有辦法再訪問(wèn)這個(gè)值了,因此就可以將其占用的內(nèi)存空間回收。
但引用計(jì)數(shù)會(huì)存在一個(gè)循環(huán)引用的問(wèn)題:
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}也就是說(shuō),在函數(shù)執(zhí)行完之后,objA 和 objB 還將繼續(xù)存在,因此它們的引用次數(shù)永遠(yuǎn)不會(huì)是 0,假如這個(gè)函數(shù)被重復(fù)多次調(diào)用,就會(huì)導(dǎo)致大量?jī)?nèi)存得不到回收 。
為了避免這樣的循環(huán)引用問(wèn)題,最好在不使用它們的時(shí)候手動(dòng)斷開(kāi)連接:
objA.someOtherObject = null; objB.anotherObject = null;
當(dāng)垃圾收集器下次運(yùn)行時(shí),就會(huì)刪除這些值并回收它們所占用的內(nèi)存。
Tips:一旦數(shù)據(jù)不再有用,最好將其設(shè)為 null。
( 此條適合全局變量和全局對(duì)象的屬性,因?yàn)榫植孔兞繒?huì)在它們離開(kāi)執(zhí)行環(huán)境時(shí)自動(dòng)被解除引用 )。
ok,JavaScript 基礎(chǔ)的變量、作用域和垃圾回收咱就先講到這,下一篇會(huì)聊聊 JavaScript 面向?qū)ο蟮某绦蛟O(shè)計(jì)和函數(shù)表達(dá)式。
以上就是細(xì)說(shuō)JavaScript中的變量,作用域和垃圾回收的詳細(xì)內(nèi)容,更多關(guān)于JavaScript變量 作用域 垃圾回收的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用element-ui實(shí)現(xiàn)遠(yuǎn)程搜索兩種實(shí)現(xiàn)方式
這篇文章主要介紹了利用element-ui的兩種遠(yuǎn)程搜索實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-12-12
基于bootstrap實(shí)現(xiàn)bootstrap中文網(wǎng)巨幕效果
這篇文章主要介紹了基于bootstrap實(shí)現(xiàn)bootstrap中文網(wǎng)巨幕效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05
在Javascript里訪問(wèn)SharePoint列表數(shù)據(jù)的實(shí)現(xiàn)方法
在進(jìn)行SharePoint訂制的時(shí)候經(jīng)常會(huì)遇到開(kāi)發(fā)手段受限制的問(wèn)題,比如通常公司都會(huì)限制服務(wù)器的訪問(wèn)以及部署,很多開(kāi)發(fā)都只能夠在客戶端來(lái)進(jìn)行2011-05-05
layer實(shí)現(xiàn)登錄彈框,登錄成功后關(guān)閉彈框并調(diào)用父窗口的例子
今天小編就為大家分享一篇layer實(shí)現(xiàn)登錄彈框,登錄成功后關(guān)閉彈框并調(diào)用父窗口的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
微信小程序 數(shù)據(jù)緩存實(shí)現(xiàn)方法詳解
這篇文章主要介紹了微信小程序 數(shù)據(jù)緩存實(shí)現(xiàn)方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
javascript實(shí)現(xiàn)數(shù)字+字母驗(yàn)證碼的簡(jiǎn)單實(shí)例
本篇文章只要是對(duì)javascript實(shí)現(xiàn)數(shù)字+字母驗(yàn)證碼的簡(jiǎn)單實(shí)例進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-02-02
整理Javascript數(shù)組學(xué)習(xí)筆記
整理Javascript數(shù)組學(xué)習(xí)筆記,之前一系列的文章是跟我學(xué)習(xí)Javascript,本文就是進(jìn)一步學(xué)習(xí)javascript數(shù)組,希望大家繼續(xù)關(guān)注2015-11-11
通過(guò)循環(huán)優(yōu)化 JavaScript 程序
這篇文章主要介紹了通過(guò)循環(huán)優(yōu)化 JavaScript 程序,對(duì)于提高 JavaScript 程序的性能這個(gè)問(wèn)題,最簡(jiǎn)單同時(shí)也是很容易被忽視的方法就是學(xué)習(xí)如何正確編寫高性能循環(huán)語(yǔ)句。下面我們來(lái)學(xué)習(xí)一下吧2019-06-06

