JavaScript變量與函數提高

做者:小斯基@毛豆前端前端

本文要點:

  • 函數提高;
  • var,let,const三種方式變量提高的區別;

首先看一些你們熟悉的代碼:
code1:git

// foo調用在定義以前
foo();
function foo() {
  console.log('hello world');
}
// hello world
複製代碼

code2:github

// 報錯
foo();
var foo = function() {
  console.log('hello world');
}
// TypeError: foo is not a function
複製代碼

code3:函數

// 不報錯
console.log(`a is ${a}`);
var a = 'hello world'
// a is undefined
複製代碼

code4:ui

// 報錯
console.log(`a is ${a}`);
let a = 'hello world'
// ReferenceError: a is not defined
複製代碼

code5:spa

// 報錯
console.log(`a is ${a}`);
const a = 'hello world'
// ReferenceError: a is not defined
複製代碼

下面進入正題: JS引擎會在正式執行以前先進行一些預處理,在這個過程當中,首先將變量定義和函數定義的各個階段操做(建立、初始化和賦值)提高至當前做用域的頂端,而後進行接下來的處理。(注:因爲引擎的不一樣,預處理也會有所差別)。code

code1和code2比較好理解。code1中函數定義會被提高到當前做用域頂部,而後再作後續執行,即foo函數的定義被js引擎自動提高到foo()前面,因此能夠正常調用。而code2中,foo其實是一個變量定義,而後將foo變量指向一個匿名函數, 那既然變量定義也會有提高,爲什麼會報錯呢?作用域

code3的變量提高理解起來也比較簡單,可是code4,code5中分別使用let、const拋出了錯誤,看來js對var、let、const三種定義方式的變量提高有所區別。下面將詳細講解:get

js變量和函數定義有三個階段: 建立create、初始化initialize 和賦值assign,針對不一樣的定義對不一樣的階段作提高:string

  1. 函數定義的建立、初始化和賦值三個階段都被提高了;
  2. var定義的建立、初始化被提高了,賦值未被提高;
  3. let的建立被提高了,但初始化和賦值都未被提高;
  4. const的建立被提高了,初始化未被提高,特殊的一點是const沒有賦值階段,因此const定義的變量值不能改變;

如今咱們詳細梳理下上述代碼:

  1. code1中函數定義的三個階段都被提高,因此是先建立變量foo(此時還沒法使用foo),而後將foo初始化爲undefined,再將foo賦值爲函數體,最後執行foo();
  2. code2中是var變量定義,先建立foo,而後將其賦值爲undefined,以後調用foo(),因此這裏會報錯;
  3. code3和code2相似,foo在初始化後執行console.log,因此打印出undefined;
  4. code4中是let變量定義,先建立foo,因爲初始化未被提高,因此建立以後當即執行console.log,因而就報錯了;
  5. code5與code4報錯過程相似。

如今咱們理解了js對各類變量和函數定義作提高時的區別。那麼變量和函數定義的提高有沒有優先級順序呢?答案是有的。請看以下代碼:

console.log(foo);
function foo() {
  console.log('hello world');
}

var foo = 1;
console.log(foo);

// [Function: foo]
// 1
複製代碼

這段代碼可解釋爲: 首先找到js引擎先找到var定義語句,建立變量foo,而後將其初始化爲undefined,以後找到function定義語句,從新建立foo,以後將其賦值爲函數體(由於function的賦值過程也被提高了),而後執行第一個console.log,接下來 才執行foo變量的賦值過程(由於var定義的賦值過程未被提高),最後執行第二個console.log。若將代碼中var換成let,將會拋出SyntaxError: Identifier 'foo' has already been declared。由於let變量定義不容許有第二次 建立過程, function定義的建立過程就拋錯了。

關於let還有一個頗有意思的現象,假如咱們執行以下代碼:

let x = x; // x以前未定義過
// Uncaught ReferenceError: Cannot access 'x' before initialization

x = 1;
// Uncaught ReferenceError: x is not defined
複製代碼

在拋出一段錯誤以後會發現,在當前上下文中不能再使用名爲x的變量了,也不能對x賦值。致使這個現象的緣由大概是: let定義首先建立x,以後執行代碼,發現let x = x等號右邊的x對x產生了引用,因爲變量初始化以前就被引用了,因此拋出Cannot access 'x' before initialization,而且這個錯誤會致使let x語句在建立x後初始化失敗(前面的 錯誤致使不能進入初始化過程),因爲js變量定義的初始化過程只有一次,一旦失敗就不能再次初始化了,此時的x處於一個已建立但又不能初始化的狀態,即所謂的暫時性死區, 此後對x的引用或賦值都會拋出錯誤。

另外,ES6中的class聲明也存在提高,可是它和let、const同樣,有一些限制,若是在聲明位置以前引用,會拋出一個異常。

最後提醒你們在寫js代碼過程當中,儘可能遵循一點: 先聲明,後使用

相關文章
相關標籤/搜索