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

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

學習至:html


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

一. <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');
    }
  • 什麼是匿名函數?.net

    沒有名字的函數就是匿名函數(忽然有點想笑)。code

    function() {} // 匿名函數
  • 什麼是自執行函數htm

    (function() {
      console.log(3);
    })();

    須要注意,下面這樣的寫法會報錯blog

    function() {
      console.log(3);
    }();
    緣由解析以下:
      1. function {}()其實這是一個函數聲明。
      1. JS運行的時候,會對函數聲明進行預編譯,而後在執行其餘語句。
      1. 也就是說function(){}先被預編譯了。而後JS看見了()。JS一臉懵逼,這不得報錯嗎。
      1. 而匿名函數其實是一個語句,正常執行。

    還須要知道的是,自執行函數的標識能夠是
    !function(){}() (function(){})() ~function(){}() void function(){}()
    自執行函數是能夠帶參數的,格式是這樣的!
    function(num){ console.log(num); }(3); // 3ip


三. 預編譯期 和 執行期

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

相關文章
相關標籤/搜索