大方向上: JS 是按照 代碼塊 進行 編譯、執行 的。
學習至:
- 1.變量聲明提升
- 2.新唐的博客
- 3.js中匿名函數的創建與調用方法分析
- 4.前端聖經 - 《高程三》
- 5.深入理解變量聲明提升和函數聲明提升
因為沒有好好地分類。可能會比較雜。為了系統地學習,先了解幾個概念。
一. <script>
區分的代碼塊。
JS是按照代碼塊 編譯 和 執行的。代碼塊間 相互獨立,但是 變量和方法 共享。
<script>
alert('代碼塊一');
</script>
<script>
alert('代碼塊二');
</script>
<script>
var a = 3;
</script>
<script>
console.log(a); // 3
</script>
二. 關於函數(聲明式函數、賦值型函數、匿名函數、自執行函數)
-
聲明式函數 和 賦值型函數
聲明函數與賦值函數的區別在於: 在 JS 的預編譯期間,聲明式函數會被先提取出來,然后才按照順序執行 JS代碼。
- 聲明式同學:
A(); // 'A' function A() { console.log('A'); }
- 賦值型選手:
B(); // error, B is not a function var B = function() { console.log('B'); }
- 聲明式同學:
-
什么是匿名函數?
沒有名字的函數就是匿名函數(突然有點想笑)。
function() {} // 匿名函數
-
什么是自執行函數
(function() { console.log(3); })();
需要注意,下面這樣的寫法會報錯
function() { console.log(3); }();
原因解析如下:
- 1.
function {}()
其實這是一個函數聲明。 - 2.JS運行的時候,會對函數聲明進行預編譯,然后在執行其他語句。
- 3.也就是說
function(){}
先被預編譯了。然后JS看見了()
。JS一臉懵逼,這不得報錯嗎。 - 4.而匿名函數實際上是一個語句,正常執行。
還需要知道的是,自執行函數的標識可以是
!function(){}() (function(){})() ~function(){}() void function(){}()
自執行函數是可以帶參數的,格式是這樣的!
(function(num){ console.log(num); })(3); // 3
- 1.
三. 預編譯期 和 執行期
事實上,JS的解析分為兩個階段:預編譯 和 執行期。
-
預編譯期間:對本代碼塊中的所有 聲明變量 和 函數進行處理(類似於 C語言的編譯) ,但需要注意,1.此時處理函數的只是聲明式函數2.變量也只是進行了聲明但是沒有進行初始化和賦值
-
編譯期間:從上到下 編譯 代碼塊。
接下來,我們分別結合上面的 第一點(代碼塊) 和 第二點(函數) 食用一下。
f(); // 我是函數聲明2
function f() {
console.log('我是函數聲明1');
}
function f() {
console.log('我是函數聲明2');
}
結論1:都是函數聲明的情況下,后來居上的規則 沒有變。
f(); // 我是函數聲明
function f() {
console.log('我是函數聲明');
}
var f = function() {
console.log('我是賦值型函數');
}
結論2:函數聲明 提前於 賦值函數。
console.log(a);
var a; // undefined
console.log(b); // 程序直接報錯,不往下進行
結論3:變量聲明 處於 預編譯階段。證明了我們上面的正確性。
函數下方聲明了 a,被提前(提升)了。函數下方沒有聲明 b(之前也沒有),直接報錯。
<script>
f(); // f is not defined.
</script>
<script>
function f(){};
</script>
結論4:JS引擎是按照代碼塊的順序來執行的。!!!對於還未加載的代碼,是沒有辦法進行預處理的。這也是編譯核心所在。
console.log(f); // Function
function f() {
console.log(1);
}
var f = 3;
結論5:函數聲明提升優先級大於 變量聲明,函數聲明覆蓋 變量聲明
<script>
console.log(a);
</script>
<script>
console.log(3);
</script>
結論6:代碼塊之間如果報錯,其他的代碼塊如果正確依舊能夠正確執行。
四. 開戰!!! 五是總結和整理,不想看題目的可以直接跳至 五。
代碼一:
f();
var scope = 'out';
function f() {
console.log(scope); // undefined
var scope = 'in';
console.log(scope); // 'in'
}
- 變量覆蓋,后面定義聲明的會覆蓋前面的。
- 函數內部:先進行變量聲明。
實際運行過程:
var scope = 'out';
function f() {
var scope; // 覆蓋了外部的。
console.log(scope);
scope = 'in';
console.log(scope);
}
f();
代碼二:
var getName = function(){
console.log(2);
}
function getName (){
console.log(1);
}
getName();
答案是 2. 這題看懂了前面的話很容易:
- 函數聲明被放在了預編譯階段。
- 后來的會覆蓋前面的。
實際運行過程
function getName (){
console.log(1);
}
var getName = function(){
console.log(2);
}
getName(); // 2
代碼三:
getName(); // 1
function getName() {
console.log(1);
}
var getName = function() {
console.log(2);
}
依舊很容易,不解釋。變量賦值來得太慢,不像龍卷風。
五. 總結和整理
JS的執行順序如下:
- 1.讀入第一個代碼塊
- 2.做語法分析,有錯則報語法錯誤,並跳轉到 5
- 3.對var變量和function做 預編譯(永遠不會報錯,因為只解析正確的)
- 4.執行代碼塊,有錯則報錯
- 5.如果還有下一個代碼塊,則讀入下一個代碼端,重復 2
- 6.結束
六. 想了想,是不是沒有提到變量提升?這個其實是 JS的一個 烏龍。
ES之前,JS沒有變量作用域。只有 函數作用域 和 全局作用域。
{
var a = 3; // 我們以為它只是個局域變量
}
console.log(a); // 3 -- 沒想到它在全局中打印出來了,(手動笑哭)
使用了 ES6 的 let 之后,{} 內即為一個 變量作用域。
{
let a = 3;
}
console.log(a); // error
變量提升 完。
complete.