1. JS中的作用域
在js 中有兩種作用域
- 全局作用域
- 函數(shù)作用域
在es6之前,js中沒有塊級作用域毙驯。
這里解釋一下什么是塊級作用域
var i = 0;
if(true) {
a = "我是linK哥";
}
console.log(a);
上面的代碼稽坤,i的作用域是全局作用域滨攻,a是屬于if的{}里面的,但是由于js中沒有塊級饶唤,所以這里的a的作用域仍然屬于全局作用域徐伐。
2.什么是變量提升
先來看下面的代碼:
console.log(a);
var a = 12;
從上面的代碼中可以看出,先執(zhí)行a募狂,再定義a = 12,但是執(zhí)行第一行console.log(a)的時候并沒有報錯办素,而是輸出undefined,這說明在執(zhí)行console.log(a);
之前祸穷,瀏覽器中已經(jīng)聲明了a性穿。
當(dāng)棧內(nèi)存(作用域),JS代碼自上而下執(zhí)行之前雷滚,瀏覽器首先會把“var.function"關(guān)鍵詞的變量進(jìn)行提升"聲明"或者"定義"需曾,這種預(yù)先處理機制稱之為"變量提升"。
=>聲明(declare):var a (默認(rèn)值undefined)
=>定義(defined):a=12 (定義其實就是賦值操作)
變量提升只發(fā)生在當(dāng)前作用域(例如:開始加載頁面的時候只對全局作用域下的進(jìn)行提升祈远,因為此時函數(shù)中存儲的都是字符串而已)
在全局作用域下聲明的函數(shù)或者變量是“全局變量”呆万,同理,在私有作用域下聲明的變量是“私有變量” [帶VAR/FUNCTION的才是聲明]
通過下面這段代碼詳細(xì)解讀變量提升機制
var a = 12;
/*
* 1.先聲明一個變量a车份,沒有賦值(默認(rèn)值是undefined)
* 2.在當(dāng)前作用域中開辟一個位置存儲12這個值
* 3.讓變量a和12關(guān)聯(lián)在一起(定義:賦值)
*/
var b = a;
b = 13;
console.log(a);
//編寫一個函數(shù)實現(xiàn)任意數(shù)求和
function sum () {
var total = 0;
for(var i=0;i<arguments.length;i++) {
var item = parseFloat(arguments[i]); //因為argument存儲的是字符串谋减,需要轉(zhuǎn)成數(shù)字
// !isNaN(item) ? total += item : null;
if( !isNaN(item)) {
total += item;
}
}
return total;
}
console.log(sum(10,12,15,41,10));
瀏覽器在加載頁面的時候,首先開啟一個window全局棧內(nèi)存扫沼,會把“var/function"關(guān)鍵詞的變量進(jìn)行提升"聲明"或者"定義"出爹,從上面的代碼中可以知道在變量提升階段會聲明變量a,b,還有sum,由于sum是屬于函數(shù)對象缎除,因此它的聲明會把函數(shù)體以字符串的形式放在堆內(nèi)存中以政,假設(shè)這塊堆內(nèi)存的地址是AAAFFF111,則sum就指向AAAFFF111這塊堆內(nèi)存空間伴找,接著js代碼進(jìn)行自上而下執(zhí)行盈蛮,當(dāng)執(zhí)行到
function sum ()
的時候會跳過函數(shù)創(chuàng)建的代碼,因為在變量提升階段函數(shù)已經(jīng)聲明了技矮。最后執(zhí)行sum()函數(shù)抖誉,這里會形成一個私有作用域殊轴,私有作用域形成后,也不是立即執(zhí)行代碼袒炉,一樣先進(jìn)行變量提升(變量提升錢旁理,先進(jìn)行形參賦值),然后再是js代碼自上而下地執(zhí)行我磁。
注意:在ES3/ES5語法規(guī)范中孽文,只有全局作用域和函數(shù)執(zhí)行的私有作用域(棧內(nèi)存),其中它大括號不會形成棧內(nèi)存夺艰。
3. 帶var 和不帶 var的區(qū)別
在全局作用域下聲明一個變量芋哭,也相當(dāng)于給window全局對象設(shè)置了一個屬性,全局變量和window屬性存在“映射機制”郁副,變量的值就是屬性的值(私有作用域中聲明的私有變量和window沒啥關(guān)系)
3.1 帶var
console.log(a); //undefined 由于變量替身的影響减牺,所以輸出的是undefined
console.log(window.a); //undefined
// console.log('a' in window); //true
// in 檢測屬性是否隸屬于這個對象
var a = 12;
console.log(a); //12 全局變量
console.log(window.a); //12 window屬性的a
帶var是可以進(jìn)行預(yù)解釋的(變量提升)所以賦值前面執(zhí)行是不會報錯的。在變量提升階段存谎,在全局作用域中聲明了一個變量a拔疚,此時已經(jīng)把a當(dāng)做一個屬性賦值給window了,只是還沒給它賦值a既荚。
下面通過var定義了全局變量var a = 12;
,這時候window全局對象也設(shè)置了一個屬性a=12;
稚失。所以打印輸出的結(jié)果都是12。但是這時候如果在var a = 12;
之前打印window.a
恰聘,發(fā)現(xiàn)輸出的是undefined墩虹,這說明這時候a并不是window中的屬性,我們也可以通過console.log('a' in window);
來判斷憨琳。
3.2 不帶var
// console.log(a); // error Uncaught ReferenceError: a is not defined
console.log(window.a); //undefined
console.log('a' in window); // false 說明a不是window中的屬性
a = 12; //這里并不是賦值诫钓,只是window.a = 12 的簡寫 (本質(zhì)是window下的屬性,不是變量)
console.log(a); //12
console.log(window.a); //12
不帶var的是不能進(jìn)行預(yù)解釋的(變量提升)篙螟,在前面執(zhí)行會報錯(ReferenceError)菌湃。
a=12;
不帶var并不是賦值,而是相當(dāng)于給window添加了一個叫做a的屬性遍略,其屬性值為12惧所。在a=12;
之前a是不屬于window的屬性的,可以通過console.log('a' in window);
進(jìn)行判斷绪杏。
特別注意
var a = 12,
b = 13; //遮掩寫b 是帶var 的
var a = b = 12; //這樣寫b是不帶var的
// 相當(dāng)于:
// var a = 12;
// b = 12;
3.3 私有作用域中的var
console.log(a,b); //undefined undefined
var a = 12,
b = 12;
function fn() {
console.log(a,b); //undefined 12
var a = b = 13;
//相當(dāng)于: var a = 13; b= 13;
console.log(a,b); //13 13
}
fn();
console.log(a,b); //12 13
私有作用域中帶var和不帶var也有區(qū)別:
- 帶var在私有作用域變量提升階段下愈,都聲明為私有變量,和外界沒有任何關(guān)系
- 不帶var不是私有變量蕾久,比如fn()里面的b势似,它會向它的上級作用域中查找,看是否為上級的變量,如果不是履因,就繼續(xù)查找障簿,一直到window位置(這種查找機制叫做:“作用域鏈”),也就是我們私有作用域操作的非私有變量栅迄,是可以直接操作外界的站故。
但是,如果找到window都沒有找不到呢毅舆?
function fn() {
/**
* 沒有變量提升
*/
//但是如果在這輸出b 就會報錯
// console.log(b); // ReferenceError: b is not defined
b = 13;
// 觀察
console.log('b' in window); //true
//在作用域鏈查找的過程中西篓,如果window也沒有這個變量,相當(dāng)于window設(shè)置了一個屬性b (window.b = 13)
console.log(b); //13
}
fn();
console.log(b); //13 所以這里輸出是window.b
通過上面代碼可知憋活,如果window也找不到岂津,可以分為兩種情況:
- 第一種情況:我們獲取的值
console.log(b);
報錯了,ReferenceError余掖。 - 第二種情況:相當(dāng)于給window添加了一個屬性b,其屬性值為13;
3.4 總結(jié)
- 在全局作用域中:帶var可以進(jìn)行預(yù)解釋礁鲁,所以在賦值前面執(zhí)行不會報錯盐欺,不帶var不進(jìn)行預(yù)解釋,在前面執(zhí)行會報錯(ReferenceError)仅醇。
-
a=12;
相當(dāng)于給window添加一個叫做a的屬性名冗美,其屬性值是12
var a = 12;
相當(dāng)給全局作用域添加一個全局變量a,但是不僅如此析二,它也相當(dāng)于給window添加一個屬性名粉洼。 - 私有作用域中出現(xiàn)的一個變量不是私有的,則往上級作用域進(jìn)行查找叶摄,上級沒有属韧,則繼續(xù)向上查找,一直找到window為止蛤吓。如果window也沒有宵喂,第一種是獲取值的時候會報錯,第二種是給window增加一個屬性会傲。
注意:在當(dāng)前作用域下锅棕,不管條件是否成立都要進(jìn)行變量提升
=>帶VAR的還是只聲明
=>帶function的在老版本瀏覽器渲染機制下,聲明和定義都處理淌山,但是為了迎合ES6中的塊級作用域裸燎,新版瀏覽器對于函數(shù)(在條件判斷中的函數(shù)),不管條件是否成立泼疑,都只是先聲明德绿,沒有定義,類似于VAR