JavaScript預解釋是一種毫無節操的機制
前言
JavaScript是一門解釋型的語言 , 想要執行JavaScript程式碼需要兩個階段
- 編譯階段: 編譯階段就是我們常說的JavaScript預解釋(預處理)階段,在這個階段JavaScript直譯器將完成把JavaScript指令碼程式碼轉換到位元組碼
- 執行階段: 在編譯階段JavaScript直譯器藉助執行環境把位元組碼生成機械碼,並從上到下按順序執行
本文就重點介紹預解釋,框架圖如下:
一、什麼是預解釋
預解釋:JavaScript程式碼執行之前,瀏覽器首先會預設的把所有帶var和function的進行提前的宣告或者定義
1.理解宣告和定義
宣告(declare):如var num;=>告訴瀏覽器在全域性作用域中有一個num的變量了;如果一個變數只是聲明瞭但是沒有賦值,預設的值是undefined
定義(defined):如num=12;=>給我們的變數進行賦值。
2.對於帶var和function關鍵字的在預解釋的時候操作不一樣的
var =>在預解釋的時候只是提前的宣告
function =>在預解釋的時候提前的宣告+定義都完成了
3.預解釋只發生在當前的作用域下。
例如:開始只對window下的進行預解釋,只有函式執行的時候才會對函式中的進行預解釋
二、作用域鏈
1.如何區分私有變數和全域性變數?
1)在全域性作用域下宣告(預解釋的時候)的變數是全域性變數
2)只有函式執行會產生私有的作用域,比如for(){}、if(){}和switch(){}都不會產生私有作用域
3)在"私有作用域中宣告的變數(var 宣告)"和"函式的形參"都是私有的變數。在私有作用域中,程式碼執行的時保遇到了一個變數,首先我們需要確定它是否為私有的變數,如果是私有的變數,那麼和外面的沒有在何的關係;如果不是私有的,則往當前作用域的上級作用域進行查詢,如果上級作用域也沒有則繼續查詢,一直找到window為止,這就是作用域鏈。
我們舉個例子來區別私有變數和全域性變數:
//=>變數提升:var a;var b;var c;test=AAAFFF111; var a=10,b=11,c=12; function test(a){ //=>私有作用域:a=10 var b; a=1;//=>私有變數a=1 var b=2;//=>私有變數b=2 c=3;//=>全域性變數c=3 } test(10); console.log(a);//10 console.log(b);//11 console.log(c);//3
判斷是否是私有變數一個標準就是是否是在函式中var宣告的變數和函式的形參都是私有的變數。本道題目在test函式中a是形參和var b定義的變數b都是私有變數。
2.函式傳參
這是因為當函式執行的時候,首先會形成一個新的私有的作用域,然後按照如下的步驟執行:
1)如果有形參,先給形參賦值
2)進行私有作用域中的預解釋
3)私有作用域中的程式碼從上到下執行
我們來看一道例題
var total=0;
function fn(num1,num2){
console.log(total);//->undefined 外面修改不了私有的
var total=num1 +num2;
console.log(total);//->300
}
fn(100,200);
console.log(total);//->0 私有的也修改不了外面的
3.JS中記憶體的分類
棧記憶體:用來提供一個供JS程式碼執行的環境,即作用域(全域性作用域/私有的作用域)
堆記憶體:用來儲存引用資料型別的值。物件儲存的是屬性名和屬性值,函式儲存的是程式碼字串。
三、全域性作用域下帶var和不帶var的區別
我們先來看以下兩個例子:
//例題1
console.log(num);//->undefined
var num=12;
//例題2
console.log(num2);//->Uncaught ReferenceError:num2 is not defined
num2=12;//不能預解釋
當你看到var num=12時,可能會認為只是個宣告。但JavaScript實際上會將其看成兩條宣告語句:var num;和 num=12;第一個定義宣告是在預解釋階段進行的。第二個賦值宣告會被留在原地等待執行階段。num2=12 相當於給window增加了一個叫做num2的屬性名,屬性值是12;而var num=12 首先它相當於給全域性作用域增加了一個全域性變數num,它也相當於給window增加了一個屬性名num2,屬性值是12。兩者最大區別:帶var的可以進行預解釋,所以在賦值的前面執行不會報錯;不帶var的是不能進行預解釋的,在前面執行會報錯;
接下來我們舉例說明:
//例題1
var total=0;
function fn(){
console.log(total);//undefined
var total=100;
}
fn();
console.log(total);//0
//例題2
var total=0;
function fn(){
console.log(total);//0
total=100;
}
fn();
console.log(total);//100
例題1中帶var變數在私有作用域中可以預解釋,所以第一個console打出來的值為undefined。私有作用域中出現的一個變數不是私有的,則往上級作用域進行查詢,上級沒有則繼續向上查詢,一直找到window為止,例題2中不帶var變數不是私有的,所以往上級找
四、預解釋五大毫無節操的表現
1.預解釋的時候不管你的條件是否成立,都要把帶var的進行提前的宣告。
請看下面這道例題:
if(!("num" in window)){
var num=12;//這句話會被提到大括號之外的全域性作用域:var num;->window.num;
}
console.log(num);//undefined
2.預解釋的時候只預解釋”=”左邊的,右邊的值,不參與預解釋
請看下面這道例題:
fn();//報錯
var fn=function (){ //window下的預解釋:var fn;
console.log("ok");
};
3.自執行函式:定義和執行一起完成了。
自執行函式定義的那個function在全域性作用域下不進行預解釋,當代碼執行到這個位置的時候定義和執行一起完成了。常見有以下幾種形式:
(function(num){})(10);
~function(num){}(10);
+function(num){}(10);
-function(num){}(10);
!function(num){}(10);
4.函式體中return下面的程式碼雖然不再執行了,但是需要進行預解釋;return後面跟著的都是我們返回的值,所以不進行預解釋;
function fn(){
//預解釋:var num;
console.log(num);//->undefined
return function(){
};
var num=100;
5.函式宣告和變數宣告都會被提升。但是一個值得注意的細節(這個細節可以出現在有多個“重複”宣告的程式碼中)是函式會首先被提升,然後才是變數。在預解釋的時候,如果名字已經宣告過了,不需要從新的宣告,但是需要重新的賦值;
我們先來看下兩個簡單的例子:
//例題1
function a() {}
var a
console.log(typeof a)//'function'
//例題2
var c = 1
function c(c) {
console.log(c)
var c = 3
}
c(2)//Uncaught TypeError: c is not a function
當遇到存在函式宣告和變數宣告都會被提升的情況,函式宣告優先順序比較高,最後變數宣告會被函式宣告所覆蓋,但是可以重新賦值,所以上個例子可以等價為
function c(c) {
console.log(c)
var c = 3
}
c = 1
c(2)
接下來我們看下兩道比較複雜的題目:
//例題3
fn();
function fn(){console.log(1);};
fn();
var fn=10;
fn();
function fn(){console.log(2);};
fn();
1.一開始預解釋,函式宣告和賦值一起來,fn 就是function fn(){console.log(1);};遇到var fn=10;不會重新再宣告,但是遇到function fn(){console.log(2);}就會從重新賦值,所以一開始fn()的值就是2
2.再執行fn();值不變還是2
3.fn重新賦值為10,所以執行fn()時報錯,接下去的語句就沒再執行。
//例題4
alert(a);
a();
var a=3;
function a(){
alert(10)
}
alert(a);
a=6;
a()
1.函式宣告優先於變數宣告,預解釋時候,函式宣告和賦值一起來,a就是function a(){alert(10)} ,後面遇到var a=3,也無需再重複宣告,所以先彈出function a(){alert(10)}
2.a(),執行函式,然後彈出10
3.接著執行了var a=3; 所以alert(a)就是顯示3
4.由於a不是一個函數了,所以往下在執行到a()的時候, 報錯。