日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

為了保證可讀性,本文采用意譯而非直譯。

一些名詞

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

有幾個點需要注意:

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

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

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

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

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

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

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

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

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

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

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

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

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

2. 執(zhí)行

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

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

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

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

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

如下步驟:

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

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

搞懂JavaScript引擎運行原理

 

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

作用域及作用域鏈

在前面的示例中,所有內(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
()

需要注意以下幾點:

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

打印結(jié)果如下:

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

執(zhí)行步驟:

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

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

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

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

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

搞懂JavaScript引擎運行原理

 

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

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

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

在上面的圖中,你可能注意到,函數(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)覆蓋了 vari,對于不知情的開發(fā)人員來說,這可能會導致bug。

第二個循環(huán),每次迭代創(chuàng)建了自己作用域和變量。 這是因為它使用 let關鍵字,它與 var相同,只是 let有自己的塊作用域。 另一個關鍵字是 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.  
  2. a是塊作用域,但它在函數(shù)中,而不是嵌套的,本例中使用 var是一樣的。
  3. 對于塊作用域的變量,它的行為類似于函數(shù),注意 varb可以在外部訪問,但是 constblockedVar不能。
  4. 在塊內(nèi)部,從作用域鏈向上找到 a 并將 leta更改為 9000。

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

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

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

搞懂JavaScript引擎運行原理

 

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

來個異步代碼:

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.  
  2. 打印 Message 1
  3. 調(diào)用 setTimeout
  4. 打印 Message 3
  5. 打印 Message 2

它記錄消息3

稍后,它會記錄消息2

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

調(diào)用完 setTimeout 后,我們的代碼繼續(xù)運行,沒有暫停,打印 Message 3 并執(zhí)行一些必須先執(zhí)行的操作。

瀏覽器等待一秒鐘,它就會將數(shù)據(jù)傳遞給我們的回調(diào)函數(shù)并將其添加到事件/回調(diào)隊列中( event/callback queue)。 然后停留在隊列中,只有當調(diào)用堆棧(call stack)為空時才會被壓入堆棧。

搞懂JavaScript引擎運行原理

 

代碼示例

要熟悉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)隊列被會阻塞,因為只能在調(diào)用堆棧為空時添加回調(diào)隊列。

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ù),但因為我們的循環(huán)阻塞了堆棧所花了 兩秒鐘,所以回調(diào)函數(shù)實際是兩秒后才會執(zhí)行,這是JavaScript應用程序中的常見錯誤

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

延遲函數(shù)

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

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

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

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

after timeout
last
 log
timeout 
with
 
0
 delay
!

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

用閉包來緩存

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

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

我們可以使用閉包實現(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ù)來說,有無緩存看起來無關緊要,甚至效率更低,但是對于一些復雜的計算,它可以節(jié)省很多時間。這個示例并不是一個完美的緩存示例,而是閉包的實際應用。

分享到:
標簽:JavaScript
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定