欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

細(xì)說JavaScript中的變量,作用域和垃圾回收

 更新時(shí)間:2022年11月11日 11:20:20   作者:hellocoder2029  
這篇文章主要和大家介紹一下JavaScript中的變量,作用域和垃圾回收的定義與使用,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)JavaScript有一定的幫助,需要的可以參考一下

在 JavaScript 中,數(shù)據(jù)類型可分為基本類型和引用類型,

基本類型有六種:Null,Undefined,String,Boolean,Number,Symbol;

而引用類型就是傳說中的 Object 了。

其中基本類型是按值傳遞,而引用類型的值是按引用訪問的,所以在操作對象時(shí),實(shí)際上是在操作對象的引用而不是實(shí)際的對象 ( ps:在為對象添加屬性時(shí),操作的是實(shí)際的對象 )。

關(guān)于基本類型和引用類型的不同,大概有以下幾點(diǎn):

1、引用類型是動態(tài)的屬性,而基本類型不是。

對于引用類型,我們可以為其添加、刪除屬性和方法,但不能給基本類型的值添加屬性:

// 基本類型
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ù)制基本類型的值,會將值復(fù)制到為新變量分配的位置上:

var num1 = 5;
var num2 = num1;

當(dāng)使用 num1 的值來初始化 num2 時(shí),num2 中也保存了值5,但該值只是 num1 中 5 的一個(gè)副本,兩個(gè)變量不會互相影響。

當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),傳遞的是一個(gè)指針,其指向存儲在堆中的一個(gè)對象,在復(fù)制結(jié)束后,兩個(gè)變量實(shí)際上將引用同一個(gè)對象,改變其中一個(gè)變量就會影響另一個(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ù)都是按值傳遞的。也就是說,把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣?;绢愋椭档膫鬟f如同基本類型變量的復(fù)制一樣,而引用類型的傳遞,則如同引用類型變量的復(fù)制一樣,這一點(diǎn)確實(shí)會引起很多小伙伴的爭議,歡迎討論~

  • 在向參數(shù)傳遞基本類型的值時(shí),被傳遞的值會被復(fù)制給一個(gè)局部變量( 即 arguments 對象中的一個(gè)元素 )。
  • 在向參數(shù)傳遞引用類型的值時(shí),會把這個(gè)值在內(nèi)存中的地址復(fù)制給一個(gè)局部變量,因此該局部變量的變化會反映到函數(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è)對象,并將其保存在了變量 person 中。然后,這個(gè)對象被傳遞到 setName () 函數(shù)中就被復(fù)制給了 obj,在這個(gè)函數(shù)內(nèi)部,obj 和 person 引用的是同一個(gè)對象。

很多小伙伴會認(rèn)為該參數(shù)是按引用傳遞的,為了證明對象是按值傳遞的,再看下這個(gè)修改過的代碼:

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 就會自動被修改為指向其 name 屬性為 ‘juejin’ 的新對象。但接下來再訪問 person.name 時(shí)仍然顯示 ‘Fly_001’,這說明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍保持不變。( 實(shí)際上,當(dāng)在函數(shù)內(nèi)部重寫 obj 時(shí),這個(gè)變量引用的就是一個(gè)局部對象了,其將在函數(shù)執(zhí)行完畢后立即被銷毀。)

4、檢測類型的操作符不同。

檢測基本類型適宜用 typeof 操作符

alert(typeof 'Fly_001'); // 'string';
alert(typeof []); // 'object';

因?yàn)?typeof 操作符的返回值為 'undefined','string','boolean','number','symbol','object','function' 其中之一。

它可以很友好地指出某一具體基本類型,而對于引用類型則籠統(tǒng)地返回 'object'( typeof 對 數(shù)組、正則、null 都會返回 'object' )。

在檢測引用類型時(shí)更適合用 instanceof 操作符:

result = varible instanceof constructor;

如果變量是給定引用類型的實(shí)例( 根據(jù)它的原型鏈來識別 ),那 instanceof 操作符將會返回 true。

執(zhí)行環(huán)境及作用域

下面聊下 JavaScript 中很重要的一個(gè)概念 —— 執(zhí)行環(huán)境

JS 中每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對象,在 Web 瀏覽器中,全局執(zhí)行環(huán)境是 window 對象,因此所有全局變量和函數(shù)都是作為 window 對象的屬性和方法創(chuàng)建的。

某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境將會被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀,全局執(zhí)行環(huán)境直至網(wǎng)頁或?yàn)g覽器關(guān)閉時(shí)才被銷毀( 如果存在閉包,情況又有所不同,會在后面幾篇提到 ,多謝 吳hr 指正)。

每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會被推入一個(gè)環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧會將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。

var color = 'blue';

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

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;

        // 這里可以訪問 color、anotherColor 和 tempColor;
    }

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

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

以上代碼共涉及 3 個(gè)執(zhí)行環(huán)境:全局環(huán)境、changeColor() 的局部環(huán)境和 swapColor() 局部環(huán)境。其中,內(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)境。

延長作用域鏈

雖然執(zhí)行環(huán)境的類型總共只有兩種 —— 全局和局部 (函數(shù)),但還是兩種辦法來延長作用域鏈~ 就是通過 try-catch 語句的 catch 塊和 with 語句。

這兩個(gè)語句都會在作用域鏈的前端添加一個(gè)變量對象。對 with 語句來說,會將指定的對象添加到作用域鏈中;對于 catch 語句來說,會創(chuàng)建一個(gè)新的變量對象,其中包含的是被拋出的錯誤對象的聲明。

沒有塊級作用域

JavaScript 沒有塊級作用域經(jīng)常會導(dǎo)致理解上的困惑 。在其它類 C 的語言中,由花括號封閉的代碼塊都有自己的作用域,即執(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án)境中。在函數(shù)內(nèi)部,最接近的環(huán)境就是函數(shù)的局部環(huán)境,若初始化變量時(shí)沒有使用 var 聲明,該變量會自動被添加到全局環(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ù)外部是訪問不到的。如果省略 var 關(guān)鍵字,這時(shí) sum 是可以訪問到的( 不過在嚴(yán)格模式下,初始化未聲明的變量會報(bào) 'xxx is not defined' 錯 )。

模仿塊級作用域

雖然 js 沒有塊級作用域,但我們可以用匿名函數(shù)來模仿塊級作用域~,語法格式如下:

(function() {
    // 這里是塊級作用域;
}) ();

將函數(shù)聲明包含在一對圓括號里,表示它實(shí)際上是一個(gè)函數(shù)表達(dá)式,而緊隨其后的圓括號會立即調(diào)用這個(gè)函數(shù)。實(shí)際上就相當(dāng)于:

var someFunction() {
    // 這里是塊級作用域;
};
someFunction();

同時(shí)因?yàn)?JavaScript 將 function 關(guān)鍵字當(dāng)作一個(gè)函數(shù)聲明的開始,后面不能直接跟圓括號,而函數(shù)表達(dá)式后面可以跟圓括號,所以將函數(shù)聲明加上圓括號轉(zhuǎn)換成函數(shù)表達(dá)式。

無論在什么地方,只要臨時(shí)需要一些變量,就可以使用私有作用域:

function outputNumbers(count) {
    (function () {
        for (var i = 0; i < count; i ++) {
            alert(i);
        }
    }) ();

    alert(i); // 會導(dǎo)致錯誤,讀取不到 i;
}

因?yàn)樵谀涿瘮?shù)中定義的任何變量,都會在執(zhí)行結(jié)束時(shí)立即銷毀,所以變量 i 只能在循環(huán)中使用。

查詢標(biāo)識符

當(dāng)在某個(gè)環(huán)境中為了讀取或?qū)懭攵靡粋€(gè)變量或函數(shù)名 ( 標(biāo)識符 ),必須通過搜索來確定該它實(shí)際代表什么。

搜索過程從作用域的前端開始,向上逐級查找,如果存在一個(gè)局部的變量的定義,則停止搜索,即同名局部變量將覆蓋同名全局變量:

var color = 'blue';

function getColor() {
    var color = 'red'; // 局部變量;
    return color;
}

alert(getColor()); // 'red';
alert(window.color); // 'blue';

垃圾收集

JavaScript 具有自動垃圾收集機(jī)制,所以開發(fā)人員不必?fù)?dān)心內(nèi)存使用問題,是不是很開森 ,但最好還是了解下 。

首先我們來分析函數(shù)中局部變量的正常生命周期:局部變量只在函數(shù)執(zhí)行的過程中存在,函數(shù)執(zhí)行結(jié)束后就會釋放掉它們的內(nèi)存以供將來使用。所以 垃圾收集器必須跟蹤哪些變量有用、哪些變量沒用,具體到瀏覽器的實(shí)現(xiàn)有兩個(gè)策略:標(biāo)記清除和引用計(jì)數(shù)

標(biāo)記清除

此乃 JavaScript 中最常用的垃圾收集機(jī)制。

垃圾收集器在運(yùn)行的時(shí)候會把存儲在內(nèi)存中的所有變量都加上標(biāo)記,然后去掉環(huán)境中的變量及被環(huán)境中的變量引用的變量的標(biāo)記,

在此之后還有標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,因?yàn)榄h(huán)境中的變量已經(jīng)無法訪問到這些變量了。最后垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。

引用計(jì)數(shù)

另一種出鏡率不高的垃圾收集策略是引用計(jì)數(shù)。

它主要跟蹤記錄每個(gè)值被引用的次數(shù),當(dāng)某個(gè)值的引用次數(shù)為 0 時(shí),則說明沒有辦法再訪問這個(gè)值了,因此就可以將其占用的內(nèi)存空間回收。

但引用計(jì)數(shù)會存在一個(gè)循環(huán)引用的問題:

function problem() {
    var objA = new Object();
    var objB = new Object();

    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}

也就是說,在函數(shù)執(zhí)行完之后,objA 和 objB 還將繼續(xù)存在,因此它們的引用次數(shù)永遠(yuǎn)不會是 0,假如這個(gè)函數(shù)被重復(fù)多次調(diào)用,就會導(dǎo)致大量內(nèi)存得不到回收 。

為了避免這樣的循環(huán)引用問題,最好在不使用它們的時(shí)候手動斷開連接:

objA.someOtherObject = null;
objB.anotherObject = null;

當(dāng)垃圾收集器下次運(yùn)行時(shí),就會刪除這些值并回收它們所占用的內(nèi)存。

Tips:一旦數(shù)據(jù)不再有用,最好將其設(shè)為 null。

( 此條適合全局變量和全局對象的屬性,因?yàn)榫植孔兞繒谒鼈冸x開執(zhí)行環(huán)境時(shí)自動被解除引用 )。

ok,JavaScript 基礎(chǔ)的變量、作用域和垃圾回收咱就先講到這,下一篇會聊聊 JavaScript 面向?qū)ο蟮某绦蛟O(shè)計(jì)和函數(shù)表達(dá)式。

以上就是細(xì)說JavaScript中的變量,作用域和垃圾回收的詳細(xì)內(nèi)容,更多關(guān)于JavaScript變量 作用域 垃圾回收的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論