<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

        新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理

        來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 21:54:42
        文檔

        新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理

        新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理:一些名詞 JS引擎 — 一個讀取代碼并運(yùn)行的引擎,沒有單一的JS引擎;,每個瀏覽器都有自己的引擎,如谷歌有V。 作用域 — 可以從中訪問變量的區(qū)域。 詞法作用域— 在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦?/div>
        推薦度:
        導(dǎo)讀新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理:一些名詞 JS引擎 — 一個讀取代碼并運(yùn)行的引擎,沒有單一的JS引擎;,每個瀏覽器都有自己的引擎,如谷歌有V。 作用域 — 可以從中訪問變量的區(qū)域。 詞法作用域— 在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦?/div>

        一些名詞

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

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

        詞法作用域— 在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的,因此當(dāng)詞法分析器處理代碼時會保持作用域不變。

        塊作用域 — 由花括號{}創(chuàng)建的范圍

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

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

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

        事件循環(huán)(Event Loop) - 瀏覽器API完成函數(shù)調(diào)用的過程,將回調(diào)函數(shù)推送到回調(diào)隊(duì)列(callback queue),然后當(dāng)堆棧為空時,它將回調(diào)函數(shù)推送到調(diào)用堆棧。

        堆棧 —一種數(shù)據(jù)結(jié)構(gòu),只能將元素推入并彈出頂部元素。 想想堆疊一個字形的塔樓; 你不能刪除中間塊,后進(jìn)先出。

        堆 — 變量存儲在內(nèi)存中。

        調(diào)用堆棧 — 函數(shù)調(diào)用的隊(duì)列,它實(shí)現(xiàn)了堆棧數(shù)據(jù)類型,這意味著一次可以運(yùn)行一個函數(shù)。 調(diào)用函數(shù)將其推入堆棧并從函數(shù)返回將其彈出堆棧。

        執(zhí)行上下文 — 當(dāng)函數(shù)放入到調(diào)用堆棧時由JS創(chuàng)建的環(huán)境。

        閉包 — 當(dāng)在另一個函數(shù)內(nèi)創(chuàng)建一個函數(shù)時,它“記住”它在以后調(diào)用時創(chuàng)建的環(huán)境。

        垃圾收集 — 當(dāng)內(nèi)存中的變量被自動刪除時,因?yàn)樗辉偈褂?,引擎要處理掉它?/p>

        變量的提升— 當(dāng)變量內(nèi)存沒有賦值時會被提升到全局的頂部并設(shè)置為undefined。

        this —由JavaScript為每個新的執(zhí)行上下文自動創(chuàng)建的變量/關(guān)鍵字。

        調(diào)用堆棧(Call Stack)

        看看下面的代碼:

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

        有幾個點(diǎn)需要注意:

      1. 變量聲明的位置(一個在上,一個在下)
      2. 函數(shù)a調(diào)用下面定義的函數(shù)b, 函數(shù)b調(diào)用函數(shù)c
      3. 當(dāng)它被執(zhí)行時你期望發(fā)生什么? 是否發(fā)生錯誤,因?yàn)閎在a之后聲明或者一切正常? console.log 打印的變量又是怎么樣?

        以下是打印結(jié)果:

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

        來分解一下上述的執(zhí)行步驟。

        1. 變量和函數(shù)聲明(創(chuàng)建階段)

        第一步是在內(nèi)存中為所有變量和函數(shù)分配空間。 但請注意,除了undefined之外,尚未為變量分配值。 因此,myVar在被打印時的值是undefined,因?yàn)镴S引擎從頂部開始逐行執(zhí)行代碼。

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

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

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

        這些都存在于JS創(chuàng)建的全局上下文中,因?yàn)樗挥谌挚臻g中。

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

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

        2. 執(zhí)行

        接下來,JS 引擎會逐行執(zhí)行代碼。

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

        已經(jīng)創(chuàng)建了所有函數(shù),下一步是執(zhí)行函數(shù) a()

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

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

        如下步驟:

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

        下面調(diào)用堆棧的執(zhí)行示意圖:

        1. 創(chuàng)建全局上下文,全局變量和函數(shù)。
        2. 每個函數(shù)的調(diào)用,會創(chuàng)建一個上下文,外部環(huán)境的引用及 this。
        3. 函數(shù)執(zhí)行結(jié)束后會從堆棧中彈出,并且它的執(zhí)行上下文被垃圾收集回收(閉包除外)。
        4. 當(dāng)調(diào)用堆棧為空時,它將從事件隊(duì)列中獲取事件。

        作用域及作用域鏈

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

        函數(shù)/詞法作用域

        考慮如下代碼:

        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()

        需要注意以下幾點(diǎn):

        1. 全局作用域和函數(shù)內(nèi)部都聲明了變量
        2. 函數(shù)c現(xiàn)在在函數(shù)b中聲明

        打印結(jié)果如下:

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

        執(zhí)行步驟:

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

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

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

        下面是執(zhí)行示意圖:

        請記住,外部引用是單向的,它不是雙向關(guān)系。例如,函數(shù)b不能直接跳到函數(shù)c的上下文中并從那里獲取變量。

        最好將它看作一個只能在一個方向上運(yùn)行的鏈(范圍鏈)。

      4. a -> global
      5. c -> b -> global
      6. 在上面的圖中,你可能注意到,函數(shù)是創(chuàng)建新作用域的一種方式。(除了全局作用域)然而,還有另一種方法可以創(chuàng)建新的作用域,就是塊作用域。

        塊作用域

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

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

        打印結(jié)果:

        i = 10
        j = 99

        第一個循環(huán)覆蓋了var i,對于不知情的開發(fā)人員來說,這可能會導(dǎo)致bug。

        第二個循環(huán),每次迭代創(chuàng)建了自己作用域和變量。 這是因?yàn)樗褂胠et關(guān)鍵字,它與var相同,只是let有自己的塊作用域。 另一個關(guān)鍵字是const,它與let相同,但const常量且無法更改(指內(nèi)存地址)。

        塊作用域由大括號 {} 創(chuàng)建的作用域

        再看一個例子:

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

        打印結(jié)果:

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

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

        事件循環(huán)(Event Loop)

        接下來看看事件循環(huán)。 這是回調(diào),事件和瀏覽器API工作的地方

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

        來個異步代碼:

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

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

        Message 1
        Message 3
        Message 2
        1. 打印 Message 1
        2. 調(diào)用 setTimeout
        3. 打印 Message 3
        4. 打印 Message 2

        它記錄消息3

        稍后,它會記錄消息2

        setTimeout是一個 API,和大多數(shù)瀏覽器 API一樣,當(dāng)它被調(diào)用時,它會向?yàn)g覽器發(fā)送一些數(shù)據(jù)和回調(diào)。我們這邊是延遲一秒打印 Message 2。

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

        隊(duì)列中,只有當(dāng)**調(diào)用堆棧(call stack)**為空時才會被壓入堆棧。

        代碼示例

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

        簡單的閉包

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

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

        塊代碼

        我們使用無限循環(huán)將將調(diào)用堆棧塞滿,會發(fā)生什么,回調(diào)隊(duì)列被會阻塞,因?yàn)橹荒茉谡{(diào)用堆棧為空時添加回調(diào)隊(duì)列。

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

        我們試圖在250毫秒之后調(diào)用一個函數(shù),但因?yàn)槲覀兊难h(huán)阻塞了堆棧所花了兩秒鐘,所以回調(diào)函數(shù)實(shí)際是兩秒后才會執(zhí)行,這是JavaScript應(yīng)用程序中的常見錯誤。

        setTimeout不能保證在設(shè)置的時間之后調(diào)用函數(shù)。相反,更好的描述是,在至少經(jīng)過這段時間之后調(diào)用這個函數(shù)。

        延遲函數(shù)

        當(dāng) setTimeout 的設(shè)置為0,情況是怎么樣?

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

        你可能期望它被立即調(diào)用,但是,事實(shí)并非如此。

        執(zhí)行結(jié)果:

        after timeout
        last log
        timeout with 0 delay!

        它會立即被推到回調(diào)隊(duì)列,但它仍然會等待調(diào)用堆棧為空才會執(zhí)行。

        用閉包來緩存

        Memoization是緩存函數(shù)調(diào)用結(jié)果的過程。

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

        // 緩存函數(shù),接收一個函數(shù)
        const memoize = (func) => {
        // 緩存對象
        // keys 是 arguments, values are results
        const cache = {}
        // 返回一個新的函數(shù)
        // it remembers the cache object & func (closure)
        // ...args is any number of arguments
        return (...args) => {
        // 將參數(shù)轉(zhuǎn)換為字符串,以便我們可以存儲它
        const argStr = JSON.stringify(args)
        // 如果已經(jīng)存,則打印
        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))

        執(zhí)行結(jié)果:

        cache {} false
        first add call: 3
        cache { '[1,2]': 3 } true
        second add call 3

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

        對于add函數(shù)來說,有無緩存看起來無關(guān)緊要,甚至效率更低,但是對于一些復(fù)雜的計(jì)算,它可以節(jié)省很多時間。這個示例并不是一個完美的緩存示例,而是閉包的實(shí)際應(yīng)用。

        代碼部署后可能存在的BUG沒法實(shí)時知道,事后為了解決這些BUG,花了大量的時間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 Fundebug。

        聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理

        新手入門帶你學(xué)習(xí)JavaScript引擎運(yùn)行原理:一些名詞 JS引擎 — 一個讀取代碼并運(yùn)行的引擎,沒有單一的JS引擎;,每個瀏覽器都有自己的引擎,如谷歌有V。 作用域 — 可以從中訪問變量的區(qū)域。 詞法作用域— 在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦?/div>
        推薦度:
        標(biāo)簽: 新手 運(yùn)行 引擎
        • 熱門焦點(diǎn)

        最新推薦

        猜你喜歡

        熱門推薦

        Top
        主站蜘蛛池模板: 亚洲AV色吊丝无码| 亚洲精品偷拍视频免费观看| 亚洲人成中文字幕在线观看| 亚洲欧好州第一的日产suv| 国产精品免费无遮挡无码永久视频| 日韩成全视频观看免费观看高清 | 中文字幕不卡高清免费| 无码人妻精品一二三区免费| 亚洲网址在线观看你懂的| 国产精品玖玖美女张开腿让男人桶爽免费看 | 影音先锋在线免费观看| 亚洲日本中文字幕| 三级网站在线免费观看| 免费人成在线观看播放国产 | 免费无码又爽又刺激高潮软件| 精品国产免费观看| 亚洲精品乱码久久久久久蜜桃图片| 91香焦国产线观看看免费| 亚洲成AV人片在线播放无码| 二个人看的www免费视频| 亚洲欧洲中文日韩av乱码| 曰批免费视频播放在线看片二| 免费无遮挡无码视频网站| 亚洲不卡中文字幕| 亚洲国产精品无码久久一区二区 | 亚洲欧美一区二区三区日产| 91禁漫免费进入| 中文字幕亚洲第一在线| 777爽死你无码免费看一二区| 亚洲日本在线观看网址| 精品久久久久久久免费人妻 | 免费毛片在线看不用播放器| 亚洲AV综合色区无码一区| 最好看最新的中文字幕免费| 亚洲熟妇AV一区二区三区宅男| 免费a级毛片无码av| 精品免费视在线观看| 亚洲国产成a人v在线观看| 日韩亚洲国产二区| 亚洲精品免费视频| 色噜噜噜噜亚洲第一|