undefined
。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è)在上,一個(gè)在下)
函數(shù)a
調(diào)用下面定義的函數(shù)b
, 函數(shù)b調(diào)用函數(shù)c
b
在a
之后聲明或者一切正常? console.log
打印的變量又是怎么樣?'myVar' undefined
'myOtherVar' 10
'Hello world!'
undefined
之外,尚未為變量分配值。 因此,myVar
在被打印時(shí)的值是undefined
,因?yàn)镴S引擎從頂部開始逐行執(zhí)行代碼。var myOtherVar = undefined
var myVar = undefined
function a() {...}
function b() {...}
function c() {...}
全局對(duì)象(瀏覽器中是 window
對(duì)象,NodeJs 中是 global
對(duì)象)
this 指向全局對(duì)象
myOtherVar = 10
在全局上下文中,myOtherVar
被賦值為10
a()
function a() {
console.log('myVar', myVar)
b()
}
創(chuàng)建新的函數(shù)上下文
a
函數(shù)里面沒有聲明變量和函數(shù)
函數(shù)內(nèi)部創(chuàng)建了 this
并指向全局對(duì)象(window)
接著引用了外部變量 myVar
,myVar
屬于全局作用域的。
接著調(diào)用函數(shù) b
,函數(shù)b
的過程跟 a
一樣,這里不做分析。
創(chuàng)建全局上下文,全局變量和函數(shù)。
每個(gè)函數(shù)的調(diào)用,會(huì)創(chuàng)建一個(gè)上下文,外部環(huán)境的引用及 this
。
函數(shù)執(zhí)行結(jié)束后會(huì)從堆棧中彈出,并且它的執(zhí)行上下文被垃圾收集回收(閉包除外)。
當(dāng)調(diào)用堆棧為空時(shí),它將從事件隊(duì)列中獲取事件。
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()
全局作用域和函數(shù)內(nèi)部都聲明了變量
函數(shù)c
現(xiàn)在在函數(shù)b
中聲明
myOtherVar: 'global otherVar'
myVar: 'inside B'
全局創(chuàng)建和聲明 - 創(chuàng)建內(nèi)存中的所有函數(shù)和變量以及全局對(duì)象和 this
執(zhí)行 - 它逐行讀取代碼,給變量賦值,并執(zhí)行函數(shù)a
函數(shù)a創(chuàng)建一個(gè)新的上下文并被放入堆棧,在上下文中創(chuàng)建變量myOtherVar
,然后調(diào)用函數(shù)b
函數(shù)b 也會(huì)創(chuàng)建一個(gè)新的上下文,同樣也被放入堆棧中
myVar
變量,并聲明函數(shù)c函數(shù)b試圖打印myOtherVar
,但這個(gè)變量并不存在于函數(shù)b中,函數(shù)b 就會(huì)使用它的外部引用上作用域鏈向上找。由于函數(shù)b是全局聲明的,而不是在函數(shù)a內(nèi)部聲明的,所以它使用全局變量myOtherVar。
函數(shù)c執(zhí)行步驟一樣。由于函數(shù)c本身沒有變量myVar
,所以它它通過作用域鏈向上找,也就是函數(shù)b,因?yàn)?/span>myVar
是函數(shù)b內(nèi)部聲明過。
a -> global
c -> b -> global
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 = 10
j = 99
var i
,對(duì)于不知情的開發(fā)人員來說,這可能會(huì)導(dǎo)致bug。let
關(guān)鍵字,它與var
相同,只是let
有自己的塊作用域。 另一個(gè)關(guān)鍵字是const
,它與let
相同,但const
常量且無法更改(指內(nèi)存地址)。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 = 9000
b = 11
ReferenceError: blockedVar is not defined
a
是塊作用域,但它在函數(shù)中,而不是嵌套的,本例中使用var
是一樣的。
對(duì)于塊作用域的變量,它的行為類似于函數(shù),注意var b
可以在外部訪問,但是const blockedVar
不能。
在塊內(nèi)部,從作用域鏈向上找到 a
并將let a
更改為9000
。
function logMessage2 () {
console.log('Message 2')
}
console.log('Message 1')
setTimeout(logMessage2, 1000)
console.log('Message 3')
setTimeout
函數(shù)來延遲一條消息。 我們知道js是同步,來看看輸出結(jié)果Message 1
Message 3
Message 2
打印 Message 1
調(diào)用 setTimeout
打印 Message 3
打印 Message 2
setTimeout
是一個(gè) API,和大多數(shù)瀏覽器 API一樣,當(dāng)它被調(diào)用時(shí),它會(huì)向?yàn)g覽器發(fā)送一些數(shù)據(jù)和回調(diào)。我們這邊是延遲一秒打印 Message 2。setTimeout
后,我們的代碼繼續(xù)運(yùn)行,沒有暫停,打印 Message 3 并執(zhí)行一些必須先執(zhí)行的操作。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
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'
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ù)。setTimeout
的設(shè)置為0,情況是怎么樣?function defer () {
setTimeout(() => console.log('timeout with 0 delay!'), 0)
console.log('after timeout')
console.log('last log')
}
defer()
after timeout
last log
timeout with 0 delay!
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ù),接收一個(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))
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ì)象中。add
函數(shù)來說,有無緩存看起來無關(guān)緊要,甚至效率更低,但是對(duì)于一些復(fù)雜的計(jì)算,它可以節(jié)省很多時(shí)間。這個(gè)示例并不是一個(gè)完美的緩存示例,而是閉包的實(shí)際應(yīng)用。聯(lián)系客服