JS執行順序-函數聲明提升、匿名函數、函數表達式


大方向上: JS 是按照 代碼塊 進行 編譯、執行 的。

學習至:


因為沒有好好地分類。可能會比較雜。為了系統地學習,先了解幾個概念。

一. <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
    

三. 預編譯期 和 執行期

事實上,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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM