摘要:內(nèi)存泄漏指的是,程序之前需要用到部分內(nèi)存,而這部分內(nèi)存在用完之后并沒有返回到內(nèi)存池。基本事件遞歸調(diào)用為什么是單線程的一個線程代表著在同一時間段內(nèi)可以多帶帶執(zhí)行的程序部分的數(shù)目。
原文地址:How Does JavaScript Really Work? (Part 2)
原文作者:Priyesh Patel
照片來源于Unsplash上的Samuel Zeller
在這篇文章的第一部分,我簡要概述了編程語言的一般工作機制,并深入探討了 V8 引擎的管道。第二部分將介紹一些更重要的概念,這些概念是每一個 JavaScript 程序員都必須了解的,并且不僅僅和 V8 引擎有關(guān)。
對于任何一個程序員來說,最關(guān)注的兩個問題無非就是:時間復(fù)雜度和空間復(fù)雜度。第一部分介紹了 V8 為改進 JavaScript 執(zhí)行時間所做的速度提升和優(yōu)化,第二部分則將著重介紹內(nèi)存管理方面的知識。
Orinoco 的 logo:V8 的垃圾回收器
每當(dāng)你在 JavaScript 程序中定義了一個變量、常量或者對象時,你都需要一個地方來存儲它。這個地方就是內(nèi)存堆。
當(dāng)遇到語句 var a = 10 的時候,內(nèi)存會分配一個位置用于存儲 a 的值
可用內(nèi)存是有限的,而復(fù)雜的程序可能有很多變量和嵌套對象,因此合理地使用可用內(nèi)存非常重要。
和諸如 C 這種需要顯式分配和釋放內(nèi)存的語言不同,JavaScript 提供了自動垃圾回收機制。一旦對象/變量離開了上下文并且不再使用,它的內(nèi)存就會被回收并返還到可用內(nèi)存池中。
在 V8 中,垃圾回收器的名字叫做 Orinoco,它的處理過程非常高效。這篇文章有相關(guān)解釋。
標(biāo)記與清除算法標(biāo)記和清除算法
我們通常會使用這種簡單有效的算法來判定可以從內(nèi)存堆中安全清除的對象。算法的工作方式正如其名:將對象標(biāo)記為可獲得/不可獲得,并將不可獲得的對象清除。
垃圾回收器周期性地從根部或者全局對象開始,移向被它們引用的對象,接著再移向被這些對象引用的對象,以此類推。所有不可獲得的對象會在之后被清除。
雖然垃圾回收器很高效,但是開發(fā)者不應(yīng)該就此將內(nèi)存管理的問題束之高閣。管理內(nèi)存是一個很復(fù)雜的過程,哪一塊內(nèi)存不再需要并不是單憑一個算法就能決定的。
內(nèi)存泄漏指的是,程序之前需要用到部分內(nèi)存,而這部分內(nèi)存在用完之后并沒有返回到內(nèi)存池。
下面是一些會導(dǎo)致你的程序出現(xiàn)內(nèi)存泄漏的常見錯誤:
全局變量:如果你不斷地創(chuàng)建全局變量,不管有沒有用到它們,它們都將滯留在程序的整個執(zhí)行過程中。如果這些變量是深層嵌套對象,將會浪費大量內(nèi)存。
var a = { ... } var b = { ... } function hello() { c = a; // 這是一個你沒有意識到的全局變量 }
如果你試圖訪問一個此前沒有聲明過的變量,那么將在全局作用域中創(chuàng)建一個變量。在上面的例子中,c 是沒有使用 var 關(guān)鍵字顯式創(chuàng)建的變量/對象。
事件監(jiān)聽器:為了增強網(wǎng)站的交互性或者是制作一些浮華的動畫,你可能會創(chuàng)建大量的事件監(jiān)聽器。而用戶在你的單頁面應(yīng)用中移向其他頁面時,你又忘記移除這些監(jiān)聽器,那么也可能會導(dǎo)致內(nèi)存泄漏。當(dāng)用戶在這些頁面來回移動的時候,這些監(jiān)聽器會不斷增加。
var element = document.getElementById("button"); element.addEventListener("click", onClick)
Intervals 和 Timeouts:當(dāng)在這些閉包中引用對象時,除非閉包本身被清除,否則不會清除相關(guān)對象。
setInterval(() => { // 引用對象 } // 這時候忘記清除計時器 // 那么將導(dǎo)致內(nèi)存泄漏!
移除 DOM 元素:這個問題很常見,類似于全局變量導(dǎo)致的內(nèi)存泄漏。DOM 元素存在于對象圖內(nèi)存和 DOM 樹中。用例子來解釋可能會更好:
var terminator = document.getElementById("terminate"); var badElem = document.getElementById("toDelete"); terminator.addEventListener("click", function() {memory badElem.remove(); });
在你通過 id = ‘terminate’ 點擊了按鈕之后,toDelete 會從 DOM 中移除。不過,由于它仍然被監(jiān)聽器引用,為這個對象分配的內(nèi)存并不會被釋放。
var terminator = document.getElementById("terminate"); terminator.addEventListener("click", function() { var badElem = document.getElementById("toDelete"); badElem.remove(); });
badElem 是局部變量,在移除操作完成之后,內(nèi)存將會被垃圾回收器回收。
調(diào)用棧棧是一種遵循 LIFO(先進后出)規(guī)則的數(shù)據(jù)結(jié)構(gòu),用于存儲和獲取數(shù)據(jù)。JavaScript 引擎通過棧來記住一個函數(shù)中最后執(zhí)行的語句所在的位置。
function multiplyByTwo(x) { return x*2; } function calculate() { const sum = 4 + 2; return multiplyByTwo(sum); } calculate() var hello = "some more code follows"
1.引擎了解到我們的程序中有兩個函數(shù)
2.運行 calculate() 函數(shù)
3.將 calculate 壓棧并計算兩數(shù)之和
4.運行 multiplyByTwo() 函數(shù)
5.將 multiplyByTwo 函數(shù)壓棧并執(zhí)行算術(shù)計算 x*2
6.在返回結(jié)果的同時,將 multiplyByTwo() 從棧中彈出,之后回到 calculate() 函數(shù)
7.在 calculate() 函數(shù)返回結(jié)果的同時,將 calculate() 從棧中彈出,繼續(xù)執(zhí)行后面的代碼
在不對棧執(zhí)行彈出的情況下,可連續(xù)壓棧的數(shù)目取決于棧的大小。如果超過了這個界限之后還不斷地壓棧,最終會導(dǎo)致棧溢出。chrome 瀏覽器將會拋出一個錯誤以及被稱為棧幀的??煺?。
遞歸:遞歸指的是函數(shù)調(diào)用自身。遞歸可以大幅度地減少執(zhí)行算法所花費的時間(時間復(fù)雜度),不過它的理解和實施較為復(fù)雜。
下面的例子中,基本事件永遠不會執(zhí)行,lonley 函數(shù)在沒有返回值的情況下不斷地調(diào)用自身,最終會導(dǎo)致棧溢出。
function lonely() { if (false) { return 1; // 基本事件 } lonely(); // 遞歸調(diào)用 }為什么 JavaScript 是單線程的?
一個線程代表著在同一時間段內(nèi)可以多帶帶執(zhí)行的程序部分的數(shù)目。要想查看一門語言是單線程的還是多線程的,最簡單的方式就是了解它有多少個調(diào)用棧。JS 只有一個,所以它是單線程語言。
這樣不是會阻礙程序運行嗎?如果我運行多個耗時的阻塞操作,例如 HTTP 請求,那么程序必須得在每一個操作得到響應(yīng)之后才能執(zhí)行后面的代碼。
為了解決這個問題,我們需要找到一種可以在單線程下異步完成任務(wù)的辦法。事件循環(huán)就是用來發(fā)揮這個作用的。
到現(xiàn)在為止,我們談到的內(nèi)容大多包含在 V8 里面,但是如果你去查看 V8 的代碼庫,你會發(fā)現(xiàn)它并不包含例如 setTimeout 或者 DOM 的實現(xiàn)。事實上,除了運行引擎之外,JS 還包括瀏覽器提供的 Web API,這些 API 用于拓展 JS。
關(guān)于事件循環(huán)的概念,菲利普·羅伯茨講得比我更好,可以看下面這段視頻:
http://player.youku.com/embed...
關(guān)于制作一門編程語言,其實還有很多內(nèi)容,并且語言的實現(xiàn)在這些年也是不斷變化的。我希望這兩篇博客可以幫助你成為一名更好的 JS 程序員,并且接受 JS 中那些晦澀難懂的內(nèi)容 。對于諸如“V8”,“事件循環(huán)”,“調(diào)用?!边@樣的術(shù)語,你現(xiàn)在應(yīng)該熟悉了。
大部分的學(xué)生(比如我)是從一個新的框架起步,之后再去學(xué)習(xí)原生 JS?,F(xiàn)在他們應(yīng)該熟悉代碼背后發(fā)生的事情了,反過來,這將幫助他們寫出更好的代碼。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/104250.html
摘要:文章的第二部分涵蓋了內(nèi)存管理的概念,不久后將發(fā)布。的標(biāo)準(zhǔn)化工作是由國際組織負責(zé)的,相關(guān)規(guī)范被稱為或者。隨著分析器和編譯器不斷地更改字節(jié)碼,的執(zhí)行性能逐漸提高。 原文地址:How Does JavaScript Really Work? (Part 1) 原文作者:Priyesh Patel 譯者:Chor showImg(https://segmentfault.com/img...
摘要:探討判斷橫豎屏的最佳實現(xiàn)前端掘金在移動端,判斷橫豎屏的場景并不少見,比如根據(jù)橫豎屏以不同的樣式來適配,抑或是提醒用戶切換為豎屏以保持良好的用戶體驗。 探討判斷橫豎屏的最佳實現(xiàn) - 前端 - 掘金在移動端,判斷橫豎屏的場景并不少見,比如根據(jù)橫豎屏以不同的樣式來適配,抑或是提醒用戶切換為豎屏以保持良好的用戶體驗。 判斷橫豎屏的實現(xiàn)方法多種多樣,本文就此來探討下目前有哪些實現(xiàn)方法以及其中的優(yōu)...
摘要:究竟是什么是一個運行時環(huán)境。對此請求的響應(yīng)需要時間,但兩個用戶數(shù)據(jù)請求可以獨立并同時執(zhí)行。所以這會使不太適合多線程任務(wù)。這種非阻塞消除了多線程的需要,因為服務(wù)器可以同時處理多個請求。該事件將等待毫秒,然后回調(diào)函數(shù)。系統(tǒng)事件來自庫的核心。 Node.js究竟是什么? Node.js是一個JavaScript運行時環(huán)境。聽起來不錯,但這是什么意思?這是如何運作的? Node運行時環(huán)境包含執(zhí)...
摘要:究竟是什么是一個運行時環(huán)境。對此請求的響應(yīng)需要時間,但兩個用戶數(shù)據(jù)請求可以獨立并同時執(zhí)行。所以這會使不太適合多線程任務(wù)。這種非阻塞消除了多線程的需要,因為服務(wù)器可以同時處理多個請求。該事件將等待毫秒,然后回調(diào)函數(shù)。系統(tǒng)事件來自庫的核心。 Node.js究竟是什么? Node.js是一個JavaScript運行時環(huán)境。聽起來不錯,但這是什么意思?這是如何運作的? Node運行時環(huán)境包含執(zhí)...
閱讀 2519·2021-09-09 09:33
閱讀 2878·2019-08-30 15:56
閱讀 3164·2019-08-30 14:21
閱讀 915·2019-08-30 13:01
閱讀 880·2019-08-26 18:27
閱讀 3598·2019-08-26 13:47
閱讀 3468·2019-08-26 10:26
閱讀 1600·2019-08-23 18:38