JavaScript是一門單線程,解釋型,弱類型的動態語言,解釋一行執行一行。函數
JavaScript執行過程首先先語法分析,就是分析一遍代碼有沒有語法錯誤,解析期間不會執行代碼。接着就開始預編譯,預編譯完了就開始一行一行執行代碼。線程
預編譯過程會建立兩個對象,一個是全局的Global Object對象,簡寫GO,另外一個是函數的Activation Object對象,簡寫AO。兩個只是做用域不一樣,建立步驟是同樣的。cdn
預編譯大概步驟:對象
建立AO、GO對象ip
找形參和變量聲明,做爲屬性名,值爲undefined作用域
統一實參和形參it
找函數聲明,賦值函數體io
說的抽象了,咱們以一個函數爲例:console
function fn(a) {編譯
console.log(a);
var a = 1;
console.log(a);
function a() {};
console.log(a);
var b = function () {};
console.log(b);
function c() {}
}
fn(3);
建立AO = {}
把形參和變量聲明做爲屬性名和賦值undefined
AO = {
a: undefined,//參數a var a function a
b: undefined,//var b
c :undefined,//function c
}
統一形參和實參
AO = {
a: 3,
b: undefined,//var b
c :undefined,//function c
}
找函數聲明,賦值函數體
AO = {
a: function a(){},
b: undefined,//var b
c: function c(){},
}
接着就是一行一行執行了:
function fn(a) {
console.log(a);
var a = 1;
console.log(a);
function a() {};
console.log(a);
var b = function () {};
console.log(b);
function c() {}
}
fn(3);
AO = {
a: function a(){},
b: undefined,
c: function c(){},
}
當執行第一個打印的時候,打印出function,而後var a = 1的時候,聲明已經聲明過了,其實就a = 1,因此第二個打印是1,到了聲明函數a的時候已是聲明過的,再打印也是1,至於b和c就不用多說了。最後結果就是f a(){}、一、一、f(){}。
其實能夠記住幾個點,函數聲明是總體提高,變量聲明只是聲明提高。還有,若是一個變量沒有聲明,那麼默認就是window的:
(function fn() {
var a = b = 10;
}());
console.log(b);//10
console.log(a);//err
b沒有直接var聲明,那麼就是全局window的,因此b能打印,a就會報錯。
有個點要注意,JavaScript在預編譯階段, 會解釋函數聲明, 但卻會忽略表式。好比一個自執行函數:
(function fn() {
}())
當執行到有()的時候,JavaScript會去對這個表達式求解獲得返回值,返回的是一個函數且有(),因此直接執行了,其它的自執行函數原理都是這樣的,都是經過表達式。函數轉換爲表達式的方法並不必定要靠分組操做符(),咱們還能夠用void操做符,!操做符+操做符等等。
+function () {}()
void(function () {alert(0)}())
console.log(function () {alert(0)}())
這些表達式均可以當即執行函數,就算+號獲得的最終結果是NaN,可是在隱式轉換以前卻要先執行函數。
函數參數你能夠看做在函數裏面隱式的聲明瞭一個變量a:
function fn(a) {
var a;
console.log(a);//3
}
fn(3)
並且函數參數裏面在預編譯過程當中,會造成一個臨時做用域,在預編譯完了以後會消失:
function fn(a, b = function () {a = 5}) {
console.log(a);//3
b();
console.log(a);//3
}
fn(3)
(a, b = function () {a = 5})這是一個臨時的做用域,這裏面的參數a就算改變了也影響不到原來的參數a。只有在參數做用域裏面纔有效果:
function fn(a, b = (function () {a = 5})()) {
console.log(a);//5
console.log(a);//5
}
fn(3)