1. 程式人生 > >JS函式宣告和預解析的理解

JS函式宣告和預解析的理解

JS函式宣告方法

今天看到了一個自己關注了的大神給我回了私信,覺得自己彷彿摸到了大神的褲腿,哈哈,而且人還特別好,居然會給小菜鳥回私信,特別開心呀,一個菜鳥的小激動,言歸正傳啦
1.最為常見的函式宣告的方式,function+函式名稱識別符號
function func1([引數]){/*函式體*/}

2.將匿名函式賦給一個變數,呼叫方式:func2(引數)
var func2 = function([引數]){/*函式體*/}

3.定義一個函式func4,將它賦給變數func3,呼叫方法為:func3([引數]);或者func4([引數])
var func3 = function func4([引數]){/*函式體*/}

4.宣告func5為一個物件
var func5 = new Function();

1.函式宣告:

 function sum1(n1,n2){
     return n1 + n2;
 }

 2.函式表示式(函式字面量)

 var sum2 = function(n1,n2){
    return n1 + n2;

二者存在區別:
解析器會先解讀函式宣告,並使其在執行程式碼之前可訪問,而函式表示式則必須等到解析器執行到它所在的程式碼才能被真正解釋執行
例如:

     //函式預解析,使其在執行程式碼前可以訪問
     func1()
     function
func(a){
alert(a); }
</script> <script> //會提示func1未定義 func1(1); var func = function(a){ alert(a); }

之所以會出現上面的狀況,是因為在JavaScript中,存在“預解析”行為,深入理解預解析(看了一篇文章深有感悟)

一.變數和函式在記憶體中的展示

JavaScript中的變數和其他語言一樣,有基本資料型別和引用資料型別。基本資料型別:undefined,null,boolean,string,Number,引用資料型別主要是物件(包括{},[],/^$/,Date,Function)

var num = 24;
var obj = {name:'iceam',age:24};
function func(){
   console.log('hello world');
}

當瀏覽器載入html介面時,首先會提供一個全域性JavaScript程式碼執行的環境,稱之為全域性作用域。
基本資料型別按照值來操作,引用資料型別按照地址來操作


基本型別是直接儲存在棧的記憶體當中,而物件是儲存在堆記憶體中,變數只持有該物件的地址。所以obj持有一個物件地址oxff44,而函式func持有一個地址0xff66

console.log(func);
console.log(func());

第一行輸出的是整個函式定義的部分(也就是指函式本身)

function func(){
   cnsole.log('hello world');
}

func儲存的是一個地址,改地址指向一塊堆記憶體,該堆就保留了函式的定義

第二行輸出的是函式返回的結果

undefined

二.預解析

含義:在當前作用域中,JavaScript執行程式碼之前,瀏覽器回預設把所有帶var和function宣告的變數進行提前宣告或者定義

 var num = 24;

1.宣告:var num;告訴瀏覽器,在全域性作用域中有num變量了,如果一個變數只是聲明瞭,但是沒有賦值,預設值是undefined
2.定義:num = 12;定義即給變數賦值

2.2. var宣告的變數和function宣告的函式在預解析的區別

var宣告的變數和function宣告的函式在預解析的時候有區別,var宣告的變數在預解析的時候只是提前的宣告,function宣告的函式在預解析的時候會提前宣告並且會同時定義。也就是說var宣告的變數和function宣告的函式的區別是在宣告的同時有沒同時進行定義。

2.3. 預解析只發生在當前的作用域下

程式最開始的時候,只對window下的變數和函式進行預解析,只有函式執行的時候才會對函式中的變數函式進行預解析。
以例題作為分析:

console.log(num);
var num = 24;
console.log(num);

func(100,200);
function func(num1,num2){
   var total = num1 + num2;
   console.log(total);
}

輸出結果:

第一次輸出num,由於預解析的原因,把變數num的宣告提前了,定義(賦值)不會提前,所以,第一次輸出num是undefined,程式碼執行到第二次輸出num時,num已經定義了,直接輸出num的值
由於函式的宣告和定義是同時進行的,所以func()雖然是在func函式定義宣告之前呼叫的,但是依然可以正常呼叫

三、 作用域鏈

先理解以下三個概念:
函式裡面的作用域成為私有作用域,window所在的作用域稱為全域性作用域;
在全域性作用域下宣告的變數是全域性變數;
在“私有作用域中宣告的變數”和“函式的形參”都是私有變數;
在私有作用域中,程式碼執行的時候,遇到了一個變數,首先需要確定它是否為私有變數,如果是私有變數,那麼和外面的任何東西都沒有關係,如果不是私有的,則往當前作用域的上級作用域進行查詢,如果上級作用域也沒有則繼續查詢,一直查詢到window為止,這就是作用域鏈。
當函式執行的時候,首先會形成一個新的私有作用域,然後按照如下的步驟執行:
如果有形參,先給形參賦值;
進行私有作用域中的預解析;
私有作用域中的程式碼從上到下執行
函式形成一個新的私有的作用域,保護了裡面的私有變數不受外界的干擾(外面修改不了私有的,私有的也修改不了外面的),這也就是閉包的概念。

console.log(total);
var total = 0;
function func(num1,num2){
console.log(total);
var total = num1 + num2;
console.log(total);
}
func(100,200);
console.log(total);

以上程式碼執行的時候,第一次輸出total的時候會輸出undefined(因為預解析),當執行func(100,200)的時候,會執行函式體裡的內容,此時func函式會形成一個新的私有作用域,按照之前描述的步驟:
先給形參num1、num2賦值,分別為100、200;
func中的程式碼進行預解析;
執行func中的程式碼
因為在func函式內進行了預解析,所以func函式裡面的total變數會被預解析,在函式內第一次輸出total的時候,會輸出undefined,接著為total賦值了,第二次輸出total的時候就輸出300。 因為函式體內有var宣告的變數total,函式體內的輸出total並不是全域性作用域中的total。
最後一次輸出total的時候,輸出0,這裡輸出的是全域性作用域中的total。
在全域性作用域中宣告變數帶var可以進行預解析,所以在賦值的前面執行不會報錯;宣告變數的時候不帶var的時候,不能進行預解析,所以在賦值的前面執行會報錯。

console.log(num1);
var num1 = 12;

console.log(num2);
num2 = 12;


num2 = 12;相當於給window增加了一個num2的屬性名,屬性值是12;
var num1 = 12;相當於給全域性作用域增加了一個全域性變數num1,但是不僅如此,它也相當於給window增加了一個屬性名num,屬性值是12
注意:JS中,如果在不進行任何特殊處理的情況下,上面的程式碼報錯,下面的程式碼不再執行

五、 預解析中的一些變態機制

5.1 不管條件是否成立,都要把帶var的進行提前的宣告

if (!('num' in window)) { 
 var num = 12;
}
console.log(num); // undefined

JavaScript進行預解析的時候,會忽略所有if條件,因為在ES6之前並沒有塊級作用域的概念。本例中會先將num預解析,而預解析會將該變數新增到window中,作為window的一個屬性。那麼 ‘num’ in window 就返回true,取反之後為false,這時程式碼執行不會進入if塊裡面,num也就沒有被賦值,最後console.log(num)輸出為undefined。
5.2 只預解析“=”左邊的,右邊的是指,不參與預解析

fn(); // -> undefined(); // Uncaught TypeError: fn is not a function
var fn = function () {
 console.log('ok');
}

fn(); -> 'ok'
function fn() {
 console.log('ok');
}
fn(); -> 'ok'

5.3 自執行函式:定義和執行一起完成

(function (num) {
 console.log(num);
})(100);

自治性函式定義的那個function在全域性作用域下不進行預解析,當代碼執行到這個位置的時候,定義和執行一起完成了。
補充:其他定義自執行函式的方式

~ function (num) {}(100) 
+ function (num) {}(100) 
- function (num) {}(100) 
! function (num) {}(100)

5.4 return下的程式碼依然會進行預解析

function fn() {        
 console.log(num); // -> undefined
 return function () {    

 };        
 var num = 100;     
}         
fn();

函式體中return下面的程式碼,雖然不再執行了,但是需要進行預解析,return中的程式碼,都是我們的返回值,所以不進行預解析。
5.5 名字已經宣告過了,不需要重新的宣告,但是需要重新的賦值

var fn = 13;          
function fn() {         
 console.log('ok');        
}             
fn(); // Uncaught TypeError: fn is not a function

解決方法:

var fn = 13;
var a = function fn(){
 console.log('ok');
}
a();//輸出ok