JavaScript中級指南-01 函數提高與變量提高(筆記)

先來看一段代碼編程

var foo = function () {
    console.log('foo1');
}
foo();  // foo1

var foo = function () {
    console.log('foo2');
}
foo(); // foo2
複製代碼

而後來看這段代碼bash

function foo() {
    console.log('foo1');
}
foo();  // foo2

function foo() {
    console.log('foo2');
}
foo(); // foo2
複製代碼

打印的結果倒是兩個 foo2。函數

以上兩段代碼,第一個列子中的變量提高和第二個列子中的函數提高,當二者代碼混入的時候,你還可以正確的分辨出執行順序麼?ui

讓咱們來看一個混入的小例子再來詳細瞭解

console.log(a) // undefined
var a = 'hello JS' 

/* 這裏咱們要考慮一個問題? 在咱們聲明a以前爲何輸出a不會報錯呢?*/

num = 6;
num++;
var num;
console.log(num) // 7 這裏考慮一個問題? 爲何給一個尚未聲明的變量賦值會不報錯呢

function hoistFunction() {
    foo();
    function foo() {        
        console.log('running...')    
    }
}
hoistFunction(); // running...  這裏考慮一個問題? 爲何foo執行的時候沒有報錯呢
複製代碼

分析緣由

S引擎會在正式執行代碼以前進行一次」預編譯「,預編譯簡單理解就是在內存中開闢一些空間,存放一些變量和函數。具體步驟以下(browser):spa

  • 頁面建立GO全局對象(Global Object)對象(window對象)。
  • 加載第一個腳本文件
  • 腳本加載完畢後,進行語法分析。
  • 開始預編譯
* 查找函數聲明,做爲GO屬性,值賦予函數體(函數聲明優先)
* 查找變量聲明,做爲GO屬性,值賦予undefined
GO/window = {
    //頁面加載建立GO同時,建立了document、navigator、screen等等屬性,此處省略
    a: undefined,
    num: undefined,
    hoistFunction: function(y){
        foo();
        function foo() {        
        console.log('running...')    
        }
    }
}
複製代碼
  • 解釋執行代碼(直到執行函數hoistFunction,該部分也被叫作詞法分析)
* 建立AO活動對象(Active Object)
* 查找形參和變量聲明,值賦予undefined
* 實參值賦給形參
* 查找函數聲明,值賦給函數體
* 解釋執行函數中的代碼
GO/window = {
    //變量隨着執行流獲得初始化
    a: hello JS,
    num: 6, // num++ num:7
    hoistFunction: function(y){
        foo();
        function foo() {        
        console.log('running...')    
        }
    }
}
複製代碼
  • 第一個腳本文件執行完畢,加載第二個腳本文件
  • 第二個文件加載完畢後,進行語法分析
  • 開始預編譯
  • 重複預編譯步驟 ....

預解析機制使得變量提高(Hoisting),從字面上理解就是變量和函數的聲明會移動到函數或者全局代碼的開頭位置(他所在的做用域)。咱們再來分析一下上面的小例子加深一下理解。code

console.log(a) // 執行以前,變量提高做爲window的屬性, 值被設置爲undefined
var a = 'hello JS' 

/* JavaScript 僅提高聲明,而不提高初始化 */

num = 6;
num++;
var num;
console.log(num) // 變量提高 值爲undefined的num賦值爲6,再自增 => 7

function hoistFunction() {
    foo();
    function foo() {        
        console.log('running...')    
    }
}
hoistFunction(); // 函數聲明提高,能夠在函數體以前執行
複製代碼

注: JS並不存在真正的預編譯,var與function的提高實際是在語法分析階段就處理好的。並且JS的預編譯是以一個腳本文件爲塊的。一個腳本文件進行一次預編譯,而不是全文編譯完成再進行」預編譯」的對象

什麼是提高(Hosting)?

引擎會在解釋JavaScript代碼以前首先對其進行編譯,編譯過程當中的一部分工做就是找到全部的聲明,並用合適的做用域將他們關聯起來,這也正是詞法做用域的核心內容。ip

變量提高

變量聲明的提高是以變量所處的第一層詞法做用域爲「單位」的,即全局做用域中聲明的變量會提高至全局最頂層,函數內聲明的變量只會提高至該函數做用域最頂層。那麼開始的一段代碼通過預編譯則變爲內存

var a;
console.log(a); // undefined
a = "a";
var foo = () => {
    var a; // 全局變量會被局部做用域中的同名變量覆蓋
    console.log(a); // undefined
    a = "a1";
}
foo();
複製代碼

ES6新增了let和const關鍵字,使得js也有了「塊」級做用域,並且使用let和const 聲明的變量和函數是不存在提高現象的,比較有利於咱們養成良好的編程習慣。作用域

函數提高

有了上面變量提高的說明,函數提高理解起來就比較容易了,但較之變量提高,函數的提高仍是有區別的。

console.log(foo1); // [Function: foo1]
foo1(); // foo1
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
function foo1 () {
	console.log("foo1");
};
var foo2 = function () {
	console.log("foo2");
}
// 這裏可能會有人有疑問? 爲foo2會報錯,不一樣樣也是聲明?
// foo2在這裏是一個函數表達式且不會被提高
複製代碼

函數提高只會提高函數聲明,而不會提高函數表達式。

最後以一個小列子結尾,看看你到底有沒有了解函數提高以及變量提高

var a = 1;
function foo() {
    a = 10;
    console.log(a);
    return;
    function a() {};
}
foo();
console.log(a);
複製代碼

上面的小例子你答對了沒有?

上面的代碼塊通過預編譯後能夠看作以下形式(只分析foo方法內部狀況):

var a = 1; // 定義一個全局變量 a
function foo() {
    // 首先提高函數聲明function a () {}到函數做用域頂端
    // 而後function a () {}等同於 var a =  function() {};最終形式以下
    var a = function () {}; // 定義局部變量 a 並賦值。
    a = 10; // 修改局部變量 a 的值,並不會影響全局變量 a
    console.log(a); // 打印局部變量 a 的值:10
    return;
}
foo();
console.log(a); // 打印全局變量 a 的值:1
複製代碼
相關文章
相關標籤/搜索