js中的預編譯,做用域鏈,當即執行函數,閉包,包裝類

引言

剛剛學完js以後我感受JavaScript並無什麼東西,好像也不是很難。可是本身運用的時候發現本身學的並不紮實,裏面的知識點仍是挺多的,甚至有些理解了我仍是會出錯。因此爲了打好基礎,我會複習回顧JavaScript的知識點,作好記錄,記錄是我本身的學習反饋,也是同你們的學習交流,找到本身的不足。這個小記錄總結了js中基礎且重要的五個問題,看完後可以深刻的理解預編譯中變量被提高,閉包的做用以及閉包觸發哪些問題等知識點。數組

我會從深度和廣度兩個方面來敘說。緩存

文章的開始

js語言有兩個特色,一個是單線程語言,一個是解釋性語言(讀一句執行一句)。bash

知識點1:預編譯

1.js執行三步曲

  • 語法分析:通篇分析,通篇掃描一遍看有沒有低級錯誤不進行編譯
  • 預編譯: 函數總體提高,變量的聲明提高
  • 解釋執行:解釋一行執行一行

ps:定義變量包括變量聲明和變量賦值,閉包

任何未經聲明就賦值的變量歸window全部,window就是全局的域。 一切聲明的全局變量非局部變量,就都是window的屬性模塊化

2.函數中的預編譯

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

1.建立AO對象,AO執行期上下文學習

2.找出形參和變量聲明放到AO{}裏面,且值爲undefinedui

3.實參和形參相統一this

4.找出函數裏面的函數聲明而且函數體爲值spa

而後在AO裏面拿東西,寫的東西是給電腦看的,想拿東西要到電腦裏面定義好的地方去拿。而不是視覺上的效果

來看一個栗子

function fn(a) {
    console.log(a);  //function a() {}
    var a = 123;
    console.log(a);  //123
    
    function a() {}
    console.log(a);  //123
    var b = function() {}
    console.log(b);  //function() {}
    
    function d() {}
    console.log(d);  //function d() {}
}

fn(1);
複製代碼

這道題包括形參名,函數名,變量名都同樣的狀況,因此咱們不能根據本身看到的去判斷,但是看在編譯以前生成的執行期上下文。

2.1第一步:建立AO對象

AO{

}

2.2第二步:找出變量聲明和形參,值爲undefined

AO{

形參a:undefined
變量聲明b:unfined
複製代碼

}

其中有一個形參a,後邊的 var a = 123中有又有一個變量a的聲明由於前邊有一個形參a因此不用重複寫。

2.3第三步:形參實參相統一

AO {

a:1;
變量聲明b:unfined
複製代碼

}

2.4第四步:找出函數裏面的函數聲明而且函數體爲其值

a,d爲函數聲明,b爲函數表達式

AO {

a:function a() {}

變量聲明b:unfined

d:function d() {}
複製代碼

}

這a的值由1變成function a(){},是由於a是一個函數聲明,因此a的值由1變成了函數體。此時預編譯的過程完成。而後再進行編譯,解釋一行執行一行。預編譯過程當中執行的代碼在編譯的時候就不會再執行,如var a = 123; 在執行的過程當中就不會再執行var a,變量聲明被提高優先執行了。因此直接執行a = 123.

3.全局中的預編譯

全局裏的預編譯與函數裏面的預編譯相似,第一步建立GO對象,第二步找出變量聲明,沒有第三步實參和形參相統一。下面結合具體的例子來看看

3.1暗示全局變量

function test (){
    var a = b = 124;
    console.log(window.a);  //undefined
    console.log(window.b);  //124
}
複製代碼

上面的變量b是沒有通過聲明就賦值了124,因此歸window所

4.預編譯的例子

4.1噁心的預編譯0

var x =1; var y = z = 0;
function add(n) {
    return n = n+1;
}
y = add(x);
function add(n) {
    return n =n + 3;
}
z = add(x)
複製代碼

請輸出x,y,z的值。最後結果應該x爲1,y和z都是4。由於在預編譯的時候同名的函數會被覆蓋,而不是以咱們的直觀感受來判斷。所以執行因此1,2,4的結果是錯誤的。

4.1噁心的預編譯1

//GO{
//    testfunction test(){};
//    a:undefined;
//    c:234;
// }
function test (){
    console.log(b);      //undefined
    if(a) {
        var b = 100;
    }
    console.log(b);  //undefined
    c = 234;
    console.log(c);// 234
}
var a;

// AO{
//    b:undefined
//  }
test();
a= 10;
console.log(c);//234
複製代碼

在沒有執行test()以前 首先生成GO。window === GO

GO{ test:function test(){}; a:undefined; }

執行test生成

AO{ b:undefined } 在test函數中,有一個沒有變量聲明就賦值的變量c,因此應該歸全局全部因而

GO{ test:function test(){}; a:undefined; c:234; }

4.2噁心的預編譯2

//GO {
    global: undifined
    fn: function fn(){}
//}
global = 100;
 function fn() {
     console.log(global);
     global = 200;
     conaole.log(global);
     var global = 300;
 }
 
 // AO {
 //     global:undefined
 // }
 fn();
 var global;
複製代碼

首先生成GO,而後再生成AO.第一個打印的global由於fn函數裏面就有聲明變量global,不須要到GO裏面去找。因此第一個打印的就是undefined,不是100,第二個打印global=200.

4.3噁心的預編譯3

function bar() {
    return foo;
    foo = 10;
    function foo() {
        
    }
    var foo = 11;
}
console.log(bar());//function foo(){}

複製代碼

首先console.(bar())就是打印出bar的結果,也就是咱們要知道返回的foo的值。根據預編譯四部曲咱們知道函數聲明的優先級最高,因此foo:function foo(){}


console.log(bar());
function bar() {
    foo = 10;
    function foo(){
        
    }
    var foo = 11;
    return foo;
}
複製代碼

在最後對foo賦值11,因此打印出來不是函數聲明而是11.可能有些掘友會問foo是一個局部變量,爲何能夠在全局裏面輸出呢?那是由於全局裏訪問的是全局裏面的bar函數,foo是函數裏面的一個變量。

知識點2:做用域鏈

做用域鏈與原型鏈不一樣,做用域鏈[[scope]],這裏面scope的第一位存放GO,第二位存放AO。做用域鏈是執行期上下文的集合。

1.執行期上下文:

函數被執行時會建立一個執行期上下文,一個執行期上下文定義了一個函數執行時的環境,函數每次執行時的執行期上下文都是獨一無二的,因此屢次調用一個函數會致使建立多個執行期上下文,函數每次執行時,都會把新生成的執行期上下文填充到做用域鏈的最頂端。函數執行完畢,執行期上下文被銷燬。

2.如何查找變量

從做用域鏈頂端依次向下查找。

function a() {
    function b() {
        function c() {}
        c();
    }
    b();
}
a();
複製代碼

這個例子裏面有abc三個函數,函數定義的時候產生GO,函數執行前產生GO.做用域鏈以下

a defined a.[[scope]] ---> 0:GO
a doing   a.[[scope]] ---> 0: aAO //aAO表示a的AO
                           1: GO
                           
                           
b defined  b.[[scope]] ---> 0: aAO
                            1: GO      
b doing    b.[[scope]] ---> 0: bAO
                            1: aAO
                            2: GO   
                            
                            
c defined  c.[[scope]] --->  0: bAO
                             1: aAO
                             2: GO  
c doing    c.[[scope]] --->  0: cAO
                             1: bAO
                             2: aAO
                             3: GO


複製代碼

知識點3當即執行函數

1.當即執行函數執行完就能夠被銷燬,節省空間針對初始化功能的函數

//(function () {})()
//(function (){}()) //w3c 建議的方法

複製代碼

上面的括號就是將函數聲明變成了函數表達式才能夠被執行,至於爲何不寫函數名是由於當即函數執行完後函數名被銷燬,這兩個特色你可能仍是不明白,不要着急,看完了當即函數的2,3兩點再回來看第一條就明白了。

1.當即執行函數的深刻理解

  1. 只有函數表達式才能被當即執行符號執行
<script>
        function test(a, b, c) {
            var a = 123;
        }(1, 2, 3)

    </script>
複製代碼

最後控制檯會報錯,並且是低級的語法解析錯誤。截圖以下。由於只有表達式纔可以被執行符號()執行。而它只是函數聲明因此不能被執行。

如:123爲數字表達式,1233+234也叫表達式,不是說只有等於號才叫表達式。

1.1關於=的正確理解

var test = function test() {}應該拆分紅var test 和 = function test() {}兩部分來理解,前面的是變量聲明,後面的因爲帶有等號因此是函數表達式(變量賦值)。

還有一種函數表達式若是被執行的時候是不會報錯的,如:

<script>
        var test = function test() {
            var a = 123;
            console.log(a);
        }()

    </script>
複製代碼

與此相似,正負號感嘆號等能夠將函數聲明變成函數表達式,乘號和除號不能夠。

注意 3.可是出現了一個有趣的現象,test函數被執行完以後再找不到test函數了,至關於當即執行函數。

test值爲undefined
4.一個神奇的例子(阿里巴巴考試題)

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

不會報錯,也不會被執行。按道理是要報錯的,可是語法解析將(1, 2,3);當作了函數表達式。

知識點4閉包

內部的函數被保存到外部造成閉包。

1.什麼是閉包

好比下面的例子裏面,a函數裏面嵌套了b函數,a函數定義和執行以前生成GO和AO,b函數在定義的時候拿到了a的執行期上下文,而且返回了b函數,那麼它的執行期上下文也一同被帶回到函數外部。a函數執行完後它的執行期上下文被銷燬。外部拿到了被銷燬函數a的執行期上下文。

function a() {
var num = 100;
    function b() {
        num ++;
        console.log(num);
    }
    return b;
}

var demo = a();
demo();   //101
demo();   //102
複製代碼

第一次執行demo輸出101;b函數被返回,第一次demo執行===b(),在b函數執行前會生成b的AO放在做用域鏈的第0位,裏面沒有num,因此王做用域鏈的第一位也就是a的AO裏面找,剛好找到了num=100,num++使aAO{num:101},因此第一次輸出num的值是101。而後銷燬b的AO

第二次執行demo函數輸出102,第二次執行demo也就是執行b函數,b定義的時候拿到的是a的執行期上下文,拿到的是a的勞動成果。b執行以前再次生成b的AO,沒有找到mum變量,因而就去a的AO裏面尋找,此時aAO{num:101},num ++後,aAO{num:102},而後b執行完後銷燬AO。

2.閉包的做用

記得剛開始學習閉包,學完了前男友去接個人時候問我學了什麼,我說閉包。而後他問我閉包的做用我半天答不上來哈哈哈哈,不少初學者剛剛學完以後都是模棱兩可。如今就將閉包的做用進行一次概括總結。

1.實現共有變量

咱們來看一個累加器的例子

function add(){
    var count = 0;
    function demo(){
        count ++;
        console.log(count);
    }
    return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
複製代碼

解析 在這個例子裏面,add函數裏面的demo函數被返回到外部,也就是add函數執行完被銷燬的時候,count變量的值卻被帶到了外部,而後執行一次counter後count的值就加1,實現了累加器的功能。counter調一次就加一次。

2.能夠作緩存

閉包能夠當一個隱式存儲結構,咱們來看一個吃香蕉的例子

function eater() {
    var food = "";
    var obj{
        eat : function() {
            console.log("i am eating" + food);
            food = "";
        }
        push : function() {
            food = myFood;
        }
        return obj;
    }
}

var eater1 = eater();
eater1.push("香蕉");
eater1.eat()// i am eating 香蕉
複製代碼

解析 這裏面返回來了一個obj對象,該對象裏面有兩個函數也一塊兒被返回保存到外部,food這個變量能夠push函數使用,也能夠eat函數使用。兩個函數均可以對它的值進行操做,因此說food至關於一個隱式的存儲體。

3.能夠實現封裝,屬性私有化

4.模塊化開發,防止局部污染變量

5.內存泄露

構造函數原理

調用new的時候 1.在函數體最前面隱式加上this = {} 2.執行this.xxx = xxx 3.隱式返回this

function Person(name, height) {
    var that = {};
    that.name = name;
    that.height = height;
    return that;
}

var person = Person('eee',189);
var person = Person('xx', 120)
複製代碼

包裝類

原始值能夠操做屬性,隱式三段式

//包裝類
var num = 4;
num.len = 3;
//new Number(4).len = 3;      delete
//
//new Number(4).len 
console.log(num.len);
複製代碼

原始值沒有屬性和方法的,至於爲何可以調用是通過了一個包裝類的過程。可是數組有length屬性,數組length屬性能夠被修改。

文章的結束

這篇比較長,並且不是同一個時間段寫的,不夠好的地方請各位看官指出,以爲個人分享有用的話,歡迎你們關注點贊鴨,後面我將會對js中磨人的this指向作一個全面的總結~~~

最後祝祖國生日快樂,永遠和平,繁榮昌盛鴨~~~

相關文章
相關標籤/搜索