九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JavaScript

鏈接:https://www.cnblogs.com/caiyy/p/10509659.html

前言

該文章是為大家整理一個(gè)關(guān)于js的知識(shí)網(wǎng)絡(luò),重點(diǎn)是知識(shí)的羅列及之間的聯(lián)系,所以實(shí)例可能會(huì)有所不足,導(dǎo)致可能沒有對(duì)應(yīng)知識(shí)的人看不懂,希望大家能夠結(jié)合其他資料來學(xué)習(xí)這篇文章,并整理出自己的知識(shí)體系。

ok,我們開始。

JavaScript引擎

JavaScript是解釋型語言,這就是說它無需編譯,直接由JavaScript引擎直接執(zhí)行。

既然說到了解釋型語言,那么我們就來分別以下解釋型語言和編譯型語言的差別:

  • 編譯型語言:程序在執(zhí)行之前需要一個(gè)專門的編譯過程,把程序編譯成為機(jī)器語言的文件(即exe文件),運(yùn)行時(shí)不需要重新編譯,直接用編譯后的文件(exe文件)就行了。
    • 優(yōu)點(diǎn):執(zhí)行效率高
    • 缺點(diǎn):跨平臺(tái)性差
  • 解釋型語言:程序不需要編譯,程序在運(yùn)行的過程中才用解釋器編譯成機(jī)器語言,邊編譯邊執(zhí)行(沒有exe文件)。
    • 優(yōu)點(diǎn):跨平臺(tái)性好
    • 缺點(diǎn):執(zhí)行效率低

其中程序無需編譯,不是說真的不需要編譯了,直接執(zhí)行腳本字符串。而是說不需要在運(yùn)行之前先編譯程序成為exe文件,而是在運(yùn)行的過程中邊運(yùn)行邊執(zhí)行。

JavaScript解析執(zhí)行過程

ok,我們回到JavaScript的解析執(zhí)行過程。

在整體上,JavaScript的解析執(zhí)行過程分為兩個(gè)步驟:

  1. 編譯
  2. 運(yùn)行

其中,編譯是在解釋器中進(jìn)行,將代碼編譯成可執(zhí)行碼。運(yùn)行是在JavaScript引擎中進(jìn)行,執(zhí)行可執(zhí)行碼。

過程如下:

編譯過程

編譯過程不必多說,我們只要清楚這個(gè)過程會(huì)將字符串代碼編譯為可執(zhí)行碼。

執(zhí)行過程

重點(diǎn)是運(yùn)行過程,運(yùn)行又由兩個(gè)過程組成

  1. 預(yù)解析
  2. 執(zhí)行

預(yù)解析

預(yù)解析的工作是

  1. 收集變量
  2. 分號(hào)補(bǔ)全
變量收集

重點(diǎn)注意收集變量這一功能,又名為變量提升,收集的變量有以下三種:

  1. var聲明的變量,初始值為undefined
  2. arguments參數(shù),值為傳入的實(shí)參
  3. function聲明定義

若是變量名有重復(fù)的話,按照優(yōu)先級(jí)來確定:

function聲明定義>函數(shù)參數(shù)>var聲明的變量

tips:

  1. let和const聲明的變量不會(huì)在預(yù)解析階段變量提升,只有在執(zhí)行階段執(zhí)行到該行時(shí)才會(huì)聲明該變量
  2. 當(dāng)我們給一個(gè)未聲明的變量賦值時(shí),JavaScript引擎會(huì)認(rèn)為我們是要聲明一個(gè)全局變量。但如果我們?cè)L問一個(gè)為聲明的全局變量,會(huì)報(bào)錯(cuò)
  3. var a = function(){},變量提升時(shí),a是值為undefined的變量而不是函數(shù)定義

分號(hào)補(bǔ)全

JS執(zhí)行是需要分號(hào)的,但為什么以下語句卻可以正常運(yùn)行呢?

    console.log('a')    console.log('b')

正是因?yàn)轭A(yù)解析階段會(huì)進(jìn)行分號(hào)補(bǔ)全操作。

列舉幾條自動(dòng)加分號(hào)的規(guī)則:

  • 當(dāng)有換行符(包括含有換行符的多行注釋),并且下一個(gè)token沒法跟前面的語法匹配時(shí),會(huì)自動(dòng)補(bǔ)分號(hào)。
  • 當(dāng)有}時(shí),如果缺少分號(hào),會(huì)補(bǔ)分號(hào)。
  • 程序源代碼結(jié)束時(shí),如果缺少分號(hào),會(huì)補(bǔ)分號(hào)。

不過若是以下的情況,必須得加上';',否則的話,會(huì)出現(xiàn)報(bào)錯(cuò)。

  • 如果一條語句以"(","{","/"," ","-"開始,當(dāng)前一條語句沒有用;結(jié)尾的話,就會(huì)與前一條語句合在一起解釋

還有,其實(shí)所有代碼都可以寫在一行中。只要有';'來分隔開每一句就ok。并且,if及for及while的函數(shù)體也可以寫在同一行中。

只要做好分隔工作,那么就都可以寫在同一行。

變量類型及內(nèi)存分配

JavaScript有6種數(shù)據(jù)類型(暫且不論symbol):Number,Boolean,String,Null,Undefined,Object

其中,分為兩大類別

  1. 基本數(shù)據(jù)類型:Number,Boolean,String,Null,Undefined。存儲(chǔ)于棧內(nèi)存中
  2. 引用數(shù)據(jù)類型:Object(Object數(shù)據(jù)類型由Function,Object,Array組成)。存儲(chǔ)于堆內(nèi)存中

tips:

  1. 棧內(nèi)存的數(shù)據(jù)結(jié)構(gòu)為棧,堆內(nèi)存的數(shù)據(jù)結(jié)構(gòu)為樹
  2. null為空對(duì)象指針,undefined為未初始化

關(guān)于不同類型的數(shù)據(jù)是如何存儲(chǔ)在內(nèi)存的,參考下圖:

需要特別注意的是,如下:

    var a = {name:'Bob'}

變量a存儲(chǔ)的值不是該對(duì)象,而是該對(duì)象在堆內(nèi)存中的地址。

再看下面兩道題:

    // demo01.js    var a = 20;    var b = a;    b = 30;    // 這時(shí)a的值是多少?
    // demo02.js    var m = { a: 10, b: 20 }    var n = m;    n.a = 15;    // 這時(shí)m.a的值是多少

在變量對(duì)象中的數(shù)據(jù)發(fā)生復(fù)制行為時(shí),系統(tǒng)會(huì)自動(dòng)為新的變量分配一個(gè)新值。var b = a執(zhí)行之后,a與b雖然值都等于20,但是他們其實(shí)已經(jīng)是相互獨(dú)立互不影響的值了。具體如圖。所以我們修改了b的值以后,a的值并不會(huì)發(fā)生變化。

在demo02中,我們通過var n = m執(zhí)行一次復(fù)制引用類型的操作。引用類型的復(fù)制同樣也會(huì)為新的變量自動(dòng)分配一個(gè)新的值保存在變量對(duì)象中,但不同的是,這個(gè)新的值,僅僅只是引用類型的一個(gè)地址指針。當(dāng)?shù)刂分羔樝嗤瑫r(shí),盡管他們相互獨(dú)立,但是在變量對(duì)象中訪問到的具體對(duì)象實(shí)際上是同一個(gè)。如圖所示。

因此當(dāng)我改變n時(shí),m也發(fā)生了變化。這就是引用類型的特性。

內(nèi)存空間管理及垃圾回收機(jī)制

內(nèi)存是有限的,所以分配的內(nèi)存必須得在適當(dāng)?shù)臅r(shí)機(jī)回收以供后繼使用。

內(nèi)存的生命周期為:

  1. 為變量分配所需要的內(nèi)存空間
  2. 使用分配到的內(nèi)存
  3. 不需要時(shí)將其釋放回收

這第三步對(duì)應(yīng)的就是垃圾回收。

那么JavaScript引擎是如何判斷該內(nèi)存需不需要釋放呢——標(biāo)記清楚機(jī)制。

垃圾回收器每隔一段時(shí)間都會(huì)檢查一次內(nèi)存,找到其中失去引用的變量,并釋放掉。

其中失去引用一般有兩種原因

  1. 函數(shù)執(zhí)行完,局部變量沒有存在的必要
  2. 沒有變量指向堆內(nèi)存的對(duì)象

tips:

  1. 減少使用全局變量,因?yàn)槿肿兞繉?duì)應(yīng)著主函數(shù),除非關(guān)閉該頁面,否則主函數(shù)一直不會(huì)彈出調(diào)用棧,使得全局變量在關(guān)閉頁面前一直不會(huì)被釋放

深拷貝和淺拷貝

淺拷貝開辟一個(gè)新的內(nèi)存空間,僅拷貝第一層對(duì)象內(nèi)容,深拷貝也開辟一個(gè)新的內(nèi)存空間,拷貝所有層對(duì)象堆內(nèi)容。

    var arr1 = [2,4]    let arr2 = arr1    console.log(arr1==arr2)//true

這就是最常見的,但還不是淺拷貝,那如果我們這樣呢?

    var arr1 = [2,4]    var arr2 = []    for(let i in arr1){//該復(fù)制方式既適用于復(fù)制數(shù)組又適用于復(fù)制對(duì)象        arr2[i]=arr1[i]    }    console.log(arr1==arr2)//false

這樣輸出的是false,也就是說arr1和arr2指向的地址不同。那么這樣就是深拷貝了嗎?

不是的。如果arr1的成員中有個(gè)對(duì)象呢?那么對(duì)該對(duì)象的復(fù)制就是淺拷貝。

那么究竟如何才能做到淺拷貝呢?使用遞歸,每一次遞歸進(jìn)行一次如上的拷貝,直到當(dāng)前層遞歸數(shù)據(jù)為非對(duì)象。

    var arr1 = [2,4,{name:'bob'},[323,4342]]    function deepCopy(val){        var arrSec        val instanceof Array ?  arrSec =[] :  arrSec ={}        for(let i in val){                        else if(val[i] instanceof Object){                arrSec[i] = deepCopy(val[i])            }            else{                arrSec[i] = val[i]            }        }        return arrSec    }    var arr2 = deepCopy(arr1)

無需底層實(shí)現(xiàn)的淺拷貝與深拷貝:

淺拷貝:(以下方法僅適用于數(shù)組的淺拷貝)

  1. var arr2 = [...arr1]
  2. var arr2 = arr1.slice()
  3. var arr2 = arr1.concat([])//不常用

深拷貝:(這個(gè)方法既適用于對(duì)象又適用于數(shù)組)

  1. var obj2/arr2 = JSON.parse(JSON.stringify(obj1/arr1))

變量類型比較

使用typeof來檢測(cè)基本類型,用instanceof來檢測(cè)對(duì)象還是數(shù)組

數(shù)據(jù)類型有:

number,string,boolean,null,undefined,object,function,array

typeof

typeof一般只能返回如下結(jié)果:

number,string,boolean,object(null,object,array),function,undefined

tips:

  1. typeof未定義的遠(yuǎn)算數(shù),會(huì)返回undefined

instanceof

由于引用類型的數(shù)據(jù)用typeof返回的都是object(除function),所以我們用instanceof來判斷究竟是什么引用類型(這個(gè)說法不是很嚴(yán)謹(jǐn),大家可以不要記憶這個(gè)概念)。

instanceof的使用一般是左值為對(duì)象,右值為構(gòu)造函數(shù)。

判斷方法如下:

沿著左值對(duì)象的__proto__這條線走,并且沿著右值構(gòu)造函數(shù)的prototype這條線走,只要兩者能夠交叉,即同一個(gè)對(duì)象,那么就返回true。如果__proto__這條線已經(jīng)走到頭了,還未交叉,則返回false。

所以說,與其說instanceof判斷的是什么引用類型,倒不如說是判斷是否有繼承關(guān)系。

執(zhí)行上下文

大家應(yīng)該都有接觸過函數(shù)調(diào)用棧吧,執(zhí)行上下文就是每次壓入棧的內(nèi)容。

執(zhí)行上下文可以理解為當(dāng)前代碼的執(zhí)行環(huán)境,它會(huì)形成一個(gè)作用域。

JavaScript的執(zhí)行環(huán)境大致可以分為三種:

  1. 全局執(zhí)行環(huán)境:JavaScript代碼運(yùn)行起來會(huì)首先進(jìn)入該環(huán)境
  2. 函數(shù)執(zhí)行環(huán)境:當(dāng)函數(shù)被調(diào)用時(shí),會(huì)進(jìn)入當(dāng)前函數(shù)的環(huán)境
  3. eval(不建議使用,忽略)

所以,JavaScript只有全局作用域及函數(shù)作用域。

所以,我們可以這樣理解——當(dāng)開始執(zhí)行JavaScript代碼時(shí),會(huì)創(chuàng)建一個(gè)全局上下文。每當(dāng)執(zhí)行一個(gè)函數(shù),就會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文。

JavaScript引擎會(huì)以棧的方式處理它們,這個(gè)棧我們稱為函數(shù)調(diào)用棧。棧底永遠(yuǎn)是全局上下文,棧頂就是當(dāng)前正在執(zhí)行的執(zhí)行上下文。

所以,統(tǒng)一一下——當(dāng)開始執(zhí)行JavaScript代碼時(shí),創(chuàng)建一個(gè)全局上下文,壓入函數(shù)調(diào)用棧。每當(dāng)執(zhí)行一個(gè)函數(shù),就會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文,壓入函數(shù)調(diào)用棧。當(dāng)函數(shù)執(zhí)行完,該執(zhí)行上下文彈出棧。直到關(guān)閉該頁面,才會(huì)彈出全局上下文。

tips:

  1. 在此,我可以把JavaScript代碼解析執(zhí)行的過程細(xì)化一下
    • 當(dāng)開始執(zhí)行JavaScript代碼時(shí),會(huì)編譯全局作用域的代碼,然后預(yù)解析其可執(zhí)行碼(變量提升,分號(hào)補(bǔ)全),然后執(zhí)行。當(dāng)執(zhí)行完后,再次編譯函數(shù)作用域的代碼,然后預(yù)解析其可執(zhí)行碼(變量提升,分號(hào)補(bǔ)全),然后執(zhí)行。以此類推,直至執(zhí)行完JavaScript代碼。
    • 這才是JavaScript代碼邊編譯邊運(yùn)行的具體過程,而不是編譯一行,執(zhí)行一行

執(zhí)行上下文的組成

執(zhí)行上下文的生命周期可以分為兩個(gè)階段:

  1. 創(chuàng)建階段
    • 在這個(gè)階段中,執(zhí)行上下文會(huì)分別創(chuàng)建變量對(duì)象,建立作用域鏈,以及確定this的指向。
  2. 代碼執(zhí)行階段
    • 創(chuàng)建完成之后,就會(huì)開始執(zhí)行代碼,這個(gè)時(shí)候,會(huì)完成變量賦值,函數(shù)引用,以及執(zhí)行其他代碼。

執(zhí)行上下文由三部分組成:

  1. 變量對(duì)象
  2. 作用域鏈
  3. this

變量對(duì)象(VO variable Object)

該對(duì)象存儲(chǔ)的就是變量提升的arguments參數(shù),var聲明的變量,函數(shù)聲明。

在未進(jìn)入執(zhí)行階段時(shí),變量對(duì)象(VO variable Object)中的屬性都不能訪問。但在進(jìn)入執(zhí)行階段時(shí),變量對(duì)象轉(zhuǎn)換為了活動(dòng)對(duì)象(AO active Object),里面的屬性都能被訪問。

VO和AO其實(shí)都是一個(gè)對(duì)象,只是處于執(zhí)行上下文的不同生命周期。只有在函數(shù)調(diào)用棧的頂部執(zhí)行上下文的變量對(duì)象才會(huì)變成變量對(duì)象。

作用域鏈

由該環(huán)境和所有父環(huán)境的變量對(duì)象組成的鏈?zhǔn)浇Y(jié)構(gòu),保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問權(quán)限的變量和函數(shù)的有序訪問

我們通過作用域鏈,遍歷自身的變量對(duì)象到全局對(duì)象,直到找到對(duì)應(yīng)的變量。

理解作用域鏈非常關(guān)鍵,這是理解閉包的基礎(chǔ)。

執(zhí)行上下文和作用域是兩個(gè)完全不同的概念。作用域是在編譯階段就確定下來的,執(zhí)行上下文是在執(zhí)行階段才能夠創(chuàng)建的。

不過,切記,當(dāng)前作用域和上層作用域不是包含關(guān)系。

閉包

關(guān)于垃圾回收機(jī)制,有一個(gè)重要的行為,那就是,當(dāng)一個(gè)值,在內(nèi)存中失去引用時(shí),垃圾回收機(jī)制會(huì)根據(jù)特殊的算法找到它,并將其回收,釋放內(nèi)存。

而我們知道,函數(shù)的執(zhí)行上下文,在執(zhí)行完畢之后,生命周期結(jié)束,那么該函數(shù)的執(zhí)行上下文就會(huì)失去引用。其占用的內(nèi)存空間很快就會(huì)被垃圾回收器釋放??墒情]包的存在,會(huì)阻止這一過程。

實(shí)現(xiàn)閉包的操作:

  1. 函數(shù)A嵌套在函數(shù)B以內(nèi)
  2. 函數(shù)B返回函數(shù)A

閉包的核心就是——通過在外部函數(shù)(B)的外部(C)保存內(nèi)部函數(shù)(A)的引用,當(dāng)執(zhí)行該引用(A)時(shí),由于創(chuàng)建的執(zhí)行上下文的作用域鏈中包含有外部函數(shù)(B)的引用,從而使外部函數(shù)(B)的執(zhí)行上下文不會(huì)被垃圾回收。

  • 函數(shù)C中聲明函B,函數(shù)B中聲明函數(shù)C

這樣就能保存之前執(zhí)行函數(shù)B的操作結(jié)果。這樣的話,就可以在其他的執(zhí)行上下文中,操作到函數(shù)B的操作結(jié)果。

要切記哦:雖然函數(shù)A被保存在了函數(shù)C中,但函數(shù)A的作用域鏈并沒有變化,千萬不要把作用域鏈和函數(shù)調(diào)用?;煸谝黄鹆?。在閉包中,能訪問到的仍然是作用域鏈上能查詢到的數(shù)據(jù)。

閉包返回的作用域鏈中,中間層及之前層的都是不變的內(nèi)存區(qū)域,只有最高層的變量對(duì)象是每次調(diào)用函數(shù)的時(shí)候新創(chuàng)建的變量對(duì)象。

閉包的好處
  1. 讀取函數(shù)內(nèi)部的變量
  2. 這些變量的值保存在內(nèi)存里,不會(huì)在外層函數(shù)調(diào)用后自動(dòng)刪除
閉包的壞處
  1. 如若閉包為全局變量,會(huì)造成內(nèi)存泄漏

this

關(guān)于this的指向一直是大家比較頭疼的地方,似乎很難找到一個(gè)確切的標(biāo)準(zhǔn)。但this的指向還是有標(biāo)準(zhǔn)的,且往下看。

this的執(zhí)行是在調(diào)用函數(shù),即執(zhí)行上下文創(chuàng)建時(shí)才能確定的,判斷標(biāo)準(zhǔn)如下:

  1. new綁定:this指向新創(chuàng)建的對(duì)象
  2. call,apply綁定:this指向指定的對(duì)象
  3. 顯式綁定:this指向調(diào)用該函數(shù)的對(duì)象
  4. 默認(rèn)綁定:非嚴(yán)格模式下,this指向全局對(duì)象,嚴(yán)格模式,this為undefined
    • 全局代碼中的this永遠(yuǎn)指向全局變量
new

new到底做了什么呢?

  1. 創(chuàng)建一個(gè)空對(duì)象
  2. 將構(gòu)造函數(shù)的this指向該對(duì)象
  3. 執(zhí)行構(gòu)造函數(shù),從而能夠使用構(gòu)造函數(shù)中的this為該新對(duì)象添加屬性
  4. 將該對(duì)象的引用返回
call,apply

這兩者的作用都是修改函數(shù)中的this指向,功能一直,只是參數(shù)的寫法有稍許不同。

call傳參需要一個(gè)一個(gè)地傳,而apply傳參是傳一個(gè)數(shù)組。

而bind和call,apply的區(qū)別在于:

call,apply會(huì)直接執(zhí)行。bind是在函數(shù)調(diào)用之前,改變this的指向,它會(huì)返回一個(gè)函數(shù)。

箭頭函數(shù)下的this

箭頭函數(shù)是ES6的新語法,形式如下:

    (參數(shù)部分)=>{        函數(shù)體部分    }

其中,如果參數(shù)只有一個(gè),則可以省略括號(hào)。如果沒有參數(shù)或多個(gè)參數(shù),括號(hào)不能省略。

箭頭函數(shù)中this指向規(guī)則與普通函數(shù)的規(guī)則不同,他的this指向規(guī)則為:

捕獲其所在(即定義的位置)上下文的this值, 作為自己的this值,

tips:

  1. 注意,在箭頭函數(shù)中普通函數(shù)的this指向規(guī)則都失效了,也就無法使用apply,call,bind來改變函數(shù)中的this指向了。
  2. 箭頭函數(shù)無法訪問參數(shù)對(duì)象arguments
  3. 在對(duì)象中的方法千萬不要用箭頭函數(shù)來定義,因?yàn)樵摲椒ㄖ械膖his不執(zhí)行該對(duì)象
    function Person() {          console.log(this)         setTimeout(() => {            // 回調(diào)里面的 `this` 變量就指向了期望的那個(gè)對(duì)象了            console.log(this)        }, 3000);    }    var p = new Person();

普通函數(shù)的this指向和setTimeout中的箭頭函數(shù)的指向都是Person對(duì)象。

面向?qū)ο笫骄幊?,繼承,原型鏈

JavaScript是一門面向過程的語言,但隨著網(wǎng)頁需求功能的復(fù)雜化,工程化,要求JavaScript應(yīng)該也有面向?qū)ο缶幊痰哪芰Α?/p>

字面量對(duì)象

我們可以通過字面量對(duì)象來創(chuàng)建一個(gè)簡單對(duì)象

    var obj = {}

當(dāng)我們想要給我們創(chuàng)建的簡單對(duì)象添加方法時(shí),可以這樣表示

// 可以這樣    var person = {};    person.name = "TOM";    person.getName = function() {        return this.name;    }    // 也可以這樣    var person = {        name: "TOM",        getName: function() {            return this.name;        }    }

訪問屬性的時(shí)候,可以用一下兩種方式

    person.name    // 或者    person['name']

當(dāng)我們想要用一個(gè)變量值來作為屬性名來訪問屬性,就用第二種方法。

工廠模式

使用上面的方式創(chuàng)建對(duì)象很簡單,但是在很多時(shí)候并不能滿足我們的需求。就以person對(duì)象為例。假如我們?cè)趯?shí)際開發(fā)中,不僅僅需要一個(gè)名字叫做TOM的person對(duì)象,同時(shí)還需要另外一個(gè)名為Jake的person對(duì)象,雖然他們有很多相似之處,但是我們不得不重復(fù)寫兩次。

    var perTom = {        name: 'TOM',        age: 20,        getName: function() {            return this.name        }    };    var perJake = {        name: 'Jake',        age: 22,        getName: function() {            return this.name        }    }

顯然,這樣是很不合理的,當(dāng)有太多的相似對(duì)象,編寫代碼會(huì)極為痛苦。

這就引出了工廠模式。

工廠模式就是你給出原料,然后返回給你產(chǎn)品。

看代碼:

var createPerson = function (name,age){    //創(chuàng)建一個(gè)中間對(duì)象    var obj = new Object()        obj.name = name    obj.age = age    obj.getName = function(){        return this.name    }}var Tom = createPerson('Tom',18)var Cherry = createPerson('Tom',40)

不要把工廠模式想的太高大上。顯然,工廠模式幫我們解決了重復(fù)編碼的麻煩,但是他還有一個(gè)問題

無法識(shí)別工廠模式返回的對(duì)象的類型。(其次還有每次返回對(duì)象都得為方法分配一個(gè)新的內(nèi)存空間,浪費(fèi)資源)

如上述代碼,Tom和Cherry指向的對(duì)象類型都是Object類型。

構(gòu)造函數(shù)

首先構(gòu)造函數(shù)就是個(gè)普通的函數(shù),其本身沒有什么特別的地方。

但構(gòu)造函數(shù)的特殊之處就在于用new創(chuàng)建一個(gè)對(duì)象,構(gòu)造函數(shù)對(duì)該對(duì)象的屬性進(jìn)行添加。

new的具體過程在上文有詳細(xì)提到,就不贅述了。

就這樣,new關(guān)鍵字 構(gòu)造函數(shù)就能夠創(chuàng)建出一個(gè)有屬性的對(duì)象,且還能夠識(shí)別對(duì)象類型。

但是又有一個(gè)問題來了:

所有用該構(gòu)造函數(shù)創(chuàng)建的對(duì)象訪問的方法實(shí)現(xiàn)是一模一樣的,但是每次new的時(shí)候都會(huì)在內(nèi)存中分配一片新的空間以保存變量的特性和方法。

顯然這是不合理的,既然訪問的是同一個(gè)方法實(shí)現(xiàn),那么為什么不能每個(gè)實(shí)例對(duì)象都訪問同一塊內(nèi)存里的方法呢?

原型對(duì)象

我們創(chuàng)建的每一個(gè)函數(shù),都有prototype屬性指向原型對(duì)象,可以選擇在原型對(duì)象里掛載屬性和方法,這樣每創(chuàng)建一個(gè)對(duì)象,都可以通過__proto__訪問到原型對(duì)象,也就不需要再為這些屬性和變量分配空間了。

由于每個(gè)函數(shù)都可以是構(gòu)造函數(shù),每個(gè)對(duì)象都可以是原型對(duì)象,因此如果在理解原型之初就想的太多太復(fù)雜的話,反而會(huì)阻礙你的理解,這里我們要學(xué)會(huì)先簡化它們。就單純的剖析這三者的關(guān)系。

// 聲明構(gòu)造函數(shù)    function Person(name, age) {        this.name = name;        this.age = age;    }    // 通過prototye屬性,將方法掛載到原型對(duì)象上    Person.prototype.getName = function() {        return this.name;    }    var p1 = new Person('tim', 10);    var p2 = new Person('jak', 22);    console.log(p1.getName === p2.getName); // true

如圖

通過圖示我們可以看出,構(gòu)造函數(shù)的prototype與所有實(shí)例對(duì)象的__proto__都指向原型對(duì)象。而原型對(duì)象的constructor指向構(gòu)造函數(shù)。

可以這樣理解:

構(gòu)造函數(shù)中this添加的屬性和方法是私有屬性和方法(雖然這個(gè)私有屬性和方法能夠被外界直接取到),原型對(duì)象中的屬性和方法是共有屬性和方法。

當(dāng)我們?cè)L問實(shí)例對(duì)象中的屬性或者方法時(shí),會(huì)優(yōu)先訪問實(shí)例對(duì)象自身的屬性和方法,即私有屬性和方法。如若找不到,則去原型對(duì)象中尋找。

繼承與原型鏈

原型鏈如圖:

每一個(gè)對(duì)象既可以作為原型對(duì)象,又可以作為實(shí)例對(duì)象,而且有可能既是實(shí)例對(duì)象又是全局對(duì)象,這樣的一個(gè)對(duì)象正是原型鏈中的一個(gè)節(jié)點(diǎn)。

繼承分為兩個(gè)步驟:

  1. 在子構(gòu)造函數(shù)中復(fù)制父構(gòu)造函數(shù)添加的屬性和方法。即讓父構(gòu)造函數(shù)中的操作在子構(gòu)造函數(shù)中重現(xiàn)一遍
  2. 子級(jí)的原型對(duì)象指向父構(gòu)造函數(shù)的實(shí)例對(duì)象、

具體代碼如下:

var Person = function(name){    this.name = name    }Person.prototype.getName = function(){    return this.name}var cPerson = function(name,age){    Person.call(this,name)    this.age = age}cPerson.prototype = new Person('名稱')//在子級(jí)的原型里添加更多的方法cPerson.prototype.moreFunc = function(){    console.log('更多的方法')}var p = new cPerson('Tom',18)

該繼承方案有一個(gè)問題,就是

子級(jí)的原型對(duì)象是父構(gòu)造函數(shù)的實(shí)例對(duì)象,這樣的話,我們就調(diào)用了兩次父級(jí)的構(gòu)造函數(shù)(子級(jí)對(duì)象中的將子級(jí)原型對(duì)象中的給屏蔽了)。

子級(jí)原型對(duì)象為父級(jí)實(shí)例對(duì)象,實(shí)際目的僅是父級(jí)實(shí)例對(duì)象中的__proto__,從而形成原型鏈來尋找屬性和方法。

重復(fù)調(diào)用兩次父級(jí)的構(gòu)造函數(shù)是沒有意義的,所以我們改進(jìn)一下代碼。

var Person = function(name){    this.name = name    }Person.prototype.getName = function(){    return this.name}var cPerson = function(name,age){    Person.call(this,name)    this.age = age}(function (){    var Super = function (){}        Super.prototype = Person.prototype        cPerson.prototype = new Super()        //在子級(jí)的原型里添加更多的方法    cPerson.prototype.moreFunc = function(){        console.log('更多的方法')    }})()var p = new cPerson('Tom',18)

這就是繼承的最終解決方案了。

ES6新特性

let和const

前面說過,js只有全局作用域及函數(shù)作用域,沒有塊級(jí)作用域。

但是let和const會(huì)引入塊級(jí)作用域。

這兩者的特點(diǎn)為:

  1. 不會(huì)在預(yù)解析階段變量提升,只有當(dāng)執(zhí)行到該行時(shí)才會(huì)創(chuàng)建let或const變量
  2. 會(huì)引入塊級(jí)作用域
  3. 不可重復(fù)命名,否則報(bào)錯(cuò)

我們常常使用let來聲明一個(gè)值會(huì)被改變的變量,而使用const來聲明一個(gè)值不會(huì)被改變的變量,也可以稱之為常量。

模板字符串

以一個(gè)例子比對(duì)一下大家就知道了

    // es6    const a = 20;    const b = 30;    const string = `${a} $=${a b}`;    // es5    var a = 20;    var b = 30;    var string = a   " "   b   "="   (a   b);

使用``將整個(gè)字符串包起來,在其中使用${}包裹一個(gè)變量或表達(dá)式

解析結(jié)構(gòu)

同樣,以一個(gè)例子來解釋

    // 首先有這么一個(gè)對(duì)象    const props = {        className: 'tiger-button',        loading: false,        clicked: true,        disabled: 'disabled'    }    // es5    var loading = props.loading;    var clicked = props.clicked;    // es6    const { loading, clicked } = props;    // 給一個(gè)默認(rèn)值,當(dāng)props對(duì)象中找不到loading時(shí),loading就等于該默認(rèn)值    const { loading = false, clicked } = props;

是不是很簡單,就是將訪問屬性與變量命名在寫法上合并為一步。

另外,數(shù)組也有屬于自己的解析結(jié)構(gòu)

// es6    const arr = [1, 2, 3];    const [a, b, c] = arr;    // es5    var arr = [1, 2, 3];    var a = arr[0];    var b = arr[1];    var c = arr[2];

數(shù)組以序列號(hào)一一對(duì)應(yīng),這是一個(gè)有序的對(duì)應(yīng)關(guān)系。
而對(duì)象根據(jù)屬性名一一對(duì)應(yīng),這是一個(gè)無序的對(duì)應(yīng)關(guān)系。

展開字符串

在ES6中用...來表示展開運(yùn)算符,它可以將數(shù)組或者對(duì)象進(jìn)行展開。

    const arr1 = [1, 2, 3];    const arr2 = [...arr1, 10, 20, 30];    // 這樣,arr2 就變成了[1, 2, 3, 10, 20, 30];    const obj1 = {      a: 1,      b: 2,      c: 3    }    const obj2 = {      ...obj1,      d: 4,      e: 5,      f: 6    }    // 結(jié)果類似于 const obj2 = Object.assign({}, obj1, {d: 4})

展開字符串還可以運(yùn)用在參數(shù)中

對(duì)象字面量和class

對(duì)象字面量

ES6對(duì)對(duì)象字面量做了簡化語法的處理。

  1. 當(dāng)對(duì)象字面量的屬性和值的變量同名時(shí),可以省略值的聲明
  2. 除了屬性以外,對(duì)象字面量的方法也可以簡寫
  3. 在對(duì)象字面量中可以用[]作為屬性名,表示屬性名也可以用變量
var name = 'Bob'var age = 20var arc = 'sex'var person = {    name,    age,    [arc]:'male'    getName(){        return this.name    }}

class聲明類

class是ES6的新功能。JavaScript創(chuàng)建類的方式是構(gòu)造函數(shù),這對(duì)于普通的面向?qū)ο蟪绦騿T來說太過別樹一幟了。

所以,ES6模擬普通面向?qū)ο蟮念?對(duì)象編寫代碼方式,創(chuàng)造了class。

class實(shí)際上就是一個(gè)var聲明變量,其指向其中的constructor構(gòu)造函數(shù),只是在原先的構(gòu)造函數(shù)創(chuàng)建類的模式做了形式上的變形,使更符合JAVA的類-對(duì)象模式。

知識(shí)點(diǎn):

  1. 其中,constructor函數(shù)就是原先的構(gòu)造函數(shù),當(dāng)new一個(gè)對(duì)象的時(shí)候就是默認(rèn)調(diào)用這個(gè)函數(shù),因?yàn)轭惷褪侵赶虻腸onstructor函數(shù)。

  2. 類中聲明的方法,都是定義在該構(gòu)造函數(shù)的prototype對(duì)象中。相當(dāng)于說,只要是類中的方法,都是使用構(gòu)造函數(shù).prototype.方法名=...來聲明。
    • 所以我們可以在聲明了類之后,通過類名.prototype.方法名=...來在類外動(dòng)態(tài)為類定義方法。
    • 同時(shí),我們還可以用Object.assign(類名.prototype,字面量對(duì)象-就是一個(gè)對(duì)象,用{}來聲明)。該字面量對(duì)象不會(huì)覆蓋掉類中的其他方法,所做的工作是添加方法
  3. 如果你沒有顯式定義一個(gè)constructor,那么會(huì)隱式生成一個(gè)constructor方法,當(dāng)new的時(shí)候調(diào)用,該方法返回this。由于new返回對(duì)象由構(gòu)造函數(shù)及new執(zhí)行,所以如果你想要返回的實(shí)例對(duì)象不是該類的實(shí)例,可以在里面return new A()
  4. class中只允許有方法,不允許聲明變量,跟JAVA不一樣
  5. 只有在類中constructor中用this指定的屬性 方法才是實(shí)例屬性 實(shí)例方法,在類中定義的方法是原型方法。然后在類外用那兩種方法聲明的屬性 方法都是原型屬性 原型方法
  6. 類的所有實(shí)例都共享一個(gè)原型對(duì)象,而同時(shí)類的所有實(shí)例又可以通過__proto__來修改原型對(duì)象內(nèi)容,但千萬別這樣,因?yàn)檫@樣會(huì)影響到別的該類的實(shí)例對(duì)象,不符合業(yè)務(wù)邏輯
  7. class類不會(huì)變量提升

注意:

  1. 在類中聲明方法時(shí),不要加上function,相當(dāng)于強(qiáng)制使用對(duì)象字面量吧
  2. 在類中聲明函數(shù),函數(shù)之間不要用,隔離
class類繼承

使用extends關(guān)鍵字。

然后在子的constructor函數(shù)中使用super()來調(diào)用父的構(gòu)造函數(shù),從而復(fù)制父的實(shí)例屬性。繼承過后,通過原型鏈,可以訪問到父級(jí)的所有原型鏈(即可訪問到以往所有被用prototype聲明的對(duì)象)內(nèi)容。

super在子類定義中有兩種使用方法:

  1. 當(dāng)做構(gòu)造函數(shù)使用。super()等同于A.prototype.constructor.call(this,props)
  2. 當(dāng)做對(duì)象用,則是父的原型對(duì)象 類。所以父的實(shí)例屬性是無法訪問到的

注意:

  1. 使用super的時(shí)候,必須顯式指定是作為函數(shù)(super()),還是作為對(duì)象(super.某)使用,否則會(huì)報(bào)錯(cuò)(如console.log(super))
  2. super作為對(duì)象使用時(shí),調(diào)用父級(jí)的方法,super會(huì)綁定子類的this
static

靜態(tài)方法的意義是:在該類所有實(shí)例之上,只屬于類本身的方法,不是具體某個(gè)實(shí)例的方法,也不是所有實(shí)例都有的方法。

切記:靜態(tài)方法的意義不是省空間(這是原型方法的意義,而且實(shí)例根據(jù)原型鏈訪問不到靜態(tài)方法),而是一個(gè)能夠統(tǒng)領(lǐng)全局(中樞權(quán))的方法。

由于靜態(tài)方法中的this指向類本身,所以我們無法使用this來訪問到實(shí)例屬性,方法。但是,我們可以通過創(chuàng)建一個(gè)實(shí)例對(duì)象,從而訪問到實(shí)例屬性,方法。

class Foo{    prop:2//錯(cuò)    static prop:2//錯(cuò)    prop=2//錯(cuò)    var prop = 2//錯(cuò)}

關(guān)于私有,靜態(tài),原型,實(shí)例屬性和方法,在類中g(shù)et及set聲明函數(shù)代碼如下:

var prot = 'prot'class MyClass {      constructor() {    this[bar]()    //類內(nèi)  構(gòu)造函數(shù)內(nèi)  public實(shí)例屬性,方法    this.entity = 0    this.entityFunc = ()=>{        MyClass.staticFunc2()    }    this.entityFunc2 = ()=>{        console.log('aa')    }    //類內(nèi)  構(gòu)造函數(shù)內(nèi)  私有屬性,方法    var privat = 8    var privateFunc = function(){return 6}    this.outFunc = function(){        //操作私有變量,使用私有函數(shù)        privat = 88        privateFunc()    }     console.log(this.name); // 42   }   // 類內(nèi)  get,set攔截讀寫   get prop(){    return 9   }   set prop(par){    this._prop = 8   }   //類中  構(gòu)造函數(shù)外  原型方法   [prot](){}    }//類外  靜態(tài)屬性,方法MyClass.static = 9MyClass.staticFunc = function(){    console.log('static')    MyClass.staticFunc2()    }MyClass.staticFunc2 = function(){    console.log('static2')}//類外  原型屬性,方法MyClass.prototype.prott = ()=>3MyClass.prototype.prottt = 5console.log(Object.getOwnPropertyDescriptor(MyClass,'staticFunc2'))console.log(MyClass.name)

tips:

  1. class聲明的類不能夠不適用new就直接使用,但構(gòu)造函數(shù)聲明類的方式可以不適用new,但這樣的話,該構(gòu)造函數(shù)的this就是指向全局對(duì)象,從而直接調(diào)用構(gòu)造函數(shù)時(shí),往全局對(duì)象里增添屬性,方法。
    • 我們?cè)跇?gòu)造函數(shù)中使用new.target是否為false來判斷是否使用new來調(diào)用該構(gòu)造函數(shù),從而規(guī)避剛剛的問題
  2. 靜態(tài)方法中的this指向的是類本身,而不是實(shí)例。在實(shí)例創(chuàng)建前,靜態(tài)屬性,方法已經(jīng)存在了。我們無法做到不創(chuàng)建一個(gè)對(duì)象就使用靜態(tài)方法訪問到實(shí)例屬性
  3. This會(huì)慢慢疊加在一起,靜態(tài)方法也會(huì)慢慢疊加,但原型不會(huì)疊加在一起
  4. 靜態(tài)方法不能被實(shí)例對(duì)象調(diào)用,實(shí)例方法不能被類調(diào)用
  5. 父類的靜態(tài)方法,可以被子類繼承
  6. class類中不能夠聲明變量,只能聲明方法
  7. 靜態(tài)字段得等到程序關(guān)閉了才能釋放。靜態(tài)的要比非靜態(tài)的先加載
  8. ES6聲明的原型方法不能枚舉,但用ES5的構(gòu)造函數(shù)原型方法能夠枚舉
  9. 類名.name能夠得到該類名稱
  10. 在ES6中能夠使用變量來作為屬性名,[變量]:值,當(dāng)要調(diào)用這個(gè)屬性時(shí),對(duì)象[變量]
  11. get,set配對(duì)聲明,相當(dāng)于聲明一個(gè)完整的屬性,相當(dāng)于攔截該屬性的存取行為,可用此實(shí)現(xiàn)一個(gè)私有變量
  12. 也可以在構(gòu)造函數(shù)上聲明靜態(tài)屬性和方法
  13. 原型對(duì)象是一個(gè)空的對(duì)象,當(dāng)聲明類的時(shí)候就創(chuàng)建出來的,由類來定義該對(duì)象內(nèi)容,并將其prototype指向另一個(gè)原型對(duì)象(所以原型對(duì)象是由該類創(chuàng)建的,而不是由父類創(chuàng)建的)(修改prototype其實(shí)是打斷原來的原型鏈,重建一個(gè)新的原型鏈)
對(duì)象中聲明方法與class中聲明方法的差別

class中聲明方法:

    class Box{        constructor(msg){            this.msg = msg        }        save(){            console.log(this.msg)        }    }

對(duì)象中聲明方法:

var Box = {    save:function(){        console.log('aaa')    }}

Promise

我們要保證異步的順序執(zhí)行,在以往都是通過回調(diào)函數(shù)來實(shí)現(xiàn)。

然而回調(diào)函數(shù)有兩個(gè)缺陷:

  1. 回調(diào)里面套回調(diào),會(huì)導(dǎo)致“回調(diào)地獄”
  2. 如果使用回調(diào)的話,數(shù)據(jù)請(qǐng)求與數(shù)據(jù)處理沒有分開來

這兩者都會(huì)導(dǎo)致閱讀源碼的困難。

所以,我們需要新的方案能夠有以下兩個(gè)特點(diǎn):

  1. 用扁平式代碼結(jié)構(gòu)代替嵌套式代碼結(jié)構(gòu)
  2. 數(shù)據(jù)請(qǐng)求與數(shù)據(jù)處理涇渭分明地區(qū)分開來

Promise能做到這兩點(diǎn)。

Promise對(duì)象有三種狀態(tài):

  1. pending:等待中,或者進(jìn)行中,表示沒有得到結(jié)果
  2. resolved:已經(jīng)完成,表示得到了我們想要的結(jié)果,可以繼續(xù)往下執(zhí)行
  3. rejected:也得到結(jié)果,但由于結(jié)果并非我們所愿,所以拒絕執(zhí)行

這三種狀態(tài)不受外界影響,而且狀態(tài)只能是從pending到resolved或者pending到rejected。我們通過Promise構(gòu)造函數(shù)中的第一個(gè)函數(shù)參數(shù)來處理狀態(tài)改變。

    new Promise(function(resolve, reject) {        if(true) { resolve() };        if(false) { reject() };    })

tips:

  1. 每當(dāng)new一個(gè)Promise對(duì)象時(shí)(包括return一個(gè)剛new出來的Promise對(duì)象),就會(huì)立即執(zhí)行其中的參數(shù)函數(shù)(不需要異步執(zhí)行該參數(shù)函數(shù))
  2. 上面的resolve和reject都作為一個(gè)函數(shù),作用分別是將狀態(tài)修改為resolved或rejected

then

Promise對(duì)象中的then方法,可以接受到Promise對(duì)象的狀態(tài)變化。then方法有兩個(gè)參數(shù),第一個(gè)參數(shù)接受resolved狀態(tài)的執(zhí)行,第二個(gè)參數(shù)接受rejected狀態(tài)與異常狀態(tài)的執(zhí)行。

又因?yàn)閠hen方法執(zhí)行完后會(huì)返回一個(gè)新建的Promise對(duì)象,所以可以繼續(xù)用then來接受Promise對(duì)象的狀態(tài)變化,并執(zhí)行相應(yīng)函數(shù)。這也是扁平式代碼結(jié)構(gòu)的核心。

catch

catch方法是.then(null, rejection)的別名,用于接受rejected狀態(tài)與異常狀態(tài)的執(zhí)行。

Promise對(duì)象的rejected狀態(tài)與異常狀態(tài)具有冒泡性質(zhì),一直向后傳遞,直到被捕獲為止。

then與catch的參數(shù)傳遞

    var fn = function(num) {        return new Promise(function(resolve, reject) {            if (typeof num == 'number') {                resolve(num);            } else {                reject('TypeError');            }        })    }    fn(2).then(function(num) {        console.log('first: '   num);        return num   1;    })    .then(function(num) {        console.log('second: '   num);        return num   1;    })    .then(function(num) {        console.log('third: '   num);        return num   1;    });    // 輸出結(jié)果    first: 2    second: 3    third: 4

  1. then中第一個(gè)參數(shù)函數(shù)的參數(shù)是前面Promise對(duì)象resolve()執(zhí)行中的參數(shù)值
  2. then中第二個(gè)參數(shù)函數(shù)的參數(shù)和catch的參數(shù)函數(shù)的參數(shù)是前面Promise對(duì)象reject()執(zhí)行中的參數(shù)值
  3. then方法執(zhí)行完會(huì)返回一個(gè)Promise對(duì)象,如若沒有異常,該P(yáng)romise對(duì)象會(huì)執(zhí)行resolve(),其中的參數(shù)是then方法中return的值。如果有異常,則該P(yáng)romise對(duì)象會(huì)執(zhí)行reject(),其中的參數(shù)是then方法中return的值

then與catch的微任務(wù)隊(duì)列異步機(jī)制

new一個(gè)Promise對(duì)象時(shí)是立即執(zhí)行其中的執(zhí)行函數(shù),但是then方法與catch方法中的參數(shù)函數(shù)是異步執(zhí)行的。

即運(yùn)行代碼的過程中遇到then方法和catch方法,需要掛起其對(duì)應(yīng)的參數(shù)函數(shù),直到執(zhí)行棧為空,才執(zhí)行微任務(wù)隊(duì)列中的任務(wù)。(順帶提一句,直至當(dāng)前的微任務(wù)隊(duì)列清空,下一個(gè)宏任務(wù)才能進(jìn)棧)

Promise.all()與Promise.race()

    Promise.all([Promise.resolve(4),5,new Promise((resolve,reject)=>resolve(8))]).then(val=>console.log(val))

切記,參數(shù)為一個(gè)數(shù)組,都由Promise對(duì)象組成。且傳遞給then的參數(shù)是所有Promise對(duì)象resolve的值組成的數(shù)組。如果參數(shù)中有一個(gè)Promise對(duì)象失敗的話,則將第一個(gè)失敗的reject值傳遞給catch。

    Promise.race([Promise.resolve(4),5,new Promise((resolve,reject)=>resolve(8))]).then(val=>console.log(val))

同樣的,參數(shù)是一個(gè)由Promise對(duì)象組成的數(shù)組,傳遞給then的值是第一個(gè)完成的Promise中resolve的值。

async,await

但是,Promise這個(gè)異步方案還不夠完美,我們異步的最終目標(biāo)就是以同步的書寫方式書寫異步。

酷吧!就是說我們并不關(guān)心他是不是異步,不管是同步還是異步我們都以從上到下的書寫方式來書寫代碼,這才是人類的思維方式?。?/p>

所以也就產(chǎn)生了async這個(gè)異步解決方案了。

需要注意的是,async是基于Promise的,即他不是一套獨(dú)立的解決方案,而是基于Promise的異步機(jī)制的一次進(jìn)步,代替then()的寫法。

先聲明幾個(gè)重要的知識(shí)點(diǎn):

  1. 每個(gè)async函數(shù)執(zhí)行后返回的都是Promise對(duì)象,即每當(dāng)執(zhí)行一個(gè)async函數(shù)都會(huì)創(chuàng)建一個(gè)Promise對(duì)象并返回
  2. await的意義為等待其對(duì)應(yīng)的Promise對(duì)象的異步任務(wù)執(zhí)行完,否則將接下來的異步任務(wù)掛起,跳出async函數(shù),執(zhí)行函數(shù)外的代碼。
  3. 統(tǒng)一用try{},catch(err){}來捕捉異常和reject,替代catch
  4. await關(guān)鍵字只能在async函數(shù)中使用
  5. 將async函數(shù)中的return值賦值給外部變量,可以做到異步任務(wù)之間的通信

異步執(zhí)行順序辨析

    async function async1(){        console.log('async1 start')        await async2()        console.log('async1 end')    }    async function async2(){        console.log('async2')    }    console.log('script start')    setTimeout(function(){        console.log('setTimeout')     },0)      async1();    new Promise(function(resolve){        console.log('promise1')        resolve();    }).then(function(){        console.log('promise2')    })    console.log('script end')

執(zhí)行順序是這樣的:

  1. 進(jìn)入主代碼,輸出'async start',并將setTimeout的回調(diào)函數(shù)放入宏任務(wù)隊(duì)列中
  2. 執(zhí)行async1函數(shù),調(diào)用棧壓入async1執(zhí)行上下文,輸出'async1 start'
  3. 遇到await關(guān)鍵字,其后是執(zhí)行async函數(shù)async2,輸出'async2',同時(shí)返回一個(gè)新建的Promise對(duì)象,由于沒有async2函數(shù)執(zhí)行時(shí)沒有出現(xiàn)異常,所以這個(gè)Promise對(duì)象中的執(zhí)行函數(shù)執(zhí)行resolve(),并將該P(yáng)romise對(duì)象then方法放入微任務(wù)棧中
  4. 跳出async1函數(shù)。這一步我單獨(dú)拿出來,是為了說明其特殊性。若是沒有await關(guān)鍵字的話,那么不會(huì)退出async1函數(shù),而是繼續(xù)執(zhí)行函數(shù),輸出'async1 end'。await能夠保證下一步的代碼是在上一步的所有異步任務(wù)執(zhí)行完后才執(zhí)行的。這也就能夠保證每個(gè)Promise對(duì)象之間又是同步的
  5. 遇到了new一個(gè)Promi對(duì)象,立即執(zhí)行其執(zhí)行函數(shù),輸出'promise1',并且將then函數(shù)放入微任務(wù)隊(duì)列中
  6. 輸出'script end'
  7. 接下來到了重頭戲,這個(gè)時(shí)候執(zhí)行棧為空了,我們將微任務(wù)棧頭取出放入執(zhí)行棧中,即是第3步中的then方法,執(zhí)行。沒有輸出,然后將await async2()之后的代碼作為一個(gè)微任務(wù)放入微任務(wù)堆棧中。(這點(diǎn)也要特別注意,并不是直接同步執(zhí)行下去,而是將接下來的代碼作為一個(gè)微任務(wù)放入微任務(wù)隊(duì)列)
  8. 將第5步中放入微任務(wù)隊(duì)列中then放入執(zhí)行棧執(zhí)行,輸出'promise2'
  9. 將第7步放入微任務(wù)隊(duì)列的任務(wù)放入執(zhí)行棧執(zhí)行,輸出'async1 end'
  10. 微任務(wù)隊(duì)列清空,取出宏任務(wù)隊(duì)列中的任務(wù)放入執(zhí)行棧中執(zhí)行,輸出'setTimeout'
  11. 結(jié)束

tips:

  1. 切記,如果await后面跟的是new一個(gè)Promise對(duì)象的話,Promise對(duì)象中的執(zhí)行函數(shù)一定要執(zhí)行resolve()或reject(),否則直至退出程序都不會(huì)執(zhí)行該async函數(shù)后面的代碼
??來源:https://www.icode9.com/content-1-597551.html
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
js代碼常見技巧總結(jié)
javascript前端面試題及答案整理
es6新增新特性簡要總結(jié)
JavaScript簡易教程
「學(xué)習(xí)筆記」JavaScript基礎(chǔ)
ES6中的變量聲明
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服