javascript面試必考知識點

     最近在學習過程當中,總感受書本上 對於javascript 的一些知識點講的比較亂,想要看一些視頻教程來深刻理解一下,又發現大多數只是流於表面,閱讀一些博客,又感受在關鍵部分彷佛總被簡單的幾句話帶過,彷佛在搪塞讀者, 因而決定趁週末靜下心來好好捋一下這部份內容。javascript

涉及知識點

javascript運行過程、預編譯、執行期上下文、做用域、做用域鏈、當即執行函數、閉包 以上的知識點就像一個有方向的圓環,在掌握每個知識點以前彷佛都得先了解其它某個或者某些知識點,讓人不知從哪提及。在捋了半天關係以後,決定按照本文的順序總結。java

javascript運行過程

javascript 運行分3個步驟:數組

  1. 語法分析:javascript 引擎對你的代碼進行分析,通篇掃描分析,檢查是否有低級語法錯誤
  2. 預編譯:能夠理解爲,在內存中開闢一塊空間,存放一些變量和函數 (這樣講有點抽象,不要着急,後面將單獨深刻講解預編譯)
  3. 解釋執行:就是執行代碼

預編譯

首先,什麼叫預編譯? 講解預編譯以前,首先須要知道 變量提高 和函數提高bash

所謂提高就是指,變量或者函數在聲明以前就使用它,舉個例子:數據結構

<script>
        console.log(a)
        var a=234;
        test();
        function test(){
            console.log('good');
        }
    </script>
複製代碼

在這裏插入圖片描述
上面的 變量a 和函數 test 在聲明以前就使用了,可是沒報錯,爲何,由於有預編譯過程,在預編譯過程,將變量和函的聲明進行了提高。 爲何沒有輸出 234 可是輸出了 good ? 注意,提高的時變量的聲明,而不是定義 變量:聲明提高 函數:函數聲明總體提高

應該有人會發現問題,以下代碼閉包

<script>
        console.log(a)
        var a=234;
        function a(){
            return a;
        }
    </script>
複製代碼

輸出的是啥?其實就是涉及到變量和函數哪一個先提高的問題是吧 函數

在這裏插入圖片描述

注意,不要根據結果來判斷 函數後提高,上面的結果不是由於有什麼前後提高的說法,而是由於預編譯過程,要想搞明白上面的原理,繼續往下看: 提高只是預編譯過程當中的兩個現象,要理解預編譯,還須要理解一些東西學習

一、imply global (暗示全局變量):任何變量,若是該變量未經聲明就賦值,則此變量爲全局對象全部,注意,是任何,以下面語句中的 a 和 cui

a = 1;
var b = c = 3;
複製代碼

二、一切聲明的全局變量,均爲window的屬性spa

<script>
        function test(){
            var a = b = 123;
        }
        test();
        var c = 100;
    </script>
複製代碼

b未經聲明就賦值,注意上面說的是任何變量,未經聲明就賦值,即爲全局對象全部,也就是不須要考慮它是在哪裏賦值的,反之 a 是在函數內聲明賦值的,所以它的做用域就是函數內部,在外面訪問不了,因而輸出undefined,而 c自己就是在全局下聲明 的,天然爲全局對象全部。好,前面的理解了,接下來看到預編譯

預編譯四部曲

  1. 建立AO對象(執行期上下文,先理解成一個對象便可,存放屬性和屬性值)
  2. 找形參和變量聲明,並將其做爲AO對象的屬性,將屬性值設置爲undefined
  3. 將實參值和形參相統一
  4. 在函數體裏找函數聲明(注意是函數聲明,區分函數聲明和函數表達式的區別),也做爲AO對象的屬性,屬性值即爲函數體
預編譯發生時間

預編譯發生在函數執行前一刻

一個例子搞懂預編譯四部曲,注意搞懂AO對象內部屬性值的變化:

<script>
        function func(a){
            console.log(a);
            var a=123;
            console.log(a);
            function a(){};
            console.log(a);
            var b = function (){};
            console.log(b);
            function d (){};
        }
        func(1);
    </script>
複製代碼

咱們一步一步進行四部曲:

//第一步:建立AO對象(執行期上下文,先抽象理解成一個對象便可,
								存放屬性和屬性值)
	AO{
	            
	        }
複製代碼
//第二步:找形參和變量聲明,並將其做爲AO對象的屬性,
		將屬性值設置爲undefined
     AO{
         a:undefined //形參 ,注意函數內部也有一個 var a 可是注意,
         			由於a 先是形參,一個對象是不能有兩個同名的屬性的,
         			因此第一步以後AO對象裏的 a是找的形參a
         b:undefined //變量聲明
     }
複製代碼
//第三步:將形參值與實參相統一
     AO{
         a:1 // 實參是1 ,自此以後不用關注a怎麼來的,關注的是 a 值的變化
         b:undefined //變量聲明
     }
複製代碼
//第四步:在函數體裏找函數聲明(注意是函數聲明,
區分函數聲明和函數表達式的區別),也做爲AO對象的屬性,屬性值即爲函數體

上面的函數體中就只有 a 和 d是函數聲明, b是函數表達式,不是聲明, 
AO對象裏已經有屬性 a了,所以只需將其屬性值改爲函數體,
再添加屬性 d,將值賦爲d的函數體,最終AO對象就是下面的狀況
 AO{
     a:function a(){}
     b:undefined 
     d:function d (){}
 }
複製代碼

接下來執行函數 func(1),函數體以下,注意觀察執行過程當中AO對象中屬性值的變化

function func(a){
      console.log(a); //此時AO裏的a爲 function a(){},輸出
      var a=123;      //將AO裏的 a 值改爲 123
      console.log(a); //此時 AO裏的 a 值爲123,因此輸出123
      function a(){}; //這句不看,由於在預編譯過程已經將它提高了
      console.log(a); //此時AO裏的a值仍是 123,因此輸出123
      var b = function (){}; //將AO裏的b值改爲function (){} 
      console.log(b); //此時AO裏的b值爲函數體,所以輸出一個函數體
      function d (){}; //這句也不看,由於預編譯過程已經提高了
  }
複製代碼

結果以下:

在這裏插入圖片描述
每一個函數在執行時都會產生一個執行期上下文,也就是上面的AO,它是一個對象

上面說函數在執行的時候會產生一個獨一無二的執行期上下文,那麼,在整個全局下,每每不僅有函數,還有其它的東西,好比全局變量之類的,因此,在全局下也有一個執行期上下文 ,也是個對象 (global object) 簡稱GO ,本文如下部分全部通常的執行期上下文都簡稱爲AO,全局下的執行期上下文都簡稱爲GO,咱們回到一開始講的例子

<script>
        console.log(a)
        var a=234;
        function a(){
            return a;
        }
    </script>
複製代碼

GO的建立過程跟通常的AO是同樣的,可是有一點區別,那就是既然是在全局下 的執行期上下文,也就不存在形參了,因此只有預編譯四部曲中的第1,2,4步驟,也就是隻有3個步驟

//第一步:建立GO對象
GO{
            
        }
複製代碼
//第二步:尋找變量聲明
	GO{
          a:undefined
       }
複製代碼
//第三步:尋找函數聲明,並將其值賦爲函數體
	GO{
          a:function a(){ return a; }
       }
複製代碼

預編譯完成,而後執行console.log(a) ,此時的a是函數,因此控制檯輸出結果是:

在這裏插入圖片描述

做用域和做用域鏈

首先在javascript中,咱們說一切都是對象,函數也是對象,並且函數是第一類對象,被稱做一等公民。對象,有屬性,函數也有屬性,有些屬性是咱們能夠訪問的,有些屬性是確實存在可是確不能夠被咱們拿來訪問的,舉個例子

function test(){
}
複製代碼

好比屬性 test.name test.prototype 分別表示函數名和函數原型,這兩個屬性是咱們能夠訪問的,test.[[ scope ]] 屬性就屬於其中一個不能夠被訪問的屬性,scope意爲範圍,[[ scope ]] 這個屬性存放的就是函數的做用域。準確的說存放的是函數的做用域鏈。 那麼什麼是做用域,什麼是做用域鏈呢? 做用域:可訪問變量,對象,函數的集合 做用域鏈:[[ scope ]] 中存放着執行期上下文的集合,這個集合呈鏈式連接,咱們把這種鏈式連接稱爲做用域鏈。 但看概念彷佛仍是有點抽象,首先,從語法上來解讀一下做用域鏈的概念:首先,做用域鏈由不少執行期上下文組成,這些執行期上下文的又不是散亂的擺放,有必定的位置關係:鏈式連接。

這裏又提到了執行期上下文,也就是上面提到的AO,那麼具體什麼是執行期上下文呢? 執行期上下文 :**某個函數或全局代碼的執行環境,該環境中包含執行代碼須要的全部信息。**能夠簡單的認爲它就是一個對象 當函數執行時,會建立一個稱爲 執行期上下文 的對象,一個執行期上下文定義了一個函數執行時的環境,函數每次執行時對應的執行期上下文都是獨一無二的,因此屢次調用一個函數會致使建立多個執行期上下文,當函數執行完畢,它建立的執行期上下文會被銷燬。 下面經過一個例子來理解做用域,做用域鏈,執行期上下文的關係。

能夠看出,真正的執行期上下文比上面講到的 AO其實內部存放的東西是更復雜的,上面的AO只是執行期上下文的一種抽象提取。

<script>
        function a(){
            function b(){
                var b=234;
            }
            var a=123;
            b();
        }
        var glob=100;
        a();
    </script>

複製代碼

從全局的角度來解讀上面的代碼:定義了一個函數 a ,聲明瞭並賦值變量glob 。函數a已經定義了,那它就有上面提到的一個函數該有的屬性,此時,a 的 [[ scope ]]屬性是什麼?此時的 a.[[ scope ]] 是一個全局的執行期上下文 GO

在這裏插入圖片描述

而後 a() 執行,產生一個 執行期上下文 aAO, 並放在做用域鏈的頂端,那麼 a 的 做用域鏈爲

在這裏插入圖片描述

a() 要完成執行,必然 b() 要執行完成,b函數建立時

在這裏插入圖片描述

b在執行時產生一個執行期上下文bAO,並將其放在做用域鏈的頂端,那麼b的做用域鏈爲

在這裏插入圖片描述

從上面的流程看,若是用一種數據結構去表示做用域鏈的話,用棧表示比較合適。每次新產生一個執行期上下文,就會放到本身做用域鏈的頂端,釋放的時候也是從做用域鏈自頂向下釋放執行期上下文,即後入先出。

當即執行函數

定義:此類函數沒有聲明,在一次執行事後即釋放。適合作初始化工做。 只執行一次,執行以後就被釋放(銷燬)

建立方式

1.(function (){}());W3C建議
2.(function (){})()
複製代碼

執行符號 :()

只有表達式才能被執行符號執行,被執行符號執行的函數會自動忽略函數名 由於執行後被釋放了,函數名也訪問不了了,因此直接去掉函數名也沒什麼區別

var test = function (){
    ...
}()
//函數聲明:
function test(){
    console.log('a'');
}

//函數表達式舉例
var x = function test(){
    console.log('a'');
}
複製代碼

函數聲明前加個符號->變成表達式,如+ - ! !! && ||等 可是注意若是是邏輯運算符,操做數數量還得符合該運算符要求,如&& ||

! function test(){
    console.log('a'');
}

複製代碼

例:

function(a,b,d){
    console.log(a+b+c);
}(1,2,3);
複製代碼

系統不會執行,可是也不會報錯 系統如何理解:

function(a,b,d){
    console.log(a+b+c);
}

(1,2,3);
//系統將其識別爲正常的分開寫法
複製代碼

閉包

閉包是指能夠訪問另外一個函數做用域中變量的函數,建立閉包的最多見的方式就是在一個函數內建立另外一個函數,經過另外一個函數訪問這個函數的局部變量,利用閉包能夠突破做用鏈域,將函數內部的變量和方法傳遞到外部。

閉包的特性:

1.函數內再嵌套函數 2.內部函數能夠引用外層的參數和變量 3.參數和變量不會被垃圾回收機制回收

經典例子:
function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        arr[i] = function(){
            document.write(i+  " ");
        }
    }
    return arr;
}
var maArr=test() 
for(var j=0;j<10;j++){
    myArr[j]();
}

複製代碼

執行結果是? 10 10 10...10 //總共10個10 代碼解讀: test() 的結果是返回一個數組,數組存放的是10個函數體, 每一個函數體的做用是打印一個值 注意,只是返回函數體,不是返回函數的執行結果 myArrj纔是執行

test執行結束的判斷條件:i==10終止循環,函數執行完畢 返回10個函數體

arr[i] = function(){
    document.write(i+  " ");
}
複製代碼

上面的是一條賦值語句 系統在識別的時候只能讀到函數的引用,而無論函數體內部的是什麼,

arr[i] = function(){
   
}
複製代碼

只有在執行的時候纔會去取i值, 10個函數體在外部執行的時候纔去訪問i,10個函數在執行的時候分別產生一個 執行期上下文 iAO, 也就是本身做用域鏈的頂端,而後訪問i值準備將其打印, 可是本身的AO裏面沒有i變量,因此沿着做用域鏈去找test函數執行時產生的 執行期上下文 AO,也就是你們訪問的都是同一個執行期上下文, 訪問的都是同一個i,也就是10

那如何想要在本身想打印的時候打印出0-9呢? 運用當即執行函數:

function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        (function (j){
            arr[j] = function(){
                document.write(j+" ")
            }
        }(i));
    }
    return arr;
}
var myArr=test() 
for(var j=0;j<10;j++){
    myArr[j]();
}
複製代碼

怎麼理解: 返回的arr裏面存的是 10個當即執行函數,也就是下面的函數

(function (j){
    arr[j] = function(){
        document.write(j+" ")
    }
}(i));
複製代碼

將i做爲實參傳給形參j ,在i從0-9的循環過程裏,依次發生的是

arr[0] = function(){
    
}
arr[1] = function(){
    
}
...
arr[9] = function(){
    
}
複製代碼

注意,當即執行,執行的是 function (j){} 它裏面的只是一條賦值語句,對arr數組的每個元素賦值,也就是一個函數的引用 這個函數的功能是打印一個值,也就是上面提到的,系統怎麼識別語句的問題,此時打印語句沒有被執行 當函數在外面執行的時候,就是myArrj時,也就是打印函數的執行,此時它們去訪問 j值, 可是它們本身的AO裏沒有 j變量,因而沿着做用域鏈去訪問 j值,也就是當即執行函數執行時產生的AO, 而10個當即執行函數產生的AO是不同的,裏面的j值分別是0-9,因此打印出0-9

閉包的防範

閉包會致使多個執行函數共用一個公有變量,若是不是特殊須要,應儘可能防止這種狀況發生。

相關文章
相關標籤/搜索