最近在吃飯的時(shí)候看到一道關(guān)于函數(shù)聲明提升的問題
var a = 1;
function b() {
a = 10;
return;
function a() {
}
}
b();
console.log(a) //1
很多人在看第一眼的時(shí)候會(huì)認(rèn)為結(jié)果為10,我作為一個(gè)小白第一反應(yīng)也是認(rèn)為函數(shù)內(nèi)部的a是全局變量,所以在執(zhí)行b后a的值變?yōu)?0,之后再看了后面的分析后才恍然大悟,在b函數(shù)執(zhí)行后a依然是1,于是琢磨這寫了人生中第一篇博客,寫的不好請(qǐng)見諒,若有技術(shù)問題,會(huì)在第一時(shí)間解決
1.變量聲明
使用var聲明的變量,如果在聲明時(shí)變量沒有賦值,系統(tǒng)則默認(rèn)賦值為undefined
2.沒有塊級(jí)作用域
在ES6的let,const出現(xiàn)之前,JavaScript是沒有塊級(jí)作用域的,也就是自己的執(zhí)行環(huán)境,在《JavaScript高級(jí)程序設(shè)計(jì)》中提到
if (true) {
var color = "blue";
}
alert(color); //"blue"
在JavaScript中,if語句中的變量聲明會(huì)將變量加到當(dāng)前的執(zhí)行環(huán)境(這里指的是全局環(huán)境)中相當(dāng)于以下代碼,而var聲明的變量是在JavaScript預(yù)編譯階段就執(zhí)行了的
//預(yù)編譯階段
var color;
//執(zhí)行階段
if (true) {
color = "blue";
}
console.log(color); //"blue"
還有一個(gè)非常經(jīng)典的問題
for (var i = 0; i < 10; i++) {
//這里做任意事情
}
alert(i); //10
這里彈出i的值為10,可以理解為for循環(huán)開始的時(shí)候定義了i變量,然后在for循環(huán)完成后,i變量依然存在于全局環(huán)境中,并且在alert(i)的時(shí)候for循環(huán)已經(jīng)結(jié)束,i的值等于10,所以alert(i)的結(jié)果就是10了
3.變量聲明提升
再來回到第一個(gè)例子,如果它的條件為false
if (false) {
var color = "blue";
}
alert(color); //undefined
經(jīng)過變量聲明提升后實(shí)質(zhì)與以下代碼是一樣的
//預(yù)編譯階段
var color;
//執(zhí)行階段
if (false) {
color = "blue";
}
console.log(color); //undefined
因?yàn)閏olor定義了,但是if的條件為false,所以只聲明了color變量卻沒有賦值,最后顯示undefined
JavaScript編譯器會(huì)把變量聲明看成兩個(gè)部分分別是聲明操作(var color)和賦值操作(color="blue")
聲明操作在編譯階段進(jìn)行冰垄,聲明操作會(huì)被提升到執(zhí)行環(huán)境的頂部球榆,值是undefined(表示未初始化)
賦值操作會(huì)被留在原地等待執(zhí)行階段
注釋:聲明提前是在JavaScript引擎的預(yù)編譯時(shí)進(jìn)行丈冬,是在代碼開始運(yùn)行之前。
4.函數(shù)聲明提升
函數(shù)的兩種創(chuàng)建方式
1. 函數(shù)聲明
2. 函數(shù)表達(dá)式
函數(shù)聲明:
function sum(num1, num2) {
return num1 + num2;
}退腥;
函數(shù)聲明提升會(huì)在編譯階段把函數(shù)和函數(shù)體整體都提前到執(zhí)行環(huán)境頂部,所以我們可以在函數(shù)聲明之前調(diào)用這個(gè)函數(shù)
sum(10, 10); //20
function sum(num1, num2) {
return num1 + num2;
};
JavaScript編譯器在預(yù)編譯階段把代碼編譯后其實(shí)是這樣的
var sum;
sum = function (num1, num2) {
return num1 + num2;
}乘客;
sum(10, 10); //20
函數(shù)聲明會(huì)把先聲明一個(gè)變量sum,同時(shí)把函數(shù)賦值給變量sum
函數(shù)表達(dá)式:
var sum = function (num1, num2) {
return num1 + num2;
}
通過把匿名函數(shù)賦值給變量的定義函數(shù)的方法叫做函數(shù)表達(dá)式淀歇,而通過函數(shù)表達(dá)式創(chuàng)建的函數(shù)和函數(shù)聲明不一樣易核,函數(shù)表達(dá)式?jīng)]有函數(shù)聲明提升,只有定義了之后才能調(diào)用,上述代碼實(shí)質(zhì)上是這樣的
var sum;
sum = function (num1, num2) {
return num1 + num2;
}
在函數(shù)表達(dá)式創(chuàng)建的函數(shù)之前執(zhí)行sum函數(shù)
sum(10, 10);//報(bào)錯(cuò) Uncaught TypeError: sum is not a function
var sum = function (num1, num2) {
return num1 + num2;
}
因?yàn)橥ㄟ^函數(shù)表達(dá)式創(chuàng)建的函數(shù)沒有函數(shù)提升,所以執(zhí)行sum(10,10)時(shí)根本沒有創(chuàng)建函數(shù),但是沒有創(chuàng)建函數(shù)應(yīng)該會(huì)顯示undefined,為什么會(huì)報(bào)錯(cuò)呢,實(shí)質(zhì)上上述代碼是這樣的
//預(yù)編譯階段
var sum;
//執(zhí)行階段
sum(10, 10);
sum = function (num1, num2) {
return num1 + num2;
};
可以把函數(shù)表達(dá)式看作2部分,第一部分聲明變量sum,第二部分給變量sum賦于這個(gè)匿名函數(shù),而通過var聲明的sum存在變量提升,在預(yù)編譯階段會(huì)提升到執(zhí)行環(huán)境頂部,然后在執(zhí)行sum(10,10)就會(huì)報(bào)錯(cuò),提示sum不是一個(gè)函數(shù),undefined當(dāng)然不是一個(gè)函數(shù)啦,第三行執(zhí)行完畢后,這時(shí)sum才指向了這個(gè)函數(shù)
函數(shù)聲明提升>形參>變量聲明提升
當(dāng)同時(shí)存在函數(shù)聲明和變量聲明時(shí),來看這個(gè)例子
getName(); //1
var getName = function () {
console.log(2);
}
function getName() {
console.log(1);
}
getName(); //2
當(dāng)存在函數(shù)聲明和變量聲明時(shí),函數(shù)聲明在前,變量聲明在后,所以上述代碼是這樣的
//預(yù)編譯階段
//使用函數(shù)聲明創(chuàng)建的函數(shù)函數(shù)聲明提升
var getName;
getName = function () {
console.log(1);
};
//變量聲明提升,因?yàn)橹耙呀?jīng)聲明過getName,所以再次聲明var會(huì)被忽略
var getName;
//執(zhí)行階段
getName(); //1
getName = function () {
console.log(2);
};
getName(); //2
再來看一道面試題
var a = 1;
function b() {
a = 10;
return;
function a() {
}
}
b();
console.log(a); //1
在b函數(shù)內(nèi)部,因?yàn)榇嬖诤瘮?shù)變量聲明提升,導(dǎo)致return后面的function a () {}被提升到了b函數(shù)內(nèi)部的頂部,等同于在b函數(shù)內(nèi)部聲明了局部變量a(與函數(shù)外部的全局變量a不同),并且給變量a賦值給一個(gè)匿名函數(shù),最后再給局部變量a賦值為10,最后return退出函數(shù),局部變量a在b函數(shù)執(zhí)行完后被銷毀,console.log(a)打印變量a的時(shí)候會(huì)搜索變量a浪默,最后只在最后的全局變量中發(fā)現(xiàn)了a牡直,隨即打印了a的值,即數(shù)字1(第一行聲明的變量a)
var a = 1;
function b() {
var a;
a = function () {
};
a = 10;
return;
}
b();
console.log(a); //1