該文章是為大家整理一個(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引擎直接執(zhí)行。
既然說到了解釋型語言,那么我們就來分別以下解釋型語言和編譯型語言的差別:
其中程序無需編譯,不是說真的不需要編譯了,直接執(zhí)行腳本字符串。而是說不需要在運(yùn)行之前先編譯程序成為exe文件,而是在運(yùn)行的過程中邊運(yùn)行邊執(zhí)行。
ok,我們回到JavaScript的解析執(zhí)行過程。
在整體上,JavaScript的解析執(zhí)行過程分為兩個(gè)步驟:
其中,編譯是在解釋器中進(jìn)行,將代碼編譯成可執(zhí)行碼。運(yùn)行是在JavaScript引擎中進(jìn)行,執(zhí)行可執(zhí)行碼。
過程如下:
編譯過程不必多說,我們只要清楚這個(gè)過程會(huì)將字符串代碼編譯為可執(zhí)行碼。
重點(diǎn)是運(yùn)行過程,運(yùn)行又由兩個(gè)過程組成
預(yù)解析的工作是
重點(diǎn)注意收集變量這一功能,又名為變量提升,收集的變量有以下三種:
若是變量名有重復(fù)的話,按照優(yōu)先級(jí)來確定:
function聲明定義>函數(shù)參數(shù)>var聲明的變量
tips:
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ī)則:
不過若是以下的情況,必須得加上';',否則的話,會(huì)出現(xiàn)報(bào)錯(cuò)。
還有,其實(shí)所有代碼都可以寫在一行中。只要有';'來分隔開每一句就ok。并且,if及for及while的函數(shù)體也可以寫在同一行中。
只要做好分隔工作,那么就都可以寫在同一行。
JavaScript有6種數(shù)據(jù)類型(暫且不論symbol):Number,Boolean,String,Null,Undefined,Object
其中,分為兩大類別
tips:
關(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)存是有限的,所以分配的內(nèi)存必須得在適當(dāng)?shù)臅r(shí)機(jī)回收以供后繼使用。
內(nèi)存的生命周期為:
這第三步對(duì)應(yīng)的就是垃圾回收。
那么JavaScript引擎是如何判斷該內(nèi)存需不需要釋放呢——標(biāo)記清楚機(jī)制。
垃圾回收器每隔一段時(shí)間都會(huì)檢查一次內(nèi)存,找到其中失去引用的變量,并釋放掉。
其中失去引用一般有兩種原因
tips:
淺拷貝開辟一個(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ù)組的淺拷貝)
深拷貝:(這個(gè)方法既適用于對(duì)象又適用于數(shù)組)
使用typeof來檢測(cè)基本類型,用instanceof來檢測(cè)對(duì)象還是數(shù)組
數(shù)據(jù)類型有:
number,string,boolean,null,undefined,object,function,array
typeof一般只能返回如下結(jié)果:
number,string,boolean,object(null,object,array),function,undefined
tips:
由于引用類型的數(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)系。
大家應(yīng)該都有接觸過函數(shù)調(diào)用棧吧,執(zhí)行上下文就是每次壓入棧的內(nèi)容。
執(zhí)行上下文可以理解為當(dāng)前代碼的執(zhí)行環(huán)境,它會(huì)形成一個(gè)作用域。
JavaScript的執(zhí)行環(huán)境大致可以分為三種:
所以,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:
執(zhí)行上下文的生命周期可以分為兩個(gè)階段:
執(zhí)行上下文由三部分組成:
該對(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)閉包的操作:
閉包的核心就是——通過在外部函數(shù)(B)的外部(C)保存內(nèi)部函數(shù)(A)的引用,當(dāng)執(zhí)行該引用(A)時(shí),由于創(chuàng)建的執(zhí)行上下文的作用域鏈中包含有外部函數(shù)(B)的引用,從而使外部函數(shù)(B)的執(zhí)行上下文不會(huì)被垃圾回收。
這樣就能保存之前執(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ì)象。
關(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)如下:
new到底做了什么呢?
這兩者的作用都是修改函數(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ù)是ES6的新語法,形式如下:
(參數(shù)部分)=>{ 函數(shù)體部分 }
其中,如果參數(shù)只有一個(gè),則可以省略括號(hào)。如果沒有參數(shù)或多個(gè)參數(shù),括號(hào)不能省略。
箭頭函數(shù)中this指向規(guī)則與普通函數(shù)的規(guī)則不同,他的this指向規(guī)則為:
捕獲其所在(即定義的位置)上下文的this值, 作為自己的this值,
tips:
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ì)象。
JavaScript是一門面向過程的語言,但隨著網(wǎng)頁需求功能的復(fù)雜化,工程化,要求JavaScript應(yīng)該也有面向?qū)ο缶幊痰哪芰Α?/p>
我們可以通過字面量對(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è)普通的函數(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)存里的方法呢?
我們創(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è)步驟:
具體代碼如下:
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)
這就是繼承的最終解決方案了。
前面說過,js只有全局作用域及函數(shù)作用域,沒有塊級(jí)作用域。
但是let和const會(huì)引入塊級(jí)作用域。
這兩者的特點(diǎn)為:
我們常常使用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á)式
同樣,以一個(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ù)中
ES6對(duì)對(duì)象字面量做了簡化語法的處理。
var name = 'Bob'var age = 20var arc = 'sex'var person = { name, age, [arc]:'male' getName(){ return this.name }}
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):
其中,constructor函數(shù)就是原先的構(gòu)造函數(shù),當(dāng)new一個(gè)對(duì)象的時(shí)候就是默認(rèn)調(diào)用這個(gè)函數(shù),因?yàn)轭惷褪侵赶虻腸onstructor函數(shù)。
構(gòu)造函數(shù).prototype.方法名=...
來聲明。類名.prototype.方法名=...
來在類外動(dòng)態(tài)為類定義方法。return new A()
class類不會(huì)變量提升
注意:
使用extends關(guān)鍵字。
然后在子的constructor函數(shù)中使用super()來調(diào)用父的構(gòu)造函數(shù),從而復(fù)制父的實(shí)例屬性。繼承過后,通過原型鏈,可以訪問到父級(jí)的所有原型鏈(即可訪問到以往所有被用prototype聲明的對(duì)象)內(nèi)容。
super在子類定義中有兩種使用方法:
注意:
靜態(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:
[變量]:值
,當(dāng)要調(diào)用這個(gè)屬性時(shí),對(duì)象[變量]
class中聲明方法:
class Box{ constructor(msg){ this.msg = msg } save(){ console.log(this.msg) } }
對(duì)象中聲明方法:
var Box = { save:function(){ console.log('aaa') }}
我們要保證異步的順序執(zhí)行,在以往都是通過回調(diào)函數(shù)來實(shí)現(xiàn)。
然而回調(diào)函數(shù)有兩個(gè)缺陷:
這兩者都會(huì)導(dǎo)致閱讀源碼的困難。
所以,我們需要新的方案能夠有以下兩個(gè)特點(diǎn):
Promise能做到這兩點(diǎn)。
Promise對(duì)象有三種狀態(tài):
這三種狀態(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:
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方法是.then(null, rejection)的別名,用于接受rejected狀態(tài)與異常狀態(tài)的執(zhí)行。
Promise對(duì)象的rejected狀態(tài)與異常狀態(tài)具有冒泡性質(zhì),一直向后傳遞,直到被捕獲為止。
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
即
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.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的值。
但是,Promise這個(gè)異步方案還不夠完美,我們異步的最終目標(biāo)就是以同步的書寫方式書寫異步。
酷吧!就是說我們并不關(guān)心他是不是異步,不管是同步還是異步我們都以從上到下的書寫方式來書寫代碼,這才是人類的思維方式?。?/p>
所以也就產(chǎn)生了async這個(gè)異步解決方案了。
需要注意的是,async是基于Promise的,即他不是一套獨(dú)立的解決方案,而是基于Promise的異步機(jī)制的一次進(jìn)步,代替then()的寫法。
先聲明幾個(gè)重要的知識(shí)點(diǎn):
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í)行順序是這樣的:
tips:
聯(lián)系客服