<del id="d4fwx"><form id="d4fwx"></form></del>
      <del id="d4fwx"><form id="d4fwx"></form></del><del id="d4fwx"><form id="d4fwx"></form></del>

            <code id="d4fwx"><abbr id="d4fwx"></abbr></code>
          • JavaScript引擎的運(yùn)行原理

            這篇文章主要講解了“JavaScript引擎的運(yùn)行原理”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JavaScript引擎的運(yùn)行原理”吧!

            站在用戶的角度思考問題,與客戶深入溝通,找到榆林網(wǎng)站設(shè)計(jì)與榆林網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、申請(qǐng)域名、虛擬空間、企業(yè)郵箱。業(yè)務(wù)覆蓋榆林地區(qū)。

            一些名詞

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

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

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

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

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

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

            異步— 同時(shí)做多個(gè)事,JS通過瀏覽器API模擬異步行為

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

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

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

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

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

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

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

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

            this—由JavaScript為每個(gè)新的執(zhí)行上下文自動(dòng)創(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

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

            • 變量聲明的位置(一個(gè)在上,一個(gè)在下)

            • 函數(shù)a調(diào)用下面定義的函數(shù)b, 函數(shù)b調(diào)用函數(shù)c

            當(dāng)它被執(zhí)行時(shí)你期望發(fā)生什么? 是否發(fā)生錯(cuò)誤,因?yàn)閎在a之后聲明或者一切正常? console.log 打印的變量又是怎么樣?

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

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

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

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

            第一步是在內(nèi)存中為所有變量和函數(shù)分配空間。 但請(qǐng)注意,除了undefined之外,尚未為變量分配值。 因此,myVar在被打印時(shí)的值是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. 全局對(duì)象(瀏覽器中是 window 對(duì)象,NodeJs 中是 global 對(duì)象)

            2. this 指向全局對(duì)象

            2. 執(zhí)行

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

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

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

            每次調(diào)用函數(shù)時(shí),都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的上下文(重復(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 并指向全局對(duì)象(window)

            4. 接著引用了外部變量 myVar,myVar 屬于全局作用域的。

            5. 接著調(diào)用函數(shù) b ,函數(shù)b的過程跟 a一樣,這里不做分析。

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

            JavaScript引擎的運(yùn)行原理

            1. 創(chuàng)建全局上下文,全局變量和函數(shù)。

            2. 每個(gè)函數(shù)的調(diào)用,會(huì)創(chuàng)建一個(gè)上下文,外部環(huán)境的引用及 this。

            3. 函數(shù)執(zhí)行結(jié)束后會(huì)從堆棧中彈出,并且它的執(zhí)行上下文被垃圾收集回收(閉包除外)。

            4. 當(dāng)調(diào)用堆棧為空時(shí),它將從事件隊(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ù)和變量以及全局對(duì)象和 this

            2. 執(zhí)行 - 它逐行讀取代碼,給變量賦值,并執(zhí)行函數(shù)a

            3. 函數(shù)a創(chuàng)建一個(gè)新的上下文并被放入堆棧,在上下文中創(chuàng)建變量myOtherVar,然后調(diào)用函數(shù)b

            4. 函數(shù)b 也會(huì)創(chuàng)建一個(gè)新的上下文,同樣也被放入堆棧中

            5. 函數(shù)b的上下文中創(chuàng)建了 myVar 變量,并聲明函數(shù)c

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

            1. 函數(shù)b試圖打印myOtherVar,但這個(gè)變量并不存在于函數(shù)b中,函數(shù)b 就會(huì)使用它的外部引用上作用域鏈向上找。由于函數(shù)b是全局聲明的,而不是在函數(shù)a內(nèi)部聲明的,所以它使用全局變量myOtherVar。

            2. 函數(shù)c執(zhí)行步驟一樣。由于函數(shù)c本身沒有變量myVar,所以它它通過作用域鏈向上找,也就是函數(shù)b,因?yàn)閙yVar是函數(shù)b內(nèi)部聲明過。

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

            JavaScript引擎的運(yùn)行原理

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

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

            • a -> global

            • c -> b -> global

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

            塊作用域

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

            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

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

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

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

            再看一個(gè)例子:

            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. 對(duì)于塊作用域的變量,它的行為類似于函數(shù),注意var b可以在外部訪問,但是const blockedVar不能。

            3. 在塊內(nèi)部,從作用域鏈向上找到 a 并將let a更改為9000。

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

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

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

            JavaScript引擎的運(yùn)行原理

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

            來個(gè)異步代碼:

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

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

            Message 1
            Message 3
            Message 2
            1. 打印 Message 1

            2. 調(diào)用 setTimeout

            3. 打印 Message 3

            4. 打印 Message 2

            它記錄消息3

            稍后,它會(huì)記錄消息2

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

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

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

            JavaScript引擎的運(yùn)行原理

            代碼示例

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

            簡單的閉包

            這個(gè)例子中 有一個(gè)返回函數(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)用堆棧塞滿,會(huì)發(fā)生什么,回調(diào)隊(duì)列被會(huì)阻塞,因?yàn)橹荒茉谡{(diào)用堆棧為空時(shí)添加回調(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ù)所需的時(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'

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

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

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

            用閉包來緩存

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

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

            // 緩存函數(shù),接收一個(gè)函數(shù)
            const memoize = (func) => {
            // 緩存對(duì)象
            // keys 是 arguments, values are results
            const cache = {}
            // 返回一個(gè)新的函數(shù)
            // it remembers the cache object & func (closure)
            // ...args is any number of arguments
            return (...args) => {
            // 將參數(shù)轉(zhuǎn)換為字符串,以便我們可以存儲(chǔ)它
            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 方法,緩存對(duì)象是空的,它調(diào)用我們的傳入函數(shù)來獲取值3.然后它將args/value鍵值對(duì)存儲(chǔ)在緩存對(duì)象中。
            在第二次調(diào)用中,緩存中已經(jīng)有了,查找到并返回值。

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

            感謝各位的閱讀,以上就是“JavaScript引擎的運(yùn)行原理”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)JavaScript引擎的運(yùn)行原理這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

            網(wǎng)頁名稱:JavaScript引擎的運(yùn)行原理
            文章轉(zhuǎn)載:http://www.jbt999.com/article38/pdpssp.html

            成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站品牌網(wǎng)站制作、企業(yè)網(wǎng)站制作響應(yīng)式網(wǎng)站、云服務(wù)器、外貿(mào)網(wǎng)站建設(shè)

            廣告

            聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:[email protected]。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

            網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司

              <del id="d4fwx"><form id="d4fwx"></form></del>
              <del id="d4fwx"><form id="d4fwx"></form></del><del id="d4fwx"><form id="d4fwx"></form></del>

                    <code id="d4fwx"><abbr id="d4fwx"></abbr></code>
                  • 日韩精品一区二区三区四区五区六区 | 国产区精品豆花 | 青青操美女 | 欧美操嫩逼网 | 黄色电影第一页 |