自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文帶你解讀JavaScript中的變量、作用域和內(nèi)存問題

開發(fā) 前端
基本數(shù)據(jù)類型:undefined;null;number;boolean;string;按照值訪問的,可以操作保存在變量中的實(shí)際的值;引用數(shù)據(jù)類型:例如Array;不能直接訪問值,它是保存在內(nèi)存中的對(duì)象。

[[431723]]

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

  • 基本類型值:簡(jiǎn)單的數(shù)據(jù)段;
  • 引用類型值:多個(gè)值構(gòu)成的對(duì)象;

回顧:

基本數(shù)據(jù)類型:undefined;null;number;boolean;string;按照值訪問的,可以操作保存在變量中的實(shí)際的值;

引用數(shù)據(jù)類型:例如Array;不能直接訪問值,它是保存在內(nèi)存中的對(duì)象;

JavaScript不允許直接訪問內(nèi)存中的位置;即不能直接操作對(duì)象的內(nèi)存空間;

我們?cè)诓僮鲗?duì)象時(shí),其實(shí)是操作對(duì)象的引用,而不是對(duì)象;

注意:如果我們復(fù)制保存著某個(gè)對(duì)象的變量時(shí),那么兩個(gè)變量就會(huì)指向同一個(gè)對(duì)象,當(dāng)我們?yōu)閷?duì)象添加屬性時(shí),操作的就是實(shí)際的對(duì)象;

1.1 動(dòng)態(tài)的屬性

引用類型

  1. var person = new Object() // 創(chuàng)建一個(gè)對(duì)象 
  2. person.name = '張三' // 設(shè)置對(duì)象屬性 
  3. console.log(person.name) // 輸出對(duì)象屬性 

這個(gè)屬性會(huì)一直伴隨著對(duì)象,除非對(duì)象銷毀,否則該屬性會(huì)一直存在;

基本類型

  1. var name = 'Nick' 
  2. name.age = 20 
  3. console.log(name.age) // undefined 

只有引用值可以動(dòng)態(tài)添加后面可以使用的屬性;

1.2 復(fù)制變量值

基本類型

  1. var s = 'hello' 
  2. var s1 = s 
  3. console.log(s1) // 'hello' 
  4. console.log(s1 == s) // true 

解釋:

再新創(chuàng)建一個(gè)變量s1,它的值和s一樣,都是字符型'hello',所以s1 == s;兩者完全獨(dú)立,互不干擾;

引用類型

  1. var obj1 = new Object() 
  2. var obj2 = obj1 
  3. obj1.name = 'nick' 
  4. console.log(obj2.name
  5. console.log(obj2 == obj1) 

圖示:

 

我們的變量名obj1儲(chǔ)存的是一個(gè)對(duì)象的引用,它指向堆里面的一個(gè)對(duì)象(object),通過復(fù)制,我們只是復(fù)制了一個(gè)變量obj2,它的指向和obj1一樣都是指向object,所以設(shè)置完obj1.name = 'nick',之后修改的是指向的對(duì)象的屬性,由于obj2也是指向這個(gè)對(duì)象,所以obj2.name = 'nick';

1.3 傳遞參數(shù)

函數(shù)的傳參類似于我們變量的復(fù)制,我們來查看一下;

1.3.1 基本類型的傳參

  1. function addnum(num){ 
  2.     num += 10 
  3.     return num 
  4. var count = 20 
  5. res = addnum(count
  6. console.log(count) // 20 
  7. console.log(res) // 30 

解釋:參數(shù)作為函數(shù)的局部變量,其實(shí)并不會(huì)對(duì)全局變量造成影響,所以count還是20;

1.3.2 引用類型的傳參

  1. function test(obj){ 
  2.      obj.age = 20 
  3. var obj1 = new Object() 
  4. test(obj1) 
  5. console.log(obj1.age) // 20 

解釋:此處obj和obj1引用的是同一個(gè)對(duì)象;那么問題來了,針對(duì)于引用類型,參數(shù)的傳遞是按照值還是按照引用呢?看下面的例子:

  1. function test(obj){ 
  2.      obj.age = 20 
  3.      obj = new Object() 
  4.      obj.age = 21 
  5. var obj1 = new Object() 
  6. test(obj1) 
  7. console.log(obj1.age) // 20 

這里如果是按照引用傳遞,obj1的指向應(yīng)該變成函數(shù)內(nèi)部創(chuàng)建的對(duì)象,并且其age值為21,但是實(shí)際輸出為20,說明即使在函數(shù)內(nèi)部修改了參數(shù)的值,其原始引用仍未改變;

函數(shù)內(nèi)部創(chuàng)建的obj會(huì)隨著函數(shù)調(diào)用結(jié)束而被銷毀;

二、作用域

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

執(zhí)行環(huán)境: 定義了變量或函數(shù)有權(quán)訪問的其它數(shù)據(jù),決定了它們的行為。

全局執(zhí)行環(huán)境是最外層的執(zhí)行環(huán)境。根據(jù) ECMAScript實(shí)現(xiàn)的宿主環(huán)境,表示全局執(zhí)行環(huán)境的對(duì)象可能不一樣。在瀏覽器中,全局執(zhí)行環(huán)境就是我們常說的 window 對(duì)象。

執(zhí)行環(huán)境中的代碼在執(zhí)行的時(shí)候,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain)。這個(gè)作用域鏈決定了各級(jí)上下文中的代碼在訪問變量和函數(shù)時(shí)的順序。

代碼正在執(zhí)行的執(zhí)行環(huán)境的變量對(duì)象始終位于作用域鏈的最前端。如果上下文是函數(shù),則其活動(dòng)對(duì)象(activation object)用作變量對(duì)象?;顒?dòng)對(duì)象最初只有一個(gè)定義變量:arguments 。(全局執(zhí)行環(huán)境中沒有這個(gè)變量。)

作用域鏈中的下一個(gè)變量對(duì)象來自包含執(zhí)行環(huán)境,再下一個(gè)對(duì)象來自再下一個(gè)包含執(zhí)行環(huán)境。以此類推直至全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈的最后一個(gè)變量對(duì)象。

代碼執(zhí)行時(shí)的標(biāo)識(shí)符解析是通過沿作用域鏈逐級(jí)搜索標(biāo)識(shí)符名稱完成的。搜索過程始終從作用域鏈的最前端開始,然后逐級(jí)往后,直到找到標(biāo)識(shí)符。(如果沒有找到標(biāo)識(shí)符,那么通常會(huì)報(bào)錯(cuò)。)

  1. var color = "blue"
  2. function changeColor() { 
  3.     let anotherColor = "red"
  4.     function swapColors() { 
  5.         let tempColor = anotherColor; 
  6.         anotherColor = color; 
  7.         color = tempColor; 
  8.         // 這里可以訪問 color、anotherColor 和 tempColor 
  9.     swapColors();// 這里可以訪問 color 和 anotherColor,但訪問不到 tempColor 
  10.  
  11. changeColor();// 這里只能訪問 color 

以上代碼涉及 3 個(gè)執(zhí)行環(huán)境:全局執(zhí)行環(huán)境、 changeColor() 的局部執(zhí)行環(huán)境和 swapColors() 的局部執(zhí)行環(huán)境。全局執(zhí)行環(huán)境中有一個(gè)變量 color 和一個(gè)函數(shù) changeColor() 。changeColor() 的局部執(zhí)行環(huán)境中有一個(gè)變量 anotherColor 和一個(gè)函數(shù) swapColors() ,但在這里可以訪問全局上下文中的變量 color 。其它函數(shù)同理;

2.2 延長作用域鏈

雖然執(zhí)行環(huán)境主要有全局環(huán)境和局部環(huán)境兩種,但有其他方式來延長作用域鏈。某些語句會(huì)導(dǎo)致在作用域鏈前端臨時(shí)添加一個(gè)變量對(duì)象,這個(gè)對(duì)象在代碼執(zhí)行后會(huì)被刪除。通常在兩種情況下會(huì)出現(xiàn)這個(gè)現(xiàn)象,即代碼執(zhí)行到下面任意一種情況時(shí):

  • try / catch 語句的 catch 塊;
  • with 語句;

這兩種情況下,都會(huì)在作用域鏈前端添加一個(gè)變量對(duì)象。對(duì) with 語句來說,會(huì)向作用域鏈前端添加指定的對(duì)象;對(duì) catch 語句而言,則會(huì)創(chuàng)建一個(gè)新的變量對(duì)象,這個(gè)變量對(duì)象會(huì)包含要拋出的錯(cuò)誤對(duì)象的聲明。如下所示:

  1. function buildUrl() { 
  2.     let qs = "?debug=true"
  3.     with(location){ 
  4.         let url = href + qs; 
  5.     return url; 

這里, with 語句接收 location 對(duì)象,因此 location 會(huì)被添加到作用域鏈前端。buildUrl() 函數(shù)中定義了一個(gè)變量 qs 。當(dāng) with 語句中的代碼引用變量 href 時(shí),實(shí)際上引用的是location.href ,也就是自己變量對(duì)象的屬性。在引用 qs 時(shí),引用的則是定義在buildUrl() 中的那個(gè)變量,它位于函數(shù)環(huán)境的變量對(duì)象中;至于with語句內(nèi)部,則定義了一個(gè)url的變量,因而url變成函數(shù)執(zhí)行環(huán)境的一部分,可以作為函數(shù)的值被返回;

2.3 沒有塊級(jí)作用域

  1. if(true){ 
  2.     var color = 'red' 
  3. console.log(color) // red 

這里我們很疑惑,這個(gè)color在{}中,不應(yīng)該是局部變量嗎?為什么在全局中也能夠輸出;

解釋:在這里if語句聲明的變量將會(huì)添加到當(dāng)前的執(zhí)行環(huán)境(即全局環(huán)境),使用for語句也是一樣;

  1. for(var i = 0;i < 5;i++){ 
  2.    console.log('i'
  3. console.log(i) // 5 

聲明變量

使用var聲明的變量會(huì)被自動(dòng)添加到最接近的環(huán)境中,在函數(shù)內(nèi)部聲明,最接近的環(huán)境就是函數(shù)的局部環(huán)境;在with語句中,最接近的環(huán)境就是函數(shù)環(huán)境;如果沒有使用var聲明變量,那么就會(huì)自動(dòng)添加到全局環(huán)境中;

  1. function test(a,b){ 
  2.     var sum = a + b 
  3.     return sum 
  4. console.log(test(10,20)) // 30 
  5. console.log(sum) // ReferenceError: sum is not defined 

這里原因就不做過多解釋了,但是如果我們?cè)谠摵瘮?shù)內(nèi)部省略var,直接聲明sum,那么在函數(shù)外部也是可以輸出sum的,因?yàn)榇藭r(shí)他就是一個(gè)全局變量;

在JavaScript中,不聲明而直接初始化變量是一種錯(cuò)誤做法;

三、垃圾回收

3.1 垃圾回收機(jī)制

JavaScript 是使用垃圾回收的語言,也就是說執(zhí)行環(huán)境負(fù)責(zé)在代碼執(zhí)行時(shí)管理內(nèi)存。JavaScript 通過自動(dòng)內(nèi)存管理實(shí)現(xiàn)內(nèi)存分配和閑置資源回收。

基本過程:確定某個(gè)變量不會(huì)再使用,然后釋放它占用的內(nèi)存。

這個(gè)過程是周期性的,即垃圾回收程序每隔一定時(shí)間就會(huì)自動(dòng)運(yùn)行。垃圾回收過程是一個(gè)近似且不完美的方案,因?yàn)槟硥K內(nèi)存是否還有用,屬于“不可判定的”問題,意味著靠算法是解決不了的。

3.2 性能問題

垃圾回收程序會(huì)周期性運(yùn)行,如果內(nèi)存中分配了很多變量,則可能造成性能損失,因此垃圾回收的時(shí)間調(diào)度很重要。尤其是在內(nèi)存有限的移動(dòng)設(shè)備上,垃圾回收有可能會(huì)明顯拖慢渲染的速度和幀速率。

現(xiàn)代垃圾回收程序會(huì)基于對(duì) JavaScript 運(yùn)行時(shí)環(huán)境的探測(cè)來決定何時(shí)運(yùn)行。探測(cè)機(jī)制因引擎而異,但基本上都是根據(jù)已分配對(duì)象的大小和數(shù)量來判斷的。

由于調(diào)度垃圾回收程序方面的問題會(huì)導(dǎo)致性能下降,它的策略是根據(jù)分配數(shù),比如分配了 256 個(gè)變量、4096 個(gè)對(duì)象/數(shù)組字面量和數(shù)組槽位(slot),或者 64KB 字符串。只要滿足其中某個(gè)條件,垃圾回收程序就會(huì)運(yùn)行。

這樣實(shí)現(xiàn)的問題在于,分配那么多變量的腳本,很可能在其整個(gè)生命周期內(nèi)始終需要那么多變量,結(jié)果就會(huì)導(dǎo)致垃圾回收程序過于頻繁地運(yùn)行。

由于對(duì)性能的嚴(yán)重影響,IE7最終更新了垃圾回收程序。IE7 發(fā)布后,JavaScript 引擎的垃圾回收程序被調(diào)優(yōu)為動(dòng)態(tài)改變分配變量、字面量或數(shù)組槽位等會(huì)觸發(fā)垃圾回收的閾值。IE7 的起始閾值都與 IE6 的相同。如果垃圾回收程序回收的內(nèi)存不到已分配的 15%,這些變量、字面量或數(shù)組槽位的閾值就會(huì)翻倍。如果有一次回收的內(nèi)存達(dá)到已分配的 85%,則閾值重置為默認(rèn)值。這么一個(gè)簡(jiǎn)單的修改,極大地提升了重度依賴 JavaScript 的網(wǎng)頁在瀏覽器中的性能。

3.3 管理內(nèi)存

為什么需要管理內(nèi)存?

在使用垃圾回收的編程環(huán)境中,JavaScript 運(yùn)行在一個(gè)內(nèi)存管理與垃圾回收都很特殊的環(huán)境。分配給瀏覽器的內(nèi)存通常比分配給桌面軟件的要少很多,分配給移動(dòng)瀏覽器的就更少了。這更多出于安全考慮而不是別的,就是為了避免運(yùn)行大量 JavaScript 的網(wǎng)頁耗盡系統(tǒng)內(nèi)存而導(dǎo)致操作系統(tǒng)崩潰。這個(gè)內(nèi)存限制不僅影響變量分配,也影響調(diào)用棧以及能夠同時(shí)在一個(gè)線程中執(zhí)行的語句數(shù)量。

接觸引用

將內(nèi)存占用量保持在一個(gè)較小的值可以讓頁面性能更好。優(yōu)化內(nèi)存占用的最佳手段就是保證在執(zhí)行代碼時(shí)只保存必要的數(shù)據(jù)。如果數(shù)據(jù)不再必要,那么把它設(shè)置為 null ,從而釋放其引用。

局部變量在超出作用域后會(huì)被自動(dòng)解除引用,如下所示:

  1. function createPerson(name){ 
  2.     let localPerson = new Object(); 
  3.     localPerson.name = name
  4.     return localPerson; 
  5. let globalPerson = createPerson("Nicholas"); // 解除 globalPerson 對(duì)值的引用 
  6. globalPerson = null

在上面的代碼中,變量 globalPerson 保存著 createPerson() 函數(shù)調(diào)用返回的值。在 createPerson()內(nèi)部, localPerson 創(chuàng)建了一個(gè)對(duì)象并給它添加了一個(gè) name 屬性。然后, localPerson 作為函數(shù)值被返回,并被賦值給 globalPerson 。localPerson 在 createPerson() 執(zhí)行完成超出執(zhí)行環(huán)境后會(huì)自動(dòng)被解除引用,不需要顯式處理。但 globalPerson 是一個(gè)全局變量,應(yīng)該在不再需要時(shí)手動(dòng)解除其引用,最后一行就是這么做的。不過要注意,解除對(duì)一個(gè)值的引用并不會(huì)自動(dòng)導(dǎo)致相關(guān)內(nèi)存被回收。解除引用的關(guān)鍵在于確保相關(guān)的值已經(jīng)不在執(zhí)行環(huán)境里了,因此它在下次垃圾回收時(shí)會(huì)被回收。

 

責(zé)任編輯:姜華 來源: IT共享之家
相關(guān)推薦

2021-10-11 10:19:48

Javascript 高階函數(shù)前端

2021-10-14 10:25:05

JavaScript類型函數(shù)

2021-09-02 10:24:54

JavaScript前端語言

2021-09-09 10:26:26

Javascript 文檔對(duì)象前端

2016-12-19 11:10:32

JavaScript變量作用域

2022-04-08 09:01:14

CSS自定義屬性前端

2021-11-06 10:18:30

Python變量常量

2021-09-06 10:21:27

JavaScript表單對(duì)象 前端

2021-09-07 09:46:40

JavaScriptGenerator函數(shù)

2024-11-19 13:20:55

2023-03-31 08:16:53

Flutter優(yōu)化內(nèi)存管理

2011-04-18 09:31:35

JavaScript

2019-08-06 09:00:00

JavaScript函數(shù)式編程前端

2021-06-06 13:06:34

JVM內(nèi)存分布

2021-09-08 17:42:45

JVM內(nèi)存模型

2013-09-05 10:07:34

javaScript變量

2021-05-25 10:15:20

JavaScript 前端作用域

2020-06-24 12:01:16

Python數(shù)據(jù)類字符

2022-12-20 07:39:46

2023-11-20 08:18:49

Netty服務(wù)器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)