JS高級(jí)
作用域&作用域鏈
作用域:
1.作用域的個(gè)數(shù):n(函數(shù)聲明的個(gè)數(shù))+1(全局作用域)
2.作用域不會(huì)存儲(chǔ)變量,只是執(zhí)行查詢規(guī)則
3.作用域分全局作用域和函數(shù)(局部)作用域
4.作用域是編譯時(shí)產(chǎn)生的
5.沒有塊作用域(ES5)
6.作用域管理執(zhí)行上下文,執(zhí)行上下文管理變量
7.一般情況下,一個(gè)作用域?qū)?yīng)一個(gè)執(zhí)行上下文,但遞歸情況下一個(gè)作用域可以對(duì)應(yīng)多個(gè)執(zhí)行上下文。值得注意的是,任何情況下一個(gè)作用域只有一個(gè)活動(dòng)的執(zhí)行上下文
作用域鏈:
函數(shù)嵌套時(shí)會(huì)產(chǎn)生作用域鏈
--
作用域的作用:
1.隔離變量
2.為變量查詢制定的一套規(guī)則。
--
變量查詢的規(guī)則:
左查詢:等號(hào)左邊的變量用左查詢。
--先在變量的當(dāng)前作用域里面找變量是否聲明,沒有就到上一層找,直到整條作用域鏈都沒有找到(也就是全局),那么瀏覽器會(huì)自動(dòng)為變量聲明。
右查詢:等號(hào)非左邊的變量用右查詢規(guī)則。
--先在變量的當(dāng)前作用域里面找變量是否聲明,沒有就到上一層找,直到整條作用域鏈都沒有找到(也就是全局),那么報(bào)錯(cuò)。
--
左右查詢案例:
<script>
console.log(b); // 報(bào)錯(cuò)
(function(){
function test(a){
var b=a;
console.log(b);//2
}
test(2);
})()
console.log(b);//b is not defined
</script>
執(zhí)行步驟:
1.console.log(b); 對(duì)b右查詢,在當(dāng)前作用域(全局)查找b的聲明,沒有則報(bào)錯(cuò)
2.執(zhí)行立即執(zhí)行函數(shù),然后調(diào)用test(2)函數(shù),將實(shí)參2傳給形參a
3.var b=a;對(duì)b執(zhí)行左查詢,在當(dāng)前作用域有聲明var b,且b=a,則b為2,輸出2
4.立即執(zhí)行函數(shù)運(yùn)行結(jié)束, 運(yùn)行console.log(b);對(duì)b進(jìn)行右查詢,在當(dāng)前作用域(全局)查找b的聲明,整條作用域鏈上沒有找到,則報(bào)錯(cuò)。
作用域面試題:

以上示例圖的執(zhí)行步驟:
1.先執(zhí)行var x =10;(x為全局變量)
2.執(zhí)行show(fn);調(diào)用show()函數(shù),將實(shí)參fn函數(shù)傳給形參f,那么形參f擁有fn函數(shù)的地址值
3.f(),調(diào)用fn()函數(shù)。
4.console.log(x);對(duì)x執(zhí)行右查詢,在fn()函數(shù)中沒有對(duì)變量x進(jìn)行聲明,那么到全局作用域里面去找,剛好找到var x =10;所以結(jié)果為10.
變量釋放&內(nèi)存回收
變量釋放:
局部變量————>對(duì)應(yīng)函數(shù)的作用域執(zhí)行完后則變量釋放
全局變量————>當(dāng)運(yùn)行程序關(guān)閉時(shí)變量才會(huì)釋放
內(nèi)存回收:
垃圾收集器會(huì)按照固定的時(shí)間間隔周期性的回收內(nèi)存。一般使用標(biāo)記清除,引用計(jì)數(shù)兩種策略。
執(zhí)行上下文&執(zhí)行上下文棧
執(zhí)行上下文(可以認(rèn)為是可執(zhí)行代碼的執(zhí)行環(huán)境):
1.執(zhí)行上下文的個(gè)數(shù):n(函數(shù)的調(diào)用次數(shù))+1(全局執(zhí)行上下文)
2.在函數(shù)被調(diào)用時(shí),都會(huì)創(chuàng)建新的其對(duì)應(yīng)作用域的執(zhí)行上下文。
3.一般情況下,一個(gè)作用域?qū)?yīng)一個(gè)執(zhí)行上下文,但遞歸時(shí),一個(gè)作用域可能對(duì)應(yīng)多個(gè)執(zhí)行上下文。(每調(diào)用一次函數(shù)創(chuàng)建一個(gè)執(zhí)行上下文)
4.一個(gè)作用域只能有一個(gè)活動(dòng)狀態(tài)的執(zhí)行上下文。(單線程、同步執(zhí)行)
5.執(zhí)行上下文分為:全局執(zhí)行上下文(只有一個(gè))、函數(shù)執(zhí)行上下文(可以有無數(shù)個(gè))
--
執(zhí)行上下文棧:
當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)當(dāng)前被調(diào)用函數(shù)的執(zhí)行上下文,并將執(zhí)行上下文壓入執(zhí)行棧中。當(dāng)js第一次加載腳本時(shí),默認(rèn)進(jìn)入全局執(zhí)行上下文中。

<!--
1. 依次輸出什么?
2. 整個(gè)過程中產(chǎn)生了幾個(gè)執(zhí)行上下文?
-->
<script type="text/javascript">
var i
console.log('global begin: '+ i) //undefined
i = 1
foo(1);
function foo(i){
if (i == 4) {
return;
}
console.log('foo() begin:' + i); //1,2,3
foo(i + 1);
console.log('foo() end:' + i); //3,2,1
}
console.log('global end: ' + i) //1
</script>

執(zhí)行上下文的作用:
1.存儲(chǔ)對(duì)應(yīng)的變量
2.規(guī)則
---全局執(zhí)行上下文規(guī)則:
1.var定義的全局變量,添加為window的屬性
2.function聲明的全局函數(shù),添加為window的方法
3.提升(函數(shù)的提升優(yōu)于變量的提升)
4.this指向(window)
--
---函數(shù)執(zhí)行上下文規(guī)則
1.var定義的局部變量會(huì)成為局部執(zhí)行上下文(在js中拿不到局部執(zhí)行上下文對(duì)象)的屬性
2.function聲明的局部函數(shù)會(huì)成為局部執(zhí)行上下文(在js中拿不到局部上下文對(duì)象)的方法
3.形參變量賦值給實(shí)參
4.為arguments賦值(實(shí)參列表)
5.提升(函數(shù)表達(dá)式不會(huì)提升)
6.this指向(看調(diào)用函數(shù)的對(duì)象的調(diào)用形式)
--
---函數(shù)執(zhí)行上下文規(guī)則的this指向
1.普通調(diào)用??????this—>window
???在嚴(yán)格模式下 this—>undefined
2.隱式調(diào)用??????this—>最近的調(diào)用者
3.顯示調(diào)用??????this—>指定的對(duì)象
4.構(gòu)造調(diào)用??????this—>構(gòu)造出的實(shí)例對(duì)象
作用域與執(zhí)行上下文的關(guān)系
對(duì)應(yīng)的執(zhí)行上下文 會(huì) 成為 作用域的屬性(執(zhí)行上下文最終會(huì)掛靠給作用域)
--作用域:
從瀏覽器的角度看作用域,它是一個(gè)c++對(duì)象,對(duì)象的屬性就是執(zhí)行上下文
從js語言的角度看作用域,它是一套規(guī)則(左查詢、右查詢)
--執(zhí)行上下文:
從瀏覽器的角度看執(zhí)行上下文,它是一個(gè)c++對(duì)象,對(duì)象的屬性就是它所管理的變量
從js語言的角度看執(zhí)行上下文,它是一套規(guī)則(規(guī)則如上)
提升
總結(jié):
1.提升會(huì)提升到當(dāng)前作用域的最頂層
2.變量提升是聲明的提升
3.函數(shù)提升是整體提升
4.函數(shù)的提升優(yōu)于變量的提升
注意點(diǎn):如果有兩個(gè)同名函數(shù),那么后面的函數(shù)會(huì)覆蓋前面所定義的函數(shù)(在js語言中沒有重載的概念,所以無法通過參數(shù)來判斷到底調(diào)用哪個(gè)函數(shù))。
編碼規(guī)范
1.使用變量前一定要先聲明(避免污染全局,迎合js引擎的提升規(guī)則)
2.在分支中最好不要定義函數(shù)(因?yàn)樵诜种е械暮瘮?shù)會(huì)變?yōu)楹瘮?shù)表達(dá)式)
注意點(diǎn):當(dāng)函數(shù)名與變量名重名時(shí)
function demo(){...}==>var demo = function(){...}
var demo //跟函數(shù)名重復(fù),js引擎在解析代碼時(shí)會(huì)忽略此條聲明
demo=5;
demo()
//最終demo = function(){...}=5 5() 結(jié)果報(bào)錯(cuò)
隱式丟失
當(dāng)我們對(duì)函數(shù)進(jìn)行隱式調(diào)用(對(duì)象.屬性)時(shí),可能會(huì)發(fā)生隱式丟失的問題。
1.以 對(duì)象.的形式 進(jìn)行變量的賦值時(shí)容易發(fā)生隱式丟失
var fn = obj.test;
fn() // 發(fā)生了隱式丟失,原本this應(yīng)該指向obj,結(jié)果fn()獨(dú)立調(diào)用函數(shù),this指向全局
var write = document.write;
write("****") // 原本this應(yīng)該指向document,結(jié)果發(fā)生隱式丟失,所以報(bào)錯(cuò)
--
bind()函數(shù)
bind()函數(shù)(硬綁定)是用來解決隱式丟失問題的一種手段。
bind(this,[Arg1,Arg2...]),使用bind來實(shí)現(xiàn)硬綁定時(shí),會(huì)產(chǎn)生一個(gè)新的函數(shù),這個(gè)新的函數(shù)的this指向被綁定的對(duì)象(也就是bind中第一個(gè)參數(shù)this指定的對(duì)象),新的函數(shù)相當(dāng)于拷貝了一份原函數(shù),將bind中括號(hào)[]中的參數(shù)相當(dāng)于實(shí)參([]中的參數(shù)可以省略),傳給新函數(shù)中的形參。
<script>
var module = {
x: 42,
getX: function (a,b) {
console.log(a,b);
return this.x;
}
}
var unboundGetX = module.getX;
/*
使用bind硬綁定產(chǎn)生一個(gè)boundGetX的函數(shù),boundGetX函數(shù)擁有跟原函數(shù)一樣的函數(shù)體(this指向了module),
相當(dāng)于拷貝了一份原函數(shù),bind中[1,2]相當(dāng)于新函數(shù)的實(shí)參
*/
var boundGetX = unboundGetX.bind(module,1,2);
console.log(boundGetX());
console.log(boundGetX);
</script>
函數(shù)調(diào)用優(yōu)先級(jí)
構(gòu)造函數(shù) > 顯示調(diào)用 > 隱式調(diào)用 > 普通調(diào)用