在談js的預解析以前,先看一段c++程序ios
#include <iostream>
using namespace std;
void useGreet(){
greet();
}
void greet(){
cout << "hello" <<endl;
}
int main() {
useGreet();
return 0;
}
複製代碼
只要是有c++基礎的都會知道以上程序是沒法經過編譯的,由於useGreet()函數中調用了一個未聲明的函數,即使greet()函數在其聲明。這是c++編譯器編譯順序自己決定的。 再來看用js寫的一段代碼c++
function useGreet(){
greet();
}
function greet(){
console.log('hello');
}
useGreet();
複製代碼
以上代碼是能夠正常運行的瀏覽器
以上兩個例子雖然與js的預解析的關係不是很大,可是也反映了js中預解析的內容函數
再來看這段js代碼ui
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
複製代碼
代碼的運行結果爲spa
undefined
2
複製代碼
再來看下面這段代碼code
let a = 1;
let fun = function(){
console.log(a);
a = 2;
console.log(a);
}
fun();
複製代碼
代碼的運行結果爲對象
1
2
複製代碼
兩段看似差別十分微小的代碼的運行結果是天差地別的。在解釋運行結果以前,咱們須要知道js中函數和變量在內存中的存儲模型,js的預解析,js的做用域鏈。ip
js的數據分爲兩類內存
- 基本類型
- 引用類型(對象類型) 其中基本類型是存儲於棧內存中,而對象是存儲於堆內存中。
var person = {
name: "Jack",
sex: "man"
}
複製代碼
上例中先聲明瞭一個變量person,而後對其進行賦值。其中person的值
{name: "Jack", sex: "man"}
存儲於堆內存,而person這個字面量存儲了一個地址,這個地址即爲person的值在堆內存中的存儲地址
js的預解析是指,在當前做用域中,JavaScript代碼執行以前,瀏覽器首先會默認的把全部帶var和function聲明的變量進行提早的聲明或者定義。即先執行變量的聲明和函數的聲明與賦值的語句,而後在按順序執行其餘語句。注意,變量和函數的預解析是不一樣的,變量的預解析只進行聲明而不執行賦值,而函數的預解析聲明與賦值都會執行。
例如
fun();
function fun(){
console.log("hello");
}
複製代碼
以上代碼能夠正常輸出hello
,其執行順序爲,先對 fun()
函數進行聲明和賦值,再執行fun()
的調用
再看下面一段代碼
fun(str);
function fun(str){
console.log(str);
}
let str = "hello";
複製代碼
以上代碼能夠的輸出結果爲undefined
,其執行順序爲,先對 fun()
函數進行聲明和賦值,在進行str
的聲明,而後執行fun()
函數的調用,而後執行str的賦值,由於在賦值以前就使用了str,因此輸出了str的初始值undefined
。
做用域鏈爲: 當在某個做用域要使用某個變量時,首先查找本做用域是否有該變量,如有中止查找並取得該變量,若沒有再查找本做用域的上級做用域,重複以上過程,直到找到該變量爲止。若到全局做用都沒有找到則報錯。
如今就能夠對最開始的兩段代碼的運行結果作出解釋
第一段
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
複製代碼
這段代碼有兩個做用域,一個全局做用域,還有一個fun()匿名函數做用域
先執行全局做用域的代碼
let a
=> let fun = function
=> fun()
=> a=1
複製代碼
再執行函數做用域的代碼,執行fun()函數調用的執行過程
let a // 預解析
=> console.log(a)
=> a=2
=> console.log(a)
複製代碼
第二段
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
複製代碼
這段代碼也有兩個做用域,一個全局做用域,還有一個fun()匿名函數做用域,惟一不一樣的是匿名函數做用域內a沒有let或var聲明
全局做用域的代碼的執行過程與第一段是同樣的
let a
=> let fun = function
=> fun()
=> a=1
複製代碼
但fun()函數調用的執行過程就不一樣了
console.log(a)
=> a=2
=> console.log(a)
複製代碼
fun()
的做用域內沒有任何變量或者函數的聲明,因此fun()
做用域內沒有預解析過程,第一句執行console.log(a)
,因爲a
在fun()
做用域內沒有定義,因此到fun()
做用域的上一級做用域中找,上一級中a
已經聲明而且賦值爲1,因此先輸出1;在執行a=2
,對a賦值爲2,再執行console.log(a)
,此時a = 2,因此輸出2