亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

新手入門帶你學習JavaScript引擎運行原理

2024-05-06 16:52:40
字體:
來源:轉載
供稿:網友

一些名詞

JS引擎 — 一個讀取代碼并運行的引擎,沒有單一的“JS引擎”;,每個瀏覽器都有自己的引擎,如谷歌有V。

作用域 — 可以從中訪問變量的“區域”。

詞法作用域— 在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的,因此當詞法分析器處理代碼時會保持作用域不變。

塊作用域 — 由花括號{}創建的范圍

作用域鏈 — 函數可以上升到它的外部環境(詞法上)來搜索一個變量,它可以一直向上查找,直到它到達全局作用域。

同步 — 一次執行一件事, “同步”引擎一次只執行一行,JavaScript是同步的。

異步 — 同時做多個事,JS通過瀏覽器API模擬異步行為

事件循環(Event Loop) - 瀏覽器API完成函數調用的過程,將回調函數推送到回調隊列(callback queue),然后當堆棧為空時,它將回調函數推送到調用堆棧。

堆棧 —一種數據結構,只能將元素推入并彈出頂部元素。 想想堆疊一個字形的塔樓; 你不能刪除中間塊,后進先出。

 — 變量存儲在內存中。

調用堆棧 — 函數調用的隊列,它實現了堆棧數據類型,這意味著一次可以運行一個函數。 調用函數將其推入堆棧并從函數返回將其彈出堆棧。

執行上下文 — 當函數放入到調用堆棧時由JS創建的環境。

閉包 — 當在另一個函數內創建一個函數時,它“記住”它在以后調用時創建的環境。

垃圾收集 — 當內存中的變量被自動刪除時,因為它不再使用,引擎要處理掉它。

變量的提升— 當變量內存沒有賦值時會被提升到全局的頂部并設置為undefined。

this —由JavaScript為每個新的執行上下文自動創建的變量/關鍵字。

調用堆棧(Call Stack)

看看下面的代碼:

var myOtherVar = 10function a() {console.log('myVar', myVar)b()}function b() {console.log('myOtherVar', myOtherVar)c()}function c() {console.log('Hello world!')}a()var myVar = 5

有幾個點需要注意:

  • 變量聲明的位置(一個在上,一個在下)
  • 函數a調用下面定義的函數b, 函數b調用函數c

當它被執行時你期望發生什么? 是否發生錯誤,因為b在a之后聲明或者一切正常? console.log 打印的變量又是怎么樣?
以下是打印結果:

"myVar" undefined"myOtherVar" 10"Hello world!"

來分解一下上述的執行步驟。

1. 變量和函數聲明(創建階段)

第一步是在內存中為所有變量和函數分配空間。 但請注意,除了undefined之外,尚未為變量分配值。 因此,myVar在被打印時的值是undefined,因為JS引擎從頂部開始逐行執行代碼。

函數與變量不一樣,函數可以一次聲明和初始化,這意味著它們可以在任何地方被調用。

所以以上代碼看起來像這樣子:

var myOtherVar = undefinedvar myVar = undefinedfunction a() {...}function b() {...}function c() {...}

這些都存在于JS創建的全局上下文中,因為它位于全局空間中。

在全局上下文中,JS還添加了:

  1. 全局對象(瀏覽器中是 window 對象,NodeJs 中是 global 對象)
  2. this 指向全局對象

2. 執行

接下來,JS 引擎會逐行執行代碼。

myOtherVar = 10在全局上下文中,myOtherVar被賦值為10

已經創建了所有函數,下一步是執行函數 a()

每次調用函數時,都會為該函數創建一個新的上下文(重復步驟1),并將其放入調用堆棧。

function a() {console.log('myVar', myVar)b()}

如下步驟:

  1. 創建新的函數上下文
  2. a 函數里面沒有聲明變量和函數
  3. 函數內部創建了 this 并指向全局對象(window)
  4. 接著引用了外部變量 myVar,myVar 屬于全局作用域的。
  5. 接著調用函數 b ,函數b的過程跟 a一樣,這里不做分析。

下面調用堆棧的執行示意圖:

JavaScript,引擎

  1. 創建全局上下文,全局變量和函數。
  2. 每個函數的調用,會創建一個上下文,外部環境的引用及 this。
  3. 函數執行結束后會從堆棧中彈出,并且它的執行上下文被垃圾收集回收(閉包除外)。
  4. 當調用堆棧為空時,它將從事件隊列中獲取事件。

作用域及作用域鏈

在前面的示例中,所有內容都是全局作用域的,這意味著我們可以從代碼中的任何位置訪問它。 現在,介紹下私有作用域以及如何定義作用域。

函數/詞法作用域

考慮如下代碼:

function a() {var myOtherVar = 'inside A'b()}function b() {var myVar = 'inside B'console.log('myOtherVar:', myOtherVar)function c() {console.log('myVar:', myVar)}c()}var myOtherVar = 'global otherVar'var myVar = 'global myVar'a()

需要注意以下幾點:

  1. 全局作用域和函數內部都聲明了變量
  2. 函數c現在在函數b中聲明

打印結果如下:

myOtherVar: "global otherVar"myVar: "inside B"

執行步驟:

  1. 全局創建和聲明 - 創建內存中的所有函數和變量以及全局對象和 this
  2. 執行 - 它逐行讀取代碼,給變量賦值,并執行函數a
  3. 函數a創建一個新的上下文并被放入堆棧,在上下文中創建變量myOtherVar,然后調用函數b
  4. 函數b 也會創建一個新的上下文,同樣也被放入堆棧中
  5. 函數b的上下文中創建了 myVar 變量,并聲明函數c

上面提到每個新上下文會創建的外部引用,外部引用取決于函數在代碼中聲明的位置。

  1. 函數b試圖打印myOtherVar,但這個變量并不存在于函數b中,函數b 就會使用它的外部引用上作用域鏈向上找。由于函數b是全局聲明的,而不是在函數a內部聲明的,所以它使用全局變量myOtherVar。
  2. 函數c執行步驟一樣。由于函數c本身沒有變量myVar,所以它它通過作用域鏈向上找,也就是函數b,因為myVar是函數b內部聲明過。

下面是執行示意圖:

JavaScript,引擎

請記住,外部引用是單向的,它不是雙向關系。例如,函數b不能直接跳到函數c的上下文中并從那里獲取變量。
最好將它看作一個只能在一個方向上運行的鏈(范圍鏈)。

  • a -> global
  • c -> b -> global

在上面的圖中,你可能注意到,函數是創建新作用域的一種方式。(除了全局作用域)然而,還有另一種方法可以創建新的作用域,就是塊作用域。

塊作用域

下面代碼中,我們有兩個變量和兩個循環,在循環重新聲明相同的變量,會打印什么(反正我是做錯了)?

function loopScope () {var i = 50var j = 99for (var i = 0; i < 10; i++) {}console.log('i =', i)for (let j = 0; j < 10; j++) {}console.log('j =', j)}loopScope()

打印結果:

i = 10j = 99

第一個循環覆蓋了var i,對于不知情的開發人員來說,這可能會導致bug。

第二個循環,每次迭代創建了自己作用域和變量。 這是因為它使用let關鍵字,它與var相同,只是let有自己的塊作用域。 另一個關鍵字是const,它與let相同,但const常量且無法更改(指內存地址)。

塊作用域由大括號 {} 創建的作用域

再看一個例子:

function blockScope () {let a = 5{const blockedVar = 'blocked'var b = 11a = 9000}console.log('a =', a)console.log('b =', b)console.log('blockedVar =', blockedVar)}blockScope()

打印結果:

a = 9000b = 11ReferenceError: blockedVar is not defined
  1. a是塊作用域,但它在函數中,而不是嵌套的,本例中使用var是一樣的。
  2. 對于塊作用域的變量,它的行為類似于函數,注意var b可以在外部訪問,但是const blockedVar不能。
  3. 在塊內部,從作用域鏈向上找到 a 并將let a更改為9000。

使用塊作用域可以使代碼更清晰,更安全,應該盡可能地使用它。

事件循環(Event Loop)

接下來看看事件循環。 這是回調,事件和瀏覽器API工作的地方

JavaScript,引擎

我們沒有過多討論的事情是堆,也叫全局內存。它是變量存儲的地方。由于了解JS引擎是如何實現其數據存儲的實際用途并不多,所以我們不在這里討論它。

來個異步代碼:

function logMessage2 () {console.log('Message 2')}console.log('Message 1')setTimeout(logMessage2, 1000)console.log('Message 3')

上述代碼主要是將一些 message 打印到控制臺。 利用setTimeout函數來延遲一條消息。 我們知道js是同步,來看看輸出結果

Message 1Message 3Message 2
  1. 打印 Message 1
  2. 調用 setTimeout
  3. 打印 Message 3
  4. 打印 Message 2

它記錄消息3

稍后,它會記錄消息2

setTimeout是一個 API,和大多數瀏覽器 API一樣,當它被調用時,它會向瀏覽器發送一些數據和回調。我們這邊是延遲一秒打印 Message 2。

調用完setTimeout 后,我們的代碼繼續運行,沒有暫停,打印 Message 3 并執行一些必須先執行的操作。
瀏覽器等待一秒鐘,它就會將數據傳遞給我們的回調函數并將其添加到事件/回調隊列中( event/callback queue)。 然后停留在

隊列中,只有當**調用堆棧(call stack)**為空時才會被壓入堆棧。

JavaScript,引擎

代碼示例

要熟悉JS引擎,最好的方法就是使用它,再來些有意義的例子。

簡單的閉包

這個例子中 有一個返回函數的函數,并在返回的函數中使用外部的變量, 這稱為閉包。

function exponent (x) {return function (y) {//和math.pow() 或者x的y次方是一樣的return y ** x}}const square = exponent(2)console.log(square(2), square(3)) // 4, 9console.log(exponent(3)(2)) // 8

塊代碼

我們使用無限循環將將調用堆棧塞滿,會發生什么,回調隊列被會阻塞,因為只能在調用堆棧為空時添加回調隊列。

function blockingCode() {const startTime = new Date().getSeconds()// 延遲函數250毫秒setTimeout(function() {const calledAt = new Date().getSeconds()const diff = calledAt - startTime// 打印調用此函數所需的時間console.log(`Callback called after: ${diff} seconds`)}, 250)// 用循環阻塞堆棧2秒鐘while(true) {const currentTime = new Date().getSeconds()// 2 秒后退出if(currentTime - startTime >= 2) break}}blockingCode() // 'Callback called after: 2 seconds'

我們試圖在250毫秒之后調用一個函數,但因為我們的循環阻塞了堆棧所花了兩秒鐘,所以回調函數實際是兩秒后才會執行,這是JavaScript應用程序中的常見錯誤。

setTimeout不能保證在設置的時間之后調用函數。相反,更好的描述是,在至少經過這段時間之后調用這個函數。

延遲函數

當 setTimeout 的設置為0,情況是怎么樣?

function defer () {setTimeout(() => console.log('timeout with 0 delay!'), 0)console.log('after timeout')console.log('last log')}defer()

你可能期望它被立即調用,但是,事實并非如此。

執行結果:

after timeoutlast logtimeout with 0 delay!

它會立即被推到回調隊列,但它仍然會等待調用堆棧為空才會執行。

用閉包來緩存

Memoization是緩存函數調用結果的過程。

例如,有一個添加兩個數字的函數add。調用add(1,2)返回3,當再次使用相同的參數add(1,2)調用它,這次不是重新計算,而是記住1 + 2是3的結果并直接返回對應的結果。 Memoization可以提高代碼運行速度,是一個很好的工具。
我們可以使用閉包實現一個簡單的memoize函數。

// 緩存函數,接收一個函數const memoize = (func) => {// 緩存對象// keys 是 arguments, values are resultsconst cache = {}// 返回一個新的函數// it remembers the cache object & func (closure)// ...args is any number of argumentsreturn (...args) => {// 將參數轉換為字符串,以便我們可以存儲它const argStr = JSON.stringify(args)// 如果已經存,則打印console.log('cache', cache, !!cache[argStr])cache[argStr] = cache[argStr] || func(...args)return cache[argStr]}}const add = memoize((a, b) => a + b)console.log('first add call: ', add(1, 2))console.log('second add call', add(1, 2))

執行結果:

cache {} falsefirst add call: 3cache { '[1,2]': 3 } truesecond add call 3

第一次 add 方法,緩存對象是空的,它調用我們的傳入函數來獲取值3.然后它將args/value鍵值對存儲在緩存對象中。
在第二次調用中,緩存中已經有了,查找到并返回值。

對于add函數來說,有無緩存看起來無關緊要,甚至效率更低,但是對于一些復雜的計算,它可以節省很多時間。這個示例并不是一個完美的緩存示例,而是閉包的實際應用。

代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
这里精品视频免费| 日韩欧美国产视频| 国产精品中文字幕久久久| 成人精品久久av网站| 91在线视频免费| 精品国产视频在线| 一本一本久久a久久精品综合小说| 国产91精品高潮白浆喷水| 亚洲国产成人精品久久| 日韩欧美有码在线| 久久99精品久久久久久噜噜| 日韩美女福利视频| 欧美激情a∨在线视频播放| 91久久精品国产| 日韩成人av一区| 激情成人在线视频| 国产91精品久| 国产日韩精品在线播放| 国产精品一区二区三| 日韩风俗一区 二区| 孩xxxx性bbbb欧美| 亚洲a级在线播放观看| 91免费综合在线| 亚洲精品久久久久国产| 国产精品揄拍一区二区| 另类少妇人与禽zozz0性伦| 国产香蕉一区二区三区在线视频| 国产精品人人做人人爽| 日韩中文综合网| 91精品久久久久久| 欧美中文字幕视频在线观看| 久久天天躁狠狠躁夜夜躁| 在线视频欧美日韩精品| 成人精品aaaa网站| 欧美成人精品在线播放| 中文字幕av一区中文字幕天堂| 欧美日韩久久久久| 欧美视频在线观看 亚洲欧| 国产va免费精品高清在线观看| 97在线视频免费播放| 日韩亚洲成人av在线| 欧美久久精品午夜青青大伊人| 亚洲香蕉av在线一区二区三区| 成人有码在线视频| 欧美成人免费一级人片100| 人人做人人澡人人爽欧美| 91精品国产91久久久| 欲色天天网综合久久| 日韩精品在线电影| 亚洲bt欧美bt日本bt| 日本精品久久电影| 91亚洲精华国产精华| 揄拍成人国产精品视频| 狠狠色噜噜狠狠狠狠97| 欧美裸体xxxxx| 国产视频在线观看一区二区| 国产偷亚洲偷欧美偷精品| 欧美在线激情视频| 欧美日韩国产91| 九九热视频这里只有精品| 亚洲日韩第一页| 九九热这里只有精品免费看| 色婷婷av一区二区三区在线观看| 成人写真视频福利网| 久久久久久97| 国产成人精品国内自产拍免费看| 欧美乱大交xxxxx| 欧美日韩中文在线| 久久精品成人欧美大片古装| 日韩激情在线视频| 国产精品69久久久久| 日韩激情视频在线| 欧美最猛性xxxx| 欧洲午夜精品久久久| 久久精品在线视频| 最近免费中文字幕视频2019| 国产精品久久久久久搜索| 日韩av最新在线观看| 色偷偷9999www| 国产精品一区二区三区成人| 狠狠色狠狠色综合日日五| 国产精品h在线观看| 久久综合免费视频影院| 欧美在线视频免费| 国产精品久久久久av免费| 尤物yw午夜国产精品视频明星| 国产精品久久久久久久久免费看| 国产精品第七影院| 热久久视久久精品18亚洲精品| 欧美午夜影院在线视频| 欧美激情久久久久| 日韩av网站在线| 色综合久久中文字幕综合网小说| 国产精品入口免费视频一| 精品日韩美女的视频高清| 国产精品天天狠天天看| 91精品国产91久久久久久吃药| 国模吧一区二区| 91精品啪在线观看麻豆免费| 日韩精品999| 欧美成人精品一区二区三区| 日韩av在线免费观看一区| 成人免费视频在线观看超级碰| 国语对白做受69| 亚洲图片欧美日产| 亚洲精品白浆高清久久久久久| 91亚洲国产成人久久精品网站| 精品亚洲一区二区| 美女精品久久久| 亚洲一区二区三区毛片| 欧美黑人狂野猛交老妇| 精品国产美女在线| 日韩欧美成人网| 欧美一级大片在线观看| 国产精品久久久| 欧美激情国产日韩精品一区18| 精品久久久久久| 欧美精品一二区| 中文字幕亚洲欧美一区二区三区| 97在线视频观看| 亚洲精品videossex少妇| 欧美丝袜第一区| 最近中文字幕2019免费| 国产精品永久免费在线| 亚洲最大的网站| 国产亚洲欧洲高清一区| 久久亚洲精品中文字幕冲田杏梨| 日韩大片在线观看视频| 亚洲天堂视频在线观看| 日本视频久久久| 亚洲精品久久久久久下一站| 国产99视频在线观看| 午夜精品一区二区三区在线视| 国产脚交av在线一区二区| 色中色综合影院手机版在线观看| 色噜噜国产精品视频一区二区| 亚洲欧美日韩国产中文专区| 欧美精品一本久久男人的天堂| 亚洲全黄一级网站| 91九色国产在线| 欧美一级视频一区二区| 久久久久久久久久久免费| 欧美日韩久久久久| 国产精品久久久久久久7电影| 色yeye香蕉凹凸一区二区av| 91av在线看| 欧美夫妻性生活xx| 亚洲精品国产精品国自产观看浪潮| 日韩视频免费中文字幕| 欧美大片va欧美在线播放| 一区二区av在线| 91精品久久久久久久久久另类| 夜夜嗨av一区二区三区免费区| 黑人精品xxx一区一二区| 亚洲成人亚洲激情| 欧美在线亚洲一区| 91精品国产91久久久久久| 日韩亚洲欧美成人| 中文字幕欧美专区| 成人黄色免费网站在线观看| 久久久免费观看视频| 亚洲精品视频网上网址在线观看| 日韩理论片久久| 久久影院资源网|