跟我學(xué)習(xí)javascript的var預(yù)解析與函數(shù)聲明提升
1、var 變量預(yù)編譯
JavaScript 的語法和 C 、Java、C# 類似,統(tǒng)稱為 C 類語法。有過 C 或 Java 編程經(jīng)驗(yàn)的同學(xué)應(yīng)該對(duì)“先聲明、后使用”的規(guī)則很熟悉,如果使用未經(jīng)聲明的變量或函數(shù),在編譯階段就會(huì)報(bào)錯(cuò)。然而,JavaScript 卻能夠在變量和函數(shù)被聲明之前使用它們。下面我們就深入了解一下其中的玄機(jī)。
先來看一段代碼:
(function() {
console.log(noSuchVariable);//ReferenceError: noSuchVariable is not defined
})();
運(yùn)行上面代碼立馬就報(bào)錯(cuò),不過,這也正是我們期望的,因?yàn)?noSuchVariable 變量根本就沒有定義過嘛!再來看看下面的代碼:
(function() {
console.log(declaredLater); //undefined
var declaredLater = "Now it's defined!";
console.log(declaredLater);// "Now it's defined!"
})();
首先,上面這段代碼是正確的,沒有任何問題。但是,為什么不報(bào)錯(cuò)了?declaredLater 變量是在調(diào)用語句后面定義的???為什么居然輸出的是 undefined?
這其實(shí)是 JavaScript 解析器搞的鬼,解析器將當(dāng)前作用域內(nèi)聲明的所有變量和函數(shù)都會(huì)放到作用域的開始處,但是,只有變量的聲明被提前到作用域的開始處了,而賦值操作被保留在原處。上述代碼對(duì)于解析器來說其實(shí)是如下這個(gè)樣子滴:
(function() {
var declaredLater; //聲明被提前到作用域開始處了!
console.log(declaredLater); // undefined
declaredLater = "Now it's defined!"; //賦值操作還在原地!
console.log(declaredLater);//"Now it's defined!"
})();
這就是為什么上述代碼不報(bào)異常的原因!變量和函數(shù)經(jīng)過“被提前”之后,declaredLater 變量其實(shí)就被放在了調(diào)用函數(shù)的前面,根據(jù) JavaScript 語法的定義,已聲明而未被賦值的變量會(huì)被自動(dòng)賦值為 undefined ,所以,第一次打印 declaredLater 變量的值就是 undefined,后面我們對(duì) declaredLater 變量進(jìn)行了賦值操作,所以,第二次再打印變量就會(huì)輸出Now it's defined!。
再來看一個(gè)例子:
var name = "Baggins";
(function () {
console.log("Original name was " + name);// "Original name was undefined"
var name = "Underhill";
console.log("New name is " + name);// "New name is Underhill"
})();
上述代碼中,我們先聲明了一個(gè)變量 name ,我們的本意是希望在第一次打印 name 變量時(shí)能夠輸出全局范圍內(nèi)定義的 name 變量,然后再在函數(shù)中定義一個(gè)局部 name 變量覆蓋全局變量,最后輸出局部變量的值??墒堑谝淮屋敵龅慕Y(jié)果和我們的預(yù)期完全不一致,原因就是我們定義的局部變量在其作用域內(nèi)被“提前”了,也就是變成了如下形式:
var name = "Baggins";
(function () {
var name; //注意:name 變量被提前了!
console.log("Original name was " + name);// "Original name was undefined"
name = "Underhill";
console.log("New name is " + name);//"New name is Underhill"
})();
由于 JavaScript 具有這樣的“怪癖”,所以建議大家將變量聲明放在作用域的最上方,這樣就能時(shí)刻提醒自己注意了。
2、函數(shù)聲明“被提前”
前邊說的是變量,接下來我們說說函數(shù)。
函數(shù)的“被提前”還要分兩種情況,一種是函數(shù)聲明,第二種是函數(shù)作為值賦值給變量,也即函數(shù)表達(dá)式。
先說第一種情況,上代碼:
isItHoisted();//"Yes!"
function isItHoisted() {
console.log("Yes!");
}
如上所示,JavaScript 解釋器允許你在函數(shù)聲明之前使用,也就是說,函數(shù)聲明并不僅僅是函數(shù)名“被提前”了,整個(gè)函數(shù)的定義也“被提前”了!所以上述代碼能夠正確執(zhí)行。
再來看第二種情況:函數(shù)表達(dá)式形式。還是先上代碼:
definitionHoisted();// "Definition hoisted!"
definitionNotHoisted();// TypeError: undefined is not a function
function definitionHoisted() {
console.log("Definition hoisted!");
}
var definitionNotHoisted = function () {
console.log("Definition not hoisted!");
};
我們做了一個(gè)對(duì)比,definitionHoisted 函數(shù)被妥妥的執(zhí)行了,符合第一種類型;definitionNotHoisted 變量“被提前”了,但是他的賦值(也就是函數(shù))并沒有被提前,從這一點(diǎn)上來說,和前面我們所講的變量“被提前”是完全一致的,并且,由于“被提前”的變量的默認(rèn)值是 undefined ,所以報(bào)的錯(cuò)誤屬于“類型不匹配”,因?yàn)?undefined 不是函數(shù),當(dāng)然不能被調(diào)用。
總結(jié)
通過上面的講解可以總結(jié)如下:
變量的聲明被提前到作用域頂部,賦值保留在原地
函數(shù)聲明整個(gè)“被提前”
函數(shù)表達(dá)式時(shí),只有變量“被提前”了,函數(shù)沒有“被提前”
3、var的副作用
隱式全局變量和明確定義的全局變量間有些小的差異,就是通過delete操作符讓變量未定義的能力。
通過var創(chuàng)建的全局變量(任何函數(shù)之外的程序中創(chuàng)建)是不能被刪除的。
無var創(chuàng)建的隱式全局變量(無視是否在函數(shù)中創(chuàng)建)是能被刪除的。
這表明,在技術(shù)上,隱式全局變量并不是真正的全局變量,但它們是全局對(duì)象的屬性。屬性是可以通過delete操作符刪除的,而變量是不能的:
// 定義三個(gè)全局變量
var global_var = 1;
global_novar = 2; // 反面教材
(function () {
global_fromfunc = 3; // 反面教材
}());
// 試圖刪除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// 測(cè)試該刪除
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
在ES5嚴(yán)格模式下,未聲明的變量(如在前面的代碼片段中的兩個(gè)反面教材)工作時(shí)會(huì)拋出一個(gè)錯(cuò)誤。
4、單var形式聲明變量
在函數(shù)頂部使用單var語句是比較有用的一種形式,其好處在于:
提供了一個(gè)單一的地方去尋找功能所需要的所有局部變量
防止變量在定義之前使用的邏輯錯(cuò)誤
少代碼(類型啊傳值啊單線完成)
單var形式長得就像下面這個(gè)樣子:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}
您可以使用一個(gè)var語句聲明多個(gè)變量,并以逗號(hào)分隔。像這種初始化變量同時(shí)初始化值的做法是很好的。這樣子可以防止邏輯錯(cuò)誤(所有未初始化但聲明的變量的初始值是undefined)和增加代碼的可讀性。在你看到代碼后,你可以根據(jù)初始化的值知道這些變量大致的用途。
以上就是針對(duì)javascript的var預(yù)解析與函數(shù)聲明提升的學(xué)習(xí)內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
使用JavaScript動(dòng)態(tài)設(shè)置樣式實(shí)現(xiàn)代碼及演示動(dòng)畫
使用onmouseover和onmouseout事件實(shí)現(xiàn)不同的效果而且是使用js動(dòng)態(tài)實(shí)現(xiàn),本文有利于鞏固你js與css方面的知識(shí),感興趣的你可以了解下哦,希望本文對(duì)你有所幫助2013-01-01
掃微信小程序碼實(shí)現(xiàn)網(wǎng)站登陸實(shí)現(xiàn)解析
這篇文章主要介紹了掃微信小程序碼實(shí)現(xiàn)網(wǎng)站登陸實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
js實(shí)現(xiàn)每日自動(dòng)換一張圖片的方法
這篇文章主要介紹了js實(shí)現(xiàn)每日自動(dòng)換一張圖片的方法,涉及javascript操作圖片與日期的相關(guān)技巧,非常簡單實(shí)用,需要的朋友可以參考下2015-05-05
jQuery實(shí)現(xiàn)仿百度首頁滑動(dòng)伸縮展開的添加服務(wù)效果代碼
這篇文章主要介紹了jQuery實(shí)現(xiàn)仿百度首頁滑動(dòng)伸縮展開的添加服務(wù)效果代碼,通過jQuery相應(yīng)鼠標(biāo)事件控制頁面元素的動(dòng)態(tài)變換功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-09-09
JavaScript箭頭函數(shù)的五種使用方法及三點(diǎn)注意事項(xiàng)
這篇文章主要介紹了JavaScript箭頭函數(shù)的五種使用方法及三點(diǎn)注意事項(xiàng),箭頭函數(shù)是ES6新增的定義函數(shù)的方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,需要的朋友可以參考一下2022-08-08
WebGL利用FBO完成立方體貼圖效果完整實(shí)例(附demo源碼下載)
這篇文章主要介紹了WebGL利用FBO完成立方體貼圖效果的方法,以完整實(shí)例形式分析了WebGL實(shí)現(xiàn)立方體貼圖的具體步驟與相關(guān)技巧,并附帶了demo源碼供讀者下載參考,需要的朋友可以參考下2016-01-01
純javascript實(shí)現(xiàn)的小游戲《Flappy Pig》實(shí)例
這篇文章主要介紹了純javascript實(shí)現(xiàn)的小游戲《Flappy Pig》,較為詳細(xì)的分析了javascript實(shí)現(xiàn)小游戲《Flappy Pig》的相關(guān)技巧,涉及javascript操作頁面元素移動(dòng)、碰撞、事件監(jiān)聽與觸發(fā)的相關(guān)技巧,需要的朋友可以參考下2015-07-07
JavaScript遍歷實(shí)現(xiàn)DFS算法和BFS算法
DFS(Depth?first?search)稱作「深度優(yōu)先遍歷」,BFS(Breadth?first?search)稱作「廣度優(yōu)先遍歷」。本文將通過JavaScript遍歷實(shí)現(xiàn)這兩種算法,需要的可以參考一下2023-01-01

