系列文章:javascript
本質上是一套規則,用於肯定在何處以及如何查找變量(標識符)。java
做用域是能夠嵌套的,引擎從當前做用域開始查找,若是找不到,就會向上一級繼續查找,當抵達到最外層的全局做用域查找後,不管找到仍是沒找到,都會中止。 bash
有如下兩種方式:閉包
⚠️注意:只會查找一級標識符,比 如foo.bar.baz,只會試圖找到 foo 標識符,找到後,對象屬性訪問規則後分別接管對 bar、baz 的屬性訪問。模塊化
舉🌰:函數
function foo(a) {
console.log(a + b);
}
var b = 2;
foo(3);
引擎:做用域,我須要爲 b 進行 LHS引用,這個你見過嗎?
全局做用域:見過見過!剛纔編譯器聲明它來着,給你。
引擎:謝謝大哥,如今我要把2賦值給 b
引擎:做用域啊,還有個事,我要對 foo 進行 RHS 引用,你見過沒啊?
全局做用域:見過呀,它是個函數,給你。
引擎:好的,我如今執行一下 foo
引擎:哥啊,我須要對 a 進行 LHS 引用,這個你見過沒?
全局做用域:這個也見過,是編譯器把它聲明成 foo 的一個形參了,拿去吧。
引擎:太棒了,如今我把3賦值給 a 了
引擎:foo 做用域啊,我要對 console 進行 RHS 引用,你見過沒啊?
foo做用域:這我也有,是個內置對象,給你
引擎:你老是那麼給力,如今我要看看這裏有沒有 log(),找到了,是個函數。
引擎:哥,我要對 a 進行 RHS 引用,雖然我記得好像有這個值,可是想讓你幫我確認如下。
foo做用域:好,這個值沒變過,你拿走吧。
引擎: 哥,我還要對 b 進行 RHS 引用,你找找唄
foo做用域:我沒聽過啊,你問問個人上級吧:
引擎:foo 的上級做用域兄弟,你見過 b 沒啊?
全局做用域:見過 b 啊,等於2,拿走不謝!
引擎:真棒,我如今把 a + b ,也就是5,傳遞進 log(...)
複製代碼
主要有兩種:post
eval():能夠接受一個字符串爲參數,並將其中的內容視爲好像在書寫時就存在於程序中這個位置的代碼。性能
function foo(str, a) {
eval(str);
console.log(a, b)
}
var b = 3;
foo("var b = 4", 2); // 2, 4
複製代碼
with:經過將一個對象的引用看成做用域來處理,將對象的屬性看成做用域中的標識符來處理,從而建立了一個新的詞法做用域。優化
function foo(obj) {
with(obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo(o1);
console.log(o1.a) //2
foo(o2)
console.loh(o2.a) //undefined;
console.log(a)
//2 在o2中,對a進行LHS引用,沒有找到,
//在o2中不會創造a這個屬性
//由於是非嚴格模式,因此會在全局做用域中建立一個變量 a,並賦值給2
複製代碼
⚠️注意:這兩個機制只在非嚴格模式下有效,嚴格模式下會拋出 Reference 錯誤。還會致使性能降低,引擎沒法在編譯時對其進行優化,因此會變慢。ui
做用域有三種:
在javascript中,定義一個函數有四種方式,分別是:
函數聲明:function 關鍵字出如今聲明中第一個詞,它的調用能夠先於聲明。
函數表達式:在執行到達時建立,並只有從那時起才能夠用。
ES6 中的箭頭函數
new Function()
⚠️注意:函數聲明和函數表達式最大的區別就是他們的名稱標識符將會被綁在何處。
var a = 2;
// 函數聲明,被綁定在所在做用域中,能夠直接經過 foo() 來調用它
function foo() {
var a = 3;
console.log(a); //3
}
foo();
// 函數表達式,foo2被綁定在函數表達式自身的函數中,而不是所在的做用域中
// 也就是,只能在函數內部裏被訪問foo2,外部做用域內不能訪問
(function foo2() {
var a = 3;
console.log(a) //3
}
)()
console.log(a) //2
複製代碼
在函數表達式中的當即執行函數表達式(IIFE)使咱們不用主動調用函數,它會本身調用,對於作模塊化、處理組件是很是有用的,IIFE通常使用匿名函數表達式。
⚠️注意:調用函數最簡單的方法就是加一對小括號,但函數聲明不能直接調用的緣由是:
解決辦法:不讓 function 關鍵字出如今行首
function fn() {
console.log(1);
}(); //報錯
const fn1 = function() {
console.log('表達式執行');
}(); //執行函數
複製代碼
在 ES6 以前,js中也是有塊做用域概念的,但只限於個別具體的語法中:
在 ES6 中,引入了新的塊做用域
⚠️注意:提高是指聲明會被視爲存在於其所出現的做用域的整個範圍內。var容許變量聲明提高,但不容許賦值或其餘運行邏輯提高。函數聲明會被先提高,而後纔是變量。
var scope = "global";
function scopeTest() {
console.log(scope);
var scope = "local" ;
}
scopeTest(); //undefined
複製代碼
當函數能夠記住而且訪問所在的詞法做用域時,而且函數是在當前詞法做用域以外執行,此時該函數和聲明該函數的詞法環境的組合。
直接看代碼吧,用語言來描述過於空洞。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
複製代碼
這是一個高頻率會看到的題,咱們指望的結果是:分別輸出數字1 - 5,每秒一個,每次一個。但實際上,會以每秒一次的頻率輸出五次6。
那代碼中到底有什麼缺陷致使它的行爲同語義所暗示的不一致呢?缺陷是咱們試圖假設循環中每一個迭代在運行時,都會爲本身"捕獲"一個 i 的副本。可是實際上,儘管這五個函數是在各個迭代中分別定義的,可是它們都被封閉在一個共享的全局做用域中,所以只有一個i。
若是想要返回的預期結果,能夠經過如下方法。
在迭代內,使用 IIFE 會爲每一個迭代都生成一個新的做用域,使得延遲函數的回調能夠將新做用域封閉在每一個迭代內部。
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, i * 1000)
})(i);
}
複製代碼
let語法本質上是將一個塊轉換成一個能夠被關閉的做用域,let聲明的變量在每次迭代都會聲明。
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000) }
最後,若是以爲文章還不錯,請點個贊吧~👍