jacascript 函數聲明、函數表達式與聲明提高(hoisting機制)

前言:這是筆者學習以後本身的理解與整理。若是有錯誤或者疑問的地方,請你們指正,我會持續更新!javascript

聲明、定義、初始化

  聲明的意思是宣稱一個變量名的存在,定義則爲這個變量分配存儲空間,初始化則是給該變量名的存儲空間賦予初始值;java

  javascript 中,變量沒有固定類型,其存儲空間會隨着初始化(並賦值)而變化;面試

        <script type="text/javascript">
            var a;//聲明a
            console.log(a);//undefined;   只聲明,沒有賦值,返回undefined
            
            a = 'hello world!';//(定義)初始化變量a而且賦值'hello world!'
            console.log(a);//hello world!
            
            var a;//再次聲明a,但沒有賦值,a的值不會被清空
            console.log(a);//hello world!
        </script>

hoisting 機制

  javascript 代碼在執行時,通常是一行一行往下執行的,可是這裏面又有一個變量聲明提高的概念;express

  javascript 引擎在執行時,會把全部變量聲明和函數聲明都提高到當前做用域的最前面(hoisting 機制);瀏覽器

變量聲明提高

        <script type="text/javascript">
//            console.log(a);//a is not defined   a沒有聲明
            console.log(b);//undefined   沒找到b(沒有初始化賦值),但已經聲明瞭
            var b = 'hello world!';   //聲明並初始化b賦值'hello world!'
            console.log(b);//hello world!
        </script>

  上面代碼中 a 和 b 的區別就是b聲明提早了,a 沒有聲明;因此上面代碼至關於:函數

        <script type="text/javascript">
            var b;//聲明b
//            console.log(a);//a is not defined  a沒有聲明
            console.log(b);//undefined
            b = 'hello world!';  //初始化並賦值'hello world!'
            console.log(b);//hello world!
        </script>    

 

函數聲明提高

  一直對一些基本的概念的理解有些模糊,先梳理如下:學習

  1. 函數聲明(function statement),使用function關鍵字聲明一個函數,再指定一個函數名,叫函數聲明。如:function fnName(){};
  2. 匿名函數anonymous functions),使用function關鍵字聲明一個函數,可是沒有命名,這個函數就叫匿名函數,寫法就是:function(){};
  3. 函數表達式(function expression),把一個匿名函數當作值傳給一個變量,叫函數表達式,這是最多見的函數表達式語法形式。如:var fnName = function(){};

函數聲明

  函數聲明(function statement),使用function關鍵字聲明一個函數,再指定一個函數名,叫函數聲明。如:function fnName(){};spa

  但須要注意的是:code

  1. 函數的聲明能夠是有條件的,好比嵌套在 if 語句中,有的瀏覽器會將這種有條件的聲明當作無條件的,不論條件是true仍是false,咱們最好不要這樣寫;
  2. 在嚴格模式下,只容許在全局做用域或函數做用域的頂層聲明函數。也就是說,不容許在非函數的代碼塊內聲明函數。

  非嚴格模式下:blog

        <script type="text/javascript"> //非嚴格模式 if(true){ function find(){ console.log(456); } } find();//456 if(false){ function cantFind(){ console.log(123); } } cantFind();//TypeError: cantFind is not a function </script>

  嚴格模式下:

        <script type="text/javascript"> 'use strict' //嚴格模式下 if(true){ function find(){ console.log(456); } } find();//ReferenceError: cantFind is not defined </script>

 

函數表達式

  函數表達式(function expression),把一個匿名函數當作值傳給一個變量,叫函數表達式,這是最多見的函數表達式語法形式。如:var fnName = function(){};

  1. 在 function 前面加!、+、 -、=甚至是逗號等或者把函數用()包起來均可以將函數聲明轉換成函數表達式;
  2. 可是!、+、-等運算符還會和函數的返回值進行運算,有時形成沒必要要的麻煩,因此咱們通常用()把函數聲明包起來或者用 = ,達到轉換函數表達式的目的;
        <script type="text/javascript">
            //在function前面加!、+、 -、=甚至是逗號等到均可以將函數聲明轉換成函數表達式,咱們通常用()或者 = 
            var a = function b(){  
                return b; 
            }
            console.log(a());//function b(){console.log(b);}
            console.log(b());//ReferenceError: b is not defined   
                             //函數表達式的標識符在外部做用域是看不到的,只能在內部做用域看到
        </script>

 

  函數聲明提高有兩種狀況:1.函數聲明;2.把函數做爲一個值傳給一個變量(函數表達式);

  和變量聲明提高同樣,函數聲明也會提高,而且定義也會提高(這個說法也不太準確,看下面的例子)

        <script type="text/javascript">
            play();//函數聲明提高了,之因此能夠正常運行,是由於定義也被提早了 
            function play(){
                console.log('hello world!');//hello world!
            }
        </script>

  以上代碼中函數play()聲明提高了,之因此能夠正常運行,是由於函數聲明提高了,定義也被提高了(這個說法也不太準確,看下面的例子),以上代碼至關於:

        <script type="text/javascript">
            function play(){
                console.log('hello world!');//hello world!
            }        
            play();
        </script>

  咱們有時候會把一個函數做爲值傳給一個變量,這時變量會被聲明提高,但函數不會,它只是一個函數表達式,咱們只須要它的返回值給變量;

  若是它是一個具名函數表達式,那麼函數表達式的標識符(函數名)在外部做用域是找不到的,只在內部做用於能找到;

        <script type="text/javascript">
            //console.log(play);//ReferenceError: play is not defined   聲明錯誤
            //play();//ReferenceError: play is not defined 聲明錯誤

            console.log(games);//undefined  變量聲明瞭,但沒賦值,因此沒找到
            //games();//TypeError: games is not a function   類型錯誤
            
            var games = function play(){  //函數表達式
                console.log(typeof play);
            }
            //play();//ReferenceError: play is not defined 聲明錯誤
            games();//function  函數表達式的標識符(函數名)在外部做用域是找不到的,只在內部做用於能找到;
        </script>

  上面寫的函數聲明的定義也被提高了,這個說法並不太準確,

<script type="text/javascript">
    if (!("a" in window)) {   
        function a() { 
            return '爲何下面彈出的a是undefined';
        }
    }
    console.log(a);  //undefined
    //若是定義也被提早,這裏應該是function a(){},因此這裏定義並無被提早,只有函數名被提早了,而後if語句進不去了,
    //由於函數聲明能夠和函數表達式相互轉換,只是寫法不一樣而已,
    //因此上面的代碼有個說法,這裏的函數a能夠理解成var a = function(){},函數聲明的方式提早的是函數名,然而這又不能解釋爲何函數能在函數聲明以前能被調用;
    
    b();
    function b(){
        console.log('函數聲明的疑問,爲何b能執行');
    }
</script>

  由於函數聲明能夠和函數表達式相互轉換,只是寫法不一樣而已,因此上面的代碼有個說法,函數a能夠理解成 var a = function(){},函數聲明的方式提早的是函數名,然而這又不能解釋爲何函數能在函數聲明以前能被調用;

變量名解析順序

  同一做用域下,相同變量名,後面的賦值會覆蓋前面的;

  同一做用域下,若是變量名和函數名相同,那麼函數名會覆蓋變量名,不論它們在代碼的順序如何;可是它們的初始化賦值是按順序執行的;

  因此,同一做用域下應避免使用相同變量名;

        <script type="text/javascript">
            console.log(a);//function a(){console.log('我是函數');}  函數名和變量名相同,函數名會覆蓋變量名
            
            var a = 'hello world!';
            function a(){
                console.log('我是函數');
            };
            
            console.log(a);//hello world!
            a();//TypeError: a is not a function  類型錯誤
        </script>

  上面代碼中,a的值最後是字符串,緣由就在於:

  1. 變量的賦值是按順序執行的;
  2. 函數聲明提高會把表達式也提早;
  3. 相同變量名,後面的賦值會覆蓋前面的;

  上面的代碼至關於:

        <script type="text/javascript">
            console.log(a);//function a(){console.log('我是函數');}  函數名和變量名相同,函數名會覆蓋變量名
            
            function a(){
                console.log('我是函數');
            };
            var a = 'hello world!';
            
            console.log(a);//hello world!
            a();//TypeError: a is not a function  類型錯誤
        </script>

 

有一道經典面試題:

        <script type="text/javascript">
            b = c;
            console.log(b); 
            console.log(c);  
            b();
            console.log(a);    
            function c() {
                a = 1, b = 2, c = 3; 
            };
        </script>

  函數c還沒運行,因此裏面的變量是不知道的,可是函數c已經聲明提早了,又由於第一行b=c,因此b和c都是函數c ;

  函數c運行以後,裏面的變量a,b,c被發現是全局變量,而且給它們賦值,因此外部能找到a=1,而且你還會發現b=2; c=3;

  若是函數c未曾運行過,那麼是找不到它們的 ;

 

這道題換一下:

        <script type="text/javascript">
            b = function c() {
                a = 1, b = 2, c = 3;
            };
            
            b();
            console.log(a); 
            console.log(b); 
            console.log(c); 
        </script>

  函數 b 運行以後,發現了裏面的全局變量,而且給它們賦值 a=1; b=2;

  可是有個特殊的變量 c,c 是函數表達式的名字,函數的名字,規範說明函數名不能被改變,因此這裏設置 c=3 實際上是對函數名賦值,是無效的,也不會被當作全局變量;並且函數表達式的標識符只有內部做用域能夠找到,因此外部是找不到的,報錯 ReferenceError: c is not defined 引用錯誤;

相關文章
相關標籤/搜索