欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
搞懂JavaScript引擎運行原理
一些名詞

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

作用域 — 可以從中訪(fǎng)問(wèn)變量的“區域”。

詞法作用域— 在詞法階段的作用域,換句話(huà)說(shuō),詞法作用域是由你在寫(xiě)代碼時(shí)將變量和塊作用域寫(xiě)在哪里來(lái)決定的,因此當詞法分析器處理代碼時(shí)會(huì )保持作用域不變。

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

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

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

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

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

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

 — 變量存儲在內存中。

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

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

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

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

變量的提升— 當變量?jì)却鏇](méi)有賦值時(shí)會(huì )被提升到全局的頂部并設置為undefined。

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

調用堆棧(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è)在下)

  • 函數a調用下面定義的函數b, 函數b調用函數c

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

'myVar' undefined'myOtherVar' 10'Hello world!'
來(lái)分解一下上述的執行步驟。

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

第一步是在內存中為所有變量和函數分配空間。 但請注意,除了undefined之外,尚未為變量分配值。 因此,myVar在被打印時(shí)的值是undefined,因為JS引擎從頂部開(kāi)始逐行執行代碼。
函數與變量不一樣,函數可以一次聲明和初始化,這意味著(zhù)它們可以在任何地方被調用。
所以以上代碼看起來(lái)像這樣子:
var myOtherVar = undefinedvar myVar = undefined
function a() {...}function b() {...}function c() {...}
這些都存在于JS創(chuàng )建的全局上下文中,因為它位于全局空間中。
在全局上下文中,JS還添加了:
  1. 全局對象(瀏覽器中是 window 對象,NodeJs 中是 global 對象)

  2. this 指向全局對象

2. 執行

接下來(lái),JS 引擎會(huì )逐行執行代碼。
myOtherVar = 10在全局上下文中,myOtherVar被賦值為10
已經(jīng)創(chuàng )建了所有函數,下一步是執行函數 a()
每次調用函數時(shí),都會(huì )為該函數創(chuàng )建一個(gè)新的上下文(重復步驟1),并將其放入調用堆棧。
function a() { console.log('myVar', myVar) b()}
如下步驟:
  1. 創(chuàng )建新的函數上下文

  2. a 函數里面沒(méi)有聲明變量和函數

  3. 函數內部創(chuàng )建了 this 并指向全局對象(window)

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

  5. 接著(zhù)調用函數 b ,函數b的過(guò)程跟 a一樣,這里不做分析。

下面調用堆棧的執行示意圖:
  1. 創(chuàng )建全局上下文,全局變量和函數。

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

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

  4. 當調用堆棧為空時(shí),它將從事件隊列中獲取事件。

作用域及作用域鏈

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

函數/詞法作用域

考慮如下代碼:
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. 全局作用域和函數內部都聲明了變量

  2. 函數c現在在函數b中聲明

打印結果如下:
myOtherVar: 'global otherVar'myVar: 'inside B'
執行步驟:
  1. 全局創(chuàng )建和聲明 - 創(chuàng )建內存中的所有函數和變量以及全局對象和 this

  2. 執行 - 它逐行讀取代碼,給變量賦值,并執行函數a

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

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

5,函數b的上下文中創(chuàng )建了 myVar 變量,并聲明函數c
上面提到每個(gè)新上下文會(huì )創(chuàng )建的外部引用,外部引用取決于函數在代碼中聲明的位置。
  1. 函數b試圖打印myOtherVar,但這個(gè)變量并不存在于函數b中,函數b 就會(huì )使用它的外部引用上作用域鏈向上找。由于函數b是全局聲明的,而不是在函數a內部聲明的,所以它使用全局變量myOtherVar。

  2. 函數c執行步驟一樣。由于函數c本身沒(méi)有變量myVar,所以它它通過(guò)作用域鏈向上找,也就是函數b,因為myVar函數b內部聲明過(guò)。

下面是執行示意圖:
請記住,外部引用是單向的,它不是雙向關(guān)系。例如,函數b不能直接跳到函數c的上下文中并從那里獲取變量。
最好將它看作一個(gè)只能在一個(gè)方向上運行的鏈(范圍鏈)。
  • a -> global

  • c -> b -> global

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

塊作用域

下面代碼中,我們有兩個(gè)變量和兩個(gè)循環(huán),在循環(huán)重新聲明相同的變量,會(huì )打印什么(反正我是做錯了)?
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()
打印結果:
i = 10j = 99
第一個(gè)循環(huán)覆蓋了var i,對于不知情的開(kāi)發(fā)人員來(lái)說(shuō),這可能會(huì )導致bug。
第二個(gè)循環(huán),每次迭代創(chuàng )建了自己作用域和變量。 這是因為它使用let關(guān)鍵字,它與var相同,只是let有自己的塊作用域。 另一個(gè)關(guān)鍵字是const,它與let相同,但const常量且無(wú)法更改(指內存地址)。
塊作用域由大括號 {} 創(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()
打印結果:

a = 9000b = 11ReferenceError: blockedVar is not defined
  1. a是塊作用域,但它在函數中,而不是嵌套的,本例中使用var是一樣的。

  2. 對于塊作用域的變量,它的行為類(lèi)似于函數,注意var b可以在外部訪(fǎng)問(wèn),但是const blockedVar不能。

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

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

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

接下來(lái)看看事件循環(huán)。 這是回調,事件和瀏覽器API工作的地方
我們沒(méi)有過(guò)多討論的事情是,也叫全局內存。它是變量存儲的地方。由于了解JS引擎是如何實(shí)現其數據存儲的實(shí)際用途并不多,所以我們不在這里討論它。
來(lái)個(gè)異步代碼:

function logMessage2 () { console.log('Message 2')}
console.log('Message 1')
setTimeout(logMessage2, 1000)
console.log('Message 3')
上述代碼主要是將一些 message 打印到控制臺。 利用setTimeout函數來(lái)延遲一條消息。 我們知道js是同步,來(lái)看看輸出結果

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

  2. 調用 setTimeout

  3. 打印 Message 3

  4. 打印 Message 2

它記錄消息3
稍后,它會(huì )記錄消息2
setTimeout是一個(gè) API,和大多數瀏覽器 API一樣,當它被調用時(shí),它會(huì )向瀏覽器發(fā)送一些數據和回調。我們這邊是延遲一秒打印 Message 2。
調用完setTimeout 后,我們的代碼繼續運行,沒(méi)有暫停,打印 Message 3 并執行一些必須先執行的操作。
瀏覽器等待一秒鐘,它就會(huì )將數據傳遞給我們的回調函數并將其添加到事件/回調隊列中( event/callback queue)。 然后停留在隊列中,只有當調用堆棧(call stack)為空時(shí)才會(huì )被壓入堆棧。

代碼示例

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

簡(jiǎn)單的閉包

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

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

塊代碼

我們使用無(wú)限循環(huán)將將調用堆棧塞滿(mǎn),會(huì )發(fā)生什么,回調隊列被會(huì )阻塞,因為只能在調用堆棧為空時(shí)添加回調隊列。

function blockingCode() { const startTime = new Date().getSeconds()
// 延遲函數250毫秒 setTimeout(function() { const calledAt = new Date().getSeconds() const diff = calledAt - startTime // 打印調用此函數所需的時(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毫秒之后調用一個(gè)函數,但因為我們的循環(huán)阻塞了堆棧所花了兩秒鐘,所以回調函數實(shí)際是兩秒后才會(huì )執行,這是JavaScript應用程序中的常見(jiàn)錯誤。
setTimeout不能保證在設置的時(shí)間之后調用函數。相反,更好的描述是,在至少經(jīng)過(guò)這段時(shí)間之后調用這個(gè)函數。

延遲函數

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

function defer () { setTimeout(() => console.log('timeout with 0 delay!'), 0) console.log('after timeout') console.log('last log')}
defer()
你可能期望它被立即調用,但是,事實(shí)并非如此。
執行結果:

after timeoutlast logtimeout with 0 delay!
它會(huì )立即被推到回調隊列,但它仍然會(huì )等待調用堆棧為空才會(huì )執行。

用閉包來(lái)緩存

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

// 緩存函數,接收一個(gè)函數const memoize = (func) => { // 緩存對象 // keys 是 arguments, values are results const cache = {}
// 返回一個(gè)新的函數 // it remembers the cache object & func (closure) // ...args is any number of arguments return (...args) => { // 將參數轉換為字符串,以便我們可以存儲它 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))
執行結果:

cache {} falsefirst add call: 3cache { '[1,2]': 3 } truesecond add call 3
第一次 add 方法,緩存對象是空的,它調用我們的傳入函數來(lái)獲取值3.然后它將args/value鍵值對存儲在緩存對象中。
在第二次調用中,緩存中已經(jīng)有了,查找到并返回值。
對于add函數來(lái)說(shuō),有無(wú)緩存看起來(lái)無(wú)關(guān)緊要,甚至效率更低,但是對于一些復雜的計算,它可以節省很多時(shí)間。這個(gè)示例并不是一個(gè)完美的緩存示例,而是閉包的實(shí)際應用。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Javascript中的變量以及如何定義靜態(tài)變量 | 盛夏蓮花
全網(wǎng)最通透的“閉包”認知 · 跨越語(yǔ)言
10 個(gè)常問(wèn)的 JS 面試題,看看自己都會(huì )了嗎?
const extern static 終極指南
web前端面試題JS篇之閉包
ES6新特性
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久