前端面試百問(wèn)百答
點(diǎn)擊下方星標(biāo)本公眾號(hào),實(shí)用前端技術(shù)文章及時(shí)了解
附筆記鏈接,閱讀往期更多優(yōu)質(zhì)文章可移步查看,喜歡的可以給我點(diǎn)贊鼓勵(lì)哦:github.com/Wscats/articles[1]
1. 介紹 js 的基本數(shù)據(jù)類型。
js?一共有六種基本數(shù)據(jù)類型,分別是?Undefined、Null、Boolean、Number、String,還有在?ES6?中新增的?Symbol?類型,
代表創(chuàng)建后獨(dú)一無(wú)二且不可變的數(shù)據(jù)類型,它的出現(xiàn)我認(rèn)為主要是為了解決可能出現(xiàn)的全局變量沖突的問(wèn)題。
復(fù)制代碼
2. JavaScript 有幾種類型的值?你能畫一下他們的內(nèi)存圖嗎?
涉及知識(shí)點(diǎn):
棧:原始數(shù)據(jù)類型(Undefined、Null、Boolean、Number、String) 堆:引用數(shù)據(jù)類型(對(duì)象、數(shù)組和函數(shù))
兩種類型的區(qū)別是:存儲(chǔ)位置不同。
原始數(shù)據(jù)類型直接存儲(chǔ)在棧(stack)中的簡(jiǎn)單數(shù)據(jù)段,占據(jù)空間小、大小固定,屬于被頻繁使用數(shù)據(jù),所以放入棧中存儲(chǔ)。
引用數(shù)據(jù)類型存儲(chǔ)在堆(heap)中的對(duì)象,占據(jù)空間大、大小不固定。如果存儲(chǔ)在棧中,將會(huì)影響程序運(yùn)行的性能;引用數(shù)據(jù)類型在
棧中存儲(chǔ)了指針,該指針指向堆中該實(shí)體的起始地址。當(dāng)解釋器尋找引用值時(shí),會(huì)首先檢索其在棧中的地址,取得地址后從堆中獲得實(shí)
體。
復(fù)制代碼
回答:
js 可以分為兩種類型的值,一種是基本數(shù)據(jù)類型,一種是復(fù)雜數(shù)據(jù)類型。
基本數(shù)據(jù)類型....(參考1)
復(fù)雜數(shù)據(jù)類型指的是?Object?類型,所有其他的如?Array、Date?等數(shù)據(jù)類型都可以理解為?Object?類型的子類。
兩種類型間的主要區(qū)別是它們的存儲(chǔ)位置不同,基本數(shù)據(jù)類型的值直接保存在棧中,而復(fù)雜數(shù)據(jù)類型的值保存在堆中,通過(guò)使用在棧中
保存對(duì)應(yīng)的指針來(lái)獲取堆中的值。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 有幾種類型的值?》[2] 《JavaScript 有幾種類型的值?能否畫一下它們的內(nèi)存圖;》[3]
3. 什么是堆?什么是棧?它們之間有什么區(qū)別和聯(lián)系?
堆和棧的概念存在于數(shù)據(jù)結(jié)構(gòu)中和操作系統(tǒng)內(nèi)存中。
在數(shù)據(jù)結(jié)構(gòu)中,棧中數(shù)據(jù)的存取方式為先進(jìn)后出。而堆是一個(gè)優(yōu)先隊(duì)列,是按優(yōu)先級(jí)來(lái)進(jìn)行排序的,優(yōu)先級(jí)可以按照大小來(lái)規(guī)定。完全
二叉樹是堆的一種實(shí)現(xiàn)方式。
在操作系統(tǒng)中,內(nèi)存被分為棧區(qū)和堆區(qū)。
棧區(qū)內(nèi)存由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
堆區(qū)內(nèi)存一般由程序員分配釋放,若程序員不釋放,程序結(jié)束時(shí)可能由垃圾回收機(jī)制回收。
復(fù)制代碼
詳細(xì)資料可以參考:《什么是堆?什么是棧?他們之間有什么區(qū)別和聯(lián)系?》[4
4. 內(nèi)部屬性 [[Class]] 是什么?
//?所有?typeof?返回值為?"object"?的對(duì)象(如數(shù)組)都包含一個(gè)內(nèi)部屬性?[[Class]](我們可以把它看作一個(gè)內(nèi)部的分類,而非
//?傳統(tǒng)的面向?qū)ο笠饬x上的類)。這個(gè)屬性無(wú)法直接訪問(wèn),一般通過(guò) Object.prototype.toString(..)?來(lái)查看。例如:
Object.prototype.toString.call(?[1,2,3]?);
//?"[object?Array]"
Object.prototype.toString.call(?/regex-literal/i?);
//?"[object?RegExp]"
復(fù)制代碼
5. 介紹 js 有哪些內(nèi)置對(duì)象?
涉及知識(shí)點(diǎn):
全局的對(duì)象(?global?objects?)或稱標(biāo)準(zhǔn)內(nèi)置對(duì)象,不要和?"全局對(duì)象(global?object)"?混淆。這里說(shuō)的全局的對(duì)象是說(shuō)在
全局作用域里的對(duì)象。全局作用域中的其他對(duì)象可以由用戶的腳本創(chuàng)建或由宿主程序提供。
標(biāo)準(zhǔn)內(nèi)置對(duì)象的分類
(1)值屬性,這些全局屬性返回一個(gè)簡(jiǎn)單值,這些值沒(méi)有自己的屬性和方法。
例如?Infinity、NaN、undefined、null?字面量
(2)函數(shù)屬性,全局函數(shù)可以直接調(diào)用,不需要在調(diào)用時(shí)指定所屬對(duì)象,執(zhí)行結(jié)束后會(huì)將結(jié)果直接返回給調(diào)用者。
例如?eval()、parseFloat()、parseInt()?等
(3)基本對(duì)象,基本對(duì)象是定義或使用其他對(duì)象的基礎(chǔ)。基本對(duì)象包括一般對(duì)象、函數(shù)對(duì)象和錯(cuò)誤對(duì)象。
例如?Object、Function、Boolean、Symbol、Error?等
(4)數(shù)字和日期對(duì)象,用來(lái)表示數(shù)字、日期和執(zhí)行數(shù)學(xué)計(jì)算的對(duì)象。
例如?Number、Math、Date
(5)字符串,用來(lái)表示和操作字符串的對(duì)象。
例如?String、RegExp
(6)可索引的集合對(duì)象,這些對(duì)象表示按照索引值來(lái)排序的數(shù)據(jù)集合,包括數(shù)組和類型數(shù)組,以及類數(shù)組結(jié)構(gòu)的對(duì)象。例如?Array
(7)使用鍵的集合對(duì)象,這些集合對(duì)象在存儲(chǔ)數(shù)據(jù)時(shí)會(huì)使用到鍵,支持按照插入順序來(lái)迭代元素。
例如?Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的數(shù)據(jù)會(huì)被組織為一個(gè)數(shù)據(jù)序列。
例如?SIMD?等
(9)結(jié)構(gòu)化數(shù)據(jù),這些對(duì)象用來(lái)表示和操作結(jié)構(gòu)化的緩沖區(qū)數(shù)據(jù),或使用?JSON?編碼的數(shù)據(jù)。
例如?JSON?等
(10)控制抽象對(duì)象
例如?Promise、Generator?等
(11)反射
例如?Reflect、Proxy
(12)國(guó)際化,為了支持多語(yǔ)言處理而加入 ECMAScript 的對(duì)象。
例如?Intl、Intl.Collator?等
(13)WebAssembly
(14)其他
例如?arguments
復(fù)制代碼
回答:
js?中的內(nèi)置對(duì)象主要指的是在程序執(zhí)行前存在全局作用域里的由?js?定義的一些全局值屬性、函數(shù)和用來(lái)實(shí)例化其他對(duì)象的構(gòu)造函
數(shù)對(duì)象。一般我們經(jīng)常用到的如全局變量值?NaN、undefined,全局函數(shù)如?parseInt()、parseFloat()?用來(lái)實(shí)例化對(duì)象的構(gòu)
造函數(shù)如?Date、Object?等,還有提供數(shù)學(xué)計(jì)算的單體內(nèi)置對(duì)象如?Math?對(duì)象。
復(fù)制代碼
詳細(xì)資料可以參考:《標(biāo)準(zhǔn)內(nèi)置對(duì)象的分類》[5] 《JS 所有內(nèi)置對(duì)象屬性和方法匯總》[6]
6. undefined 與 undeclared 的區(qū)別?
已在作用域中聲明但還沒(méi)有賦值的變量,是?undefined?的。相反,還沒(méi)有在作用域中聲明過(guò)的變量,是 undeclared 的。
對(duì)于?undeclared?變量的引用,瀏覽器會(huì)報(bào)引用錯(cuò)誤,如?ReferenceError: b is not defined 。但是我們可以使用 typ
eof?的安全防范機(jī)制來(lái)避免報(bào)錯(cuò),因?yàn)閷?duì)于?undeclared(或者?not?defined?)變量,typeof?會(huì)返回?"undefined"。
復(fù)制代碼
7. null 和 undefined 的區(qū)別?
首先?Undefined?和?Null?都是基本數(shù)據(jù)類型,這兩個(gè)基本數(shù)據(jù)類型分別都只有一個(gè)值,就是?undefined?和?null。
undefined?代表的含義是未定義,null?代表的含義是空對(duì)象。一般變量聲明了但還沒(méi)有定義的時(shí)候會(huì)返回?undefined,null
主要用于賦值給一些可能會(huì)返回對(duì)象的變量,作為初始化。
undefined?在?js?中不是一個(gè)保留字,這意味著我們可以使用?undefined?來(lái)作為一個(gè)變量名,這樣的做法是非常危險(xiǎn)的,它
會(huì)影響我們對(duì)?undefined?值的判斷。但是我們可以通過(guò)一些方法獲得安全的?undefined?值,比如說(shuō)?void?0。
當(dāng)我們對(duì)兩種類型使用?typeof?進(jìn)行判斷的時(shí)候,Null 類型化會(huì)返回?“object”,這是一個(gè)歷史遺留的問(wèn)題。當(dāng)我們使用雙等
號(hào)對(duì)兩種類型的值進(jìn)行比較時(shí)會(huì)返回?true,使用三個(gè)等號(hào)時(shí)會(huì)返回?false。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之 undefined 與 null》[7]
8. 如何獲取安全的 undefined 值?
因?yàn)?undefined?是一個(gè)標(biāo)識(shí)符,所以可以被當(dāng)作變量來(lái)使用和賦值,但是這樣會(huì)影響?undefined?的正常判斷。
表達(dá)式?void?___?沒(méi)有返回值,因此返回結(jié)果是?undefined。void?并不改變表達(dá)式的結(jié)果,只是讓表達(dá)式不返回值。
按慣例我們用?void?0?來(lái)獲得?undefined。
復(fù)制代碼
9. 說(shuō)幾條寫 JavaScript 的基本規(guī)范?
在平常項(xiàng)目開發(fā)中,我們遵守一些這樣的基本規(guī)范,比如說(shuō):
(1)一個(gè)函數(shù)作用域中所有的變量聲明應(yīng)該盡量提到函數(shù)首部,用一個(gè)?var?聲明,不允許出現(xiàn)兩個(gè)連續(xù)的?var?聲明,聲明時(shí)
????如果變量沒(méi)有值,應(yīng)該給該變量賦值對(duì)應(yīng)類型的初始值,便于他人閱讀代碼時(shí),能夠一目了然的知道變量對(duì)應(yīng)的類型值。
(2)代碼中出現(xiàn)地址、時(shí)間等字符串時(shí)需要使用常量代替。
(3)在進(jìn)行比較的時(shí)候吧,盡量使用'===',?'!=='代替'==',?'!='。
(4)不要在內(nèi)置對(duì)象的原型上添加方法,如?Array,?Date。
(5)switch?語(yǔ)句必須帶有?default?分支。
(6)for?循環(huán)必須使用大括號(hào)。
(7)if?語(yǔ)句必須使用大括號(hào)。
復(fù)制代碼
10. JavaScript 原型,原型鏈?有什么特點(diǎn)?
在?js?中我們是使用構(gòu)造函數(shù)來(lái)新建一個(gè)對(duì)象的,每一個(gè)構(gòu)造函數(shù)的內(nèi)部都有一個(gè)?prototype?屬性值,這個(gè)屬性值是一個(gè)對(duì)
象,這個(gè)對(duì)象包含了可以由該構(gòu)造函數(shù)的所有實(shí)例共享的屬性和方法。當(dāng)我們使用構(gòu)造函數(shù)新建一個(gè)對(duì)象后,在這個(gè)對(duì)象的內(nèi)部
將包含一個(gè)指針,這個(gè)指針指向構(gòu)造函數(shù)的 prototype 屬性對(duì)應(yīng)的值,在 ES5 中這個(gè)指針被稱為對(duì)象的原型。一般來(lái)說(shuō)我們
是不應(yīng)該能夠獲取到這個(gè)值的,但是現(xiàn)在瀏覽器中都實(shí)現(xiàn)了?__proto__?屬性來(lái)讓我們?cè)L問(wèn)這個(gè)屬性,但是我們最好不要使用這
個(gè)屬性,因?yàn)樗皇且?guī)范中規(guī)定的。ES5 中新增了一個(gè)?Object.getPrototypeOf()?方法,我們可以通過(guò)這個(gè)方法來(lái)獲取對(duì)
象的原型。
當(dāng)我們?cè)L問(wèn)一個(gè)對(duì)象的屬性時(shí),如果這個(gè)對(duì)象內(nèi)部不存在這個(gè)屬性,那么它就會(huì)去它的原型對(duì)象里找這個(gè)屬性,這個(gè)原型對(duì)象又
會(huì)有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來(lái)說(shuō)都是?Object.prototype?所以這就
是我們新建的對(duì)象為什么能夠使用 toString()?等方法的原因。
特點(diǎn):
JavaScript 對(duì)象是通過(guò)引用來(lái)傳遞的,我們創(chuàng)建的每個(gè)新對(duì)象實(shí)體中并沒(méi)有一份屬于自己的原型副本。當(dāng)我們修改原型時(shí),與
之相關(guān)的對(duì)象也會(huì)繼承這一改變。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之原型與原型鏈》[8]
11. js 獲取原型的方法?
p.proto p.constructor.prototype Object.getPrototypeOf(p)
12. 在 js 中不同進(jìn)制數(shù)字的表示方式
以 0X、0x 開頭的表示為十六進(jìn)制。
以 0、0O、0o 開頭的表示為八進(jìn)制。
以 0B、0b 開頭的表示為二進(jìn)制格式。
13. js 中整數(shù)的安全范圍是多少?
安全整數(shù)指的是,在這個(gè)范圍內(nèi)的整數(shù)轉(zhuǎn)化為二進(jìn)制存儲(chǔ)的時(shí)候不會(huì)出現(xiàn)精度丟失,能夠被“安全”呈現(xiàn)的最大整數(shù)是?2^53?-?1,
即9007199254740991,在?ES6?中被定義為?Number.MAX_SAFE_INTEGER。最小整數(shù)是-9007199254740991,在?ES6?中
被定義為?Number.MIN_SAFE_INTEGER。
如果某次計(jì)算的結(jié)果得到了一個(gè)超過(guò)?JavaScript?數(shù)值范圍的值,那么這個(gè)值會(huì)被自動(dòng)轉(zhuǎn)換為特殊的?Infinity?值。如果某次
計(jì)算返回了正或負(fù)的?Infinity?值,那么該值將無(wú)法參與下一次的計(jì)算。判斷一個(gè)數(shù)是不是有窮的,可以使用?isFinite?函數(shù)
來(lái)判斷。
復(fù)制代碼
14. typeof NaN 的結(jié)果是什么?
NaN?意指“不是一個(gè)數(shù)字”(not?a?number),NaN?是一個(gè)“警戒值”(sentinel?value,有特殊用途的常規(guī)值),用于指出
數(shù)字類型中的錯(cuò)誤情況,即“執(zhí)行數(shù)學(xué)運(yùn)算沒(méi)有成功,這是失敗后返回的結(jié)果”。
typeof?NaN;?//?"number"
NaN?是一個(gè)特殊值,它和自身不相等,是唯一一個(gè)非自反(自反,reflexive,即 x === x 不成立)的值。而?NaN?!=?NaN
為?true。
復(fù)制代碼
15. isNaN 和 Number.isNaN 函數(shù)的區(qū)別?
函數(shù)?isNaN?接收參數(shù)后,會(huì)嘗試將這個(gè)參數(shù)轉(zhuǎn)換為數(shù)值,任何不能被轉(zhuǎn)換為數(shù)值的的值都會(huì)返回?true,因此非數(shù)字值傳入也會(huì)
返回?true?,會(huì)影響?NaN?的判斷。
函數(shù)?Number.isNaN?會(huì)首先判斷傳入?yún)?shù)是否為數(shù)字,如果是數(shù)字再繼續(xù)判斷是否為?NaN?,這種方法對(duì)于?NaN?的判斷更為
準(zhǔn)確。
復(fù)制代碼
16. Array 構(gòu)造函數(shù)只有一個(gè)參數(shù)值時(shí)的表現(xiàn)?
Array?構(gòu)造函數(shù)只帶一個(gè)數(shù)字參數(shù)的時(shí)候,該參數(shù)會(huì)被作為數(shù)組的預(yù)設(shè)長(zhǎng)度(length),而非只充當(dāng)數(shù)組中的一個(gè)元素。這樣
創(chuàng)建出來(lái)的只是一個(gè)空數(shù)組,只不過(guò)它的 length 屬性被設(shè)置成了指定的值。
構(gòu)造函數(shù)?Array(..)?不要求必須帶?new?關(guān)鍵字。不帶時(shí),它會(huì)被自動(dòng)補(bǔ)上。
復(fù)制代碼
17. 其他值到字符串的轉(zhuǎn)換規(guī)則?
規(guī)范的?9.8?節(jié)中定義了抽象操作 ToString ,它負(fù)責(zé)處理非字符串到字符串的強(qiáng)制類型轉(zhuǎn)換。
(1)Null?和?Undefined?類型?,null?轉(zhuǎn)換為?"null",undefined?轉(zhuǎn)換為?"undefined",
(2)Boolean?類型,true?轉(zhuǎn)換為?"true",false?轉(zhuǎn)換為?"false"。
(3)Number?類型的值直接轉(zhuǎn)換,不過(guò)那些極小和極大的數(shù)字會(huì)使用指數(shù)形式。
(4)Symbol?類型的值直接轉(zhuǎn)換,但是只允許顯式強(qiáng)制類型轉(zhuǎn)換,使用隱式強(qiáng)制類型轉(zhuǎn)換會(huì)產(chǎn)生錯(cuò)誤。
(3)對(duì)普通對(duì)象來(lái)說(shuō),除非自行定義?toString()?方法,否則會(huì)調(diào)用?toString()(Object.prototype.toString())
????來(lái)返回內(nèi)部屬性?[[Class]]?的值,如"[object?Object]"。如果對(duì)象有自己的 toString()?方法,字符串化時(shí)就會(huì)
????調(diào)用該方法并使用其返回值。
復(fù)制代碼
18. 其他值到數(shù)字值的轉(zhuǎn)換規(guī)則?
有時(shí)我們需要將非數(shù)字值當(dāng)作數(shù)字來(lái)使用,比如數(shù)學(xué)運(yùn)算。為此 ES5 規(guī)范在?9.3?節(jié)定義了抽象操作 ToNumber。
(1)Undefined?類型的值轉(zhuǎn)換為?NaN。
(2)Null?類型的值轉(zhuǎn)換為?0。
(3)Boolean?類型的值,true?轉(zhuǎn)換為?1,false?轉(zhuǎn)換為?0。
(4)String?類型的值轉(zhuǎn)換如同使用?Number()?函數(shù)進(jìn)行轉(zhuǎn)換,如果包含非數(shù)字值則轉(zhuǎn)換為?NaN,空字符串為?0。
(5)Symbol?類型的值不能轉(zhuǎn)換為數(shù)字,會(huì)報(bào)錯(cuò)。
(6)對(duì)象(包括數(shù)組)會(huì)首先被轉(zhuǎn)換為相應(yīng)的基本類型值,如果返回的是非數(shù)字的基本類型值,則再遵循以上規(guī)則將其強(qiáng)制轉(zhuǎn)換為數(shù)字。
為了將值轉(zhuǎn)換為相應(yīng)的基本類型值,抽象操作 ToPrimitive 會(huì)首先(通過(guò)內(nèi)部操作 DefaultValue)檢查該值是否有valueOf()?方法。如果有并且返回基本類型值,就使用該值進(jìn)行強(qiáng)制類型轉(zhuǎn)換。如果沒(méi)有就使用 toString()?的返回值(如果存在)來(lái)進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
如果?valueOf()?和?toString()?均不返回基本類型值,會(huì)產(chǎn)生?TypeError?錯(cuò)誤。
復(fù)制代碼
19. 其他值到布爾類型的值的轉(zhuǎn)換規(guī)則?
ES5?規(guī)范?9.2?節(jié)中定義了抽象操作 ToBoolean,列舉了布爾強(qiáng)制類型轉(zhuǎn)換所有可能出現(xiàn)的結(jié)果。
以下這些是假值:
??undefined
??null
??false
??+0、-0?和?NaN
??""
假值的布爾強(qiáng)制類型轉(zhuǎn)換結(jié)果為?false。從邏輯上說(shuō),假值列表以外的都應(yīng)該是真值。
復(fù)制代碼
20. {} 和 [] 的 valueOf 和 toString 的結(jié)果是什么?
{}?的?valueOf?結(jié)果為?{}?,toString?的結(jié)果為?"[object?Object]"
[]?的?valueOf?結(jié)果為?[]?,toString?的結(jié)果為?""
復(fù)制代碼
21. 什么是假值對(duì)象?
瀏覽器在某些特定情況下,在常規(guī) JavaScript 語(yǔ)法基礎(chǔ)上自己創(chuàng)建了一些外來(lái)值,這些就是“假值對(duì)象”。假值對(duì)象看起來(lái)和
普通對(duì)象并無(wú)二致(都有屬性,等等),但將它們強(qiáng)制類型轉(zhuǎn)換為布爾值時(shí)結(jié)果為?false?最常見(jiàn)的例子是?document.all,它
是一個(gè)類數(shù)組對(duì)象,包含了頁(yè)面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供給 JavaScript 程序使用。
復(fù)制代碼
22. ~ 操作符的作用?
~?返回?2?的補(bǔ)碼,并且?~?會(huì)將數(shù)字轉(zhuǎn)換為?32?位整數(shù),因此我們可以使用?~?來(lái)進(jìn)行取整操作。
~x?大致等同于?-(x+1)。
復(fù)制代碼
23. 解析字符串中的數(shù)字和將字符串強(qiáng)制類型轉(zhuǎn)換為數(shù)字的返回結(jié)果都是數(shù)字,它們之間的區(qū)別是什么?
解析允許字符串(如?parseInt()?)中含有非數(shù)字字符,解析按從左到右的順序,如果遇到非數(shù)字字符就停止。而轉(zhuǎn)換(如 Nu
mber?())不允許出現(xiàn)非數(shù)字字符,否則會(huì)失敗并返回?NaN。
復(fù)制代碼
24. + 操作符什么時(shí)候用于字符串的拼接?
根據(jù)?ES5?規(guī)范?11.6.1?節(jié),如果某個(gè)操作數(shù)是字符串或者能夠通過(guò)以下步驟轉(zhuǎn)換為字符串的話,+?將進(jìn)行拼接操作。如果其
中一個(gè)操作數(shù)是對(duì)象(包括數(shù)組),則首先對(duì)其調(diào)用?ToPrimitive?抽象操作,該抽象操作再調(diào)用?[[DefaultValue]],以
數(shù)字作為上下文。如果不能轉(zhuǎn)換為字符串,則會(huì)將其轉(zhuǎn)換為數(shù)字類型來(lái)進(jìn)行計(jì)算。
簡(jiǎn)單來(lái)說(shuō)就是,如果?+?的其中一個(gè)操作數(shù)是字符串(或者通過(guò)以上步驟最終得到字符串),則執(zhí)行字符串拼接,否則執(zhí)行數(shù)字
加法。
那么對(duì)于除了加法的運(yùn)算符來(lái)說(shuō),只要其中一方是數(shù)字,那么另一方就會(huì)被轉(zhuǎn)為數(shù)字。
復(fù)制代碼
25. 什么情況下會(huì)發(fā)生布爾值的隱式強(qiáng)制類型轉(zhuǎn)換?
(1)?if?(..)?語(yǔ)句中的條件判斷表達(dá)式。
(2)?for?( .. ; .. ; .. )?語(yǔ)句中的條件判斷表達(dá)式(第二個(gè))。
(3)?while?(..)?和?do..while(..)?循環(huán)中的條件判斷表達(dá)式。
(4) ? :?中的條件判斷表達(dá)式。
(5)?邏輯運(yùn)算符?||(邏輯或)和?&&(邏輯與)左邊的操作數(shù)(作為條件判斷表達(dá)式)。
復(fù)制代碼
26. || 和 && 操作符的返回值?
||?和?&&?首先會(huì)對(duì)第一個(gè)操作數(shù)執(zhí)行條件判斷,如果其不是布爾值就先進(jìn)行?ToBoolean?強(qiáng)制類型轉(zhuǎn)換,然后再執(zhí)行條件
判斷。
對(duì)于?||?來(lái)說(shuō),如果條件判斷結(jié)果為?true?就返回第一個(gè)操作數(shù)的值,如果為?false?就返回第二個(gè)操作數(shù)的值。
&&?則相反,如果條件判斷結(jié)果為?true?就返回第二個(gè)操作數(shù)的值,如果為?false?就返回第一個(gè)操作數(shù)的值。
||?和?&&?返回它們其中一個(gè)操作數(shù)的值,而非條件判斷的結(jié)果
復(fù)制代碼
27. Symbol 值的強(qiáng)制類型轉(zhuǎn)換?
ES6 允許從符號(hào)到字符串的顯式強(qiáng)制類型轉(zhuǎn)換,然而隱式強(qiáng)制類型轉(zhuǎn)換會(huì)產(chǎn)生錯(cuò)誤。
Symbol?值不能夠被強(qiáng)制類型轉(zhuǎn)換為數(shù)字(顯式和隱式都會(huì)產(chǎn)生錯(cuò)誤),但可以被強(qiáng)制類型轉(zhuǎn)換為布爾值(顯式和隱式結(jié)果
都是?true?)。
復(fù)制代碼
28. == 操作符的強(qiáng)制類型轉(zhuǎn)換規(guī)則?
(1)字符串和數(shù)字之間的相等比較,將字符串轉(zhuǎn)換為數(shù)字之后再進(jìn)行比較。
(2)其他類型和布爾類型之間的相等比較,先將布爾值轉(zhuǎn)換為數(shù)字后,再應(yīng)用其他規(guī)則進(jìn)行比較。
(3)null?和?undefined?之間的相等比較,結(jié)果為真。其他值和它們進(jìn)行比較都返回假值。
(4)對(duì)象和非對(duì)象之間的相等比較,對(duì)象先調(diào)用 ToPrimitive 抽象操作后,再進(jìn)行比較。
(5)如果一個(gè)操作值為?NaN?,則相等比較返回?false(?NaN?本身也不等于?NaN?)。
(6)如果兩個(gè)操作值都是對(duì)象,則比較它們是不是指向同一個(gè)對(duì)象。如果兩個(gè)操作數(shù)都指向同一個(gè)對(duì)象,則相等操作符返回?true,否則,返回?false。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 字符串間的比較》[9]
29. 如何將字符串轉(zhuǎn)化為數(shù)字,例如 '12.3b'?
(1)使用?Number()?方法,前提是所包含的字符串不包含不合法字符。
(2)使用?parseInt()?方法,parseInt()?函數(shù)可解析一個(gè)字符串,并返回一個(gè)整數(shù)。還可以設(shè)置要解析的數(shù)字的基數(shù)。當(dāng)基數(shù)的值為?0,或沒(méi)有設(shè)置該參數(shù)時(shí),parseInt()?會(huì)根據(jù) string 來(lái)判斷數(shù)字的基數(shù)。
(3)使用?parseFloat()?方法,該函數(shù)解析一個(gè)字符串參數(shù)并返回一個(gè)浮點(diǎn)數(shù)。
(4)使用?+?操作符的隱式轉(zhuǎn)換。
復(fù)制代碼
詳細(xì)資料可以參考:《詳解 JS 中 Number()、parseInt() 和 parseFloat() 的區(qū)別》[10]
30. 如何將浮點(diǎn)數(shù)點(diǎn)左邊的數(shù)每三位添加一個(gè)逗號(hào),如 12000000.11 轉(zhuǎn)化為『12,000,000.11』?
function?format(number)?{
??return?number?&&?number.replace(/(?!^)(?=(\d{3})+\.)/g,?",");
}
復(fù)制代碼
31. 常用正則表達(dá)式
//?(1)匹配?16?進(jìn)制顏色值
var?regex?=?/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
//?(2)匹配日期,如?yyyy-mm-dd?格式
var?regex?=?/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
//?(3)匹配?qq?號(hào)
var?regex?=?/^[1-9][0-9]{4,10}$/g;
//?(4)手機(jī)號(hào)碼正則
var?regex?=?/^1[34578]\d{9}$/g;
//?(5)用戶名正則
var?regex?=?/^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
復(fù)制代碼
詳細(xì)資料可以參考:《前端表單驗(yàn)證常用的 15 個(gè) JS 正則表達(dá)式》[11] 《JS 常用正則匯總》[12]
32. 生成隨機(jī)數(shù)的各種方法?
《JS - 生成隨機(jī)數(shù)的方法匯總(不同范圍、類型的隨機(jī)數(shù))》[13]
33. 如何實(shí)現(xiàn)數(shù)組的隨機(jī)排序?
//?(1)使用數(shù)組 sort 方法對(duì)數(shù)組元素隨機(jī)排序,讓 Math.random()?出來(lái)的數(shù)與?0.5 比較,如果大于就返回 1 交換位置,如果小于就返回?-1,不交換位置。
function?randomSort(a,?b)?{
??return?Math.random()?>?0.5???-1?:?1;
}
//??缺點(diǎn):每個(gè)元素被派到新數(shù)組的位置不是隨機(jī)的,原因是 sort()?方法是依次比較的。
//?(2)隨機(jī)從原數(shù)組抽取一個(gè)元素,加入到新數(shù)組
function?randomSort(arr)?{
??var?result?=?[];
??while?(arr.length?>?0)?{
????var?randomIndex?=?Math.floor(Math.random()?*?arr.length);
????result.push(arr[randomIndex]);
????arr.splice(randomIndex,?1);
??}
??return?result;
}
//?(3)隨機(jī)交換數(shù)組內(nèi)的元素(洗牌算法類似)
function?randomSort(arr)?{
??var?index,
????randomIndex,
????temp,
????len?=?arr.length;
??for?(index?=?0;?index?????randomIndex?=?Math.floor(Math.random()?*?(len?-?index))?+?index;
????temp?=?arr[index];
????arr[index]?=?arr[randomIndex];
????arr[randomIndex]?=?temp;
??}
??return?arr;
}
//?es6
function?randomSort(array)?{
??let?length?=?array.length;
??if?(!Array.isArray(array)?||?length?<=?1)?return;
??for?(let?index?=?0;?index?1;?index++)?{
????let?randomIndex?=?Math.floor(Math.random()?*?(length?-?index))?+?index;
????[array[index],?array[randomIndex]]?=?[array[randomIndex],?array[index]];
??}
??return?array;
}
復(fù)制代碼
詳細(xì)資料可以參考:《Fisher and Yates 的原始版》[14] 《javascript 實(shí)現(xiàn)數(shù)組隨機(jī)排序?》[15] 《JavaScript 學(xué)習(xí)筆記:數(shù)組隨機(jī)排序》[16]
34. javascript 創(chuàng)建對(duì)象的幾種方式?
我們一般使用字面量的形式直接創(chuàng)建對(duì)象,但是這種創(chuàng)建方式對(duì)于創(chuàng)建大量相似對(duì)象的時(shí)候,會(huì)產(chǎn)生大量的重復(fù)代碼。但 js
和一般的面向?qū)ο蟮恼Z(yǔ)言不同,在 ES6 之前它沒(méi)有類的概念。但是我們可以使用函數(shù)來(lái)進(jìn)行模擬,從而產(chǎn)生出可復(fù)用的對(duì)象
創(chuàng)建方式,我了解到的方式有這么幾種:
(1)第一種是工廠模式,工廠模式的主要工作原理是用函數(shù)來(lái)封裝創(chuàng)建對(duì)象的細(xì)節(jié),從而通過(guò)調(diào)用函數(shù)來(lái)達(dá)到復(fù)用的目的。但是它有一個(gè)很大的問(wèn)題就是創(chuàng)建出來(lái)的對(duì)象無(wú)法和某個(gè)類型聯(lián)系起來(lái),它只是簡(jiǎn)單的封裝了復(fù)用代碼,而沒(méi)有建立起對(duì)象和類型間的關(guān)系。
(2)第二種是構(gòu)造函數(shù)模式。js 中每一個(gè)函數(shù)都可以作為構(gòu)造函數(shù),只要一個(gè)函數(shù)是通過(guò)?new?來(lái)調(diào)用的,那么我們就可以把它稱為構(gòu)造函數(shù)。執(zhí)行構(gòu)造函數(shù)首先會(huì)創(chuàng)建一個(gè)對(duì)象,然后將對(duì)象的原型指向構(gòu)造函數(shù)的 prototype 屬性,然后將執(zhí)行上下文中的?this?指向這個(gè)對(duì)象,最后再執(zhí)行整個(gè)函數(shù),如果返回值不是對(duì)象,則返回新建的對(duì)象。因?yàn)?this?的值指向了新建的對(duì)象,因此我們可以使用?this?給對(duì)象賦值。構(gòu)造函數(shù)模式相對(duì)于工廠模式的優(yōu)點(diǎn)是,所創(chuàng)建的對(duì)象和構(gòu)造函數(shù)建立起了聯(lián)系,因此我們可以通過(guò)原型來(lái)識(shí)別對(duì)象的類型。但是構(gòu)造函數(shù)存在一個(gè)缺點(diǎn)就是,造成了不必要的函數(shù)對(duì)象的創(chuàng)建,因?yàn)樵?js 中函數(shù)也是一個(gè)對(duì)象,因此如果對(duì)象屬性中如果包含函數(shù)的話,那么每次我們都會(huì)新建一個(gè)函數(shù)對(duì)象,浪費(fèi)了不必要的內(nèi)存空間,因?yàn)楹瘮?shù)是所有的實(shí)例都可以通用的。
(3)第三種模式是原型模式,因?yàn)槊恳粋€(gè)函數(shù)都有一個(gè) prototype 屬性,這個(gè)屬性是一個(gè)對(duì)象,它包含了通過(guò)構(gòu)造函數(shù)創(chuàng)建的所有實(shí)例都能共享的屬性和方法。因此我們可以使用原型對(duì)象來(lái)添加公用屬性和方法,從而實(shí)現(xiàn)代碼的復(fù)用。這種方式相對(duì)于構(gòu)造函數(shù)模式來(lái)說(shuō),解決了函數(shù)對(duì)象的復(fù)用問(wèn)題。但是這種模式也存在一些問(wèn)題,一個(gè)是沒(méi)有辦法通過(guò)傳入?yún)?shù)來(lái)初始化值,另一個(gè)是如果存在一個(gè)引用類型如?Array?這樣的值,那么所有的實(shí)例將共享一個(gè)對(duì)象,一個(gè)實(shí)例對(duì)引用類型值的改變會(huì)影響所有的實(shí)例。
(4)第四種模式是組合使用構(gòu)造函數(shù)模式和原型模式,這是創(chuàng)建自定義類型的最常見(jiàn)方式。因?yàn)闃?gòu)造函數(shù)模式和原型模式分開使用都存在一些問(wèn)題,因此我們可以組合使用這兩種模式,通過(guò)構(gòu)造函數(shù)來(lái)初始化對(duì)象的屬性,通過(guò)原型對(duì)象來(lái)實(shí)現(xiàn)函數(shù)方法的復(fù)用。這種方法很好的解決了兩種模式單獨(dú)使用時(shí)的缺點(diǎn),但是有一點(diǎn)不足的就是,因?yàn)槭褂昧藘煞N不同的模式,所以對(duì)于代碼的封裝性不夠好。
(5)第五種模式是動(dòng)態(tài)原型模式,這一種模式將原型方法賦值的創(chuàng)建過(guò)程移動(dòng)到了構(gòu)造函數(shù)的內(nèi)部,通過(guò)對(duì)屬性是否存在的判斷,可以實(shí)現(xiàn)僅在第一次調(diào)用函數(shù)時(shí)對(duì)原型對(duì)象賦值一次的效果。這一種方式很好地對(duì)上面的混合模式進(jìn)行了封裝。
(6)第六種模式是寄生構(gòu)造函數(shù)模式,這一種模式和工廠模式的實(shí)現(xiàn)基本相同,我對(duì)這個(gè)模式的理解是,它主要是基于一個(gè)已有的類型,在實(shí)例化時(shí)對(duì)實(shí)例化的對(duì)象進(jìn)行擴(kuò)展。這樣既不用修改原來(lái)的構(gòu)造函數(shù),也達(dá)到了擴(kuò)展對(duì)象的目的。它的一個(gè)缺點(diǎn)和工廠模式一樣,無(wú)法實(shí)現(xiàn)對(duì)象的識(shí)別。
嗯我目前了解到的就是這么幾種方式。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之對(duì)象創(chuàng)建》[17]
35. JavaScript 繼承的幾種實(shí)現(xiàn)方式?
我了解的 js 中實(shí)現(xiàn)繼承的幾種方式有:
(1)第一種是以原型鏈的方式來(lái)實(shí)現(xiàn)繼承,但是這種實(shí)現(xiàn)方式存在的缺點(diǎn)是,在包含有引用類型的數(shù)據(jù)時(shí),會(huì)被所有的實(shí)例對(duì)象所共享,容易造成修改的混亂。還有就是在創(chuàng)建子類型的時(shí)候不能向超類型傳遞參數(shù)。
(2)第二種方式是使用借用構(gòu)造函數(shù)的方式,這種方式是通過(guò)在子類型的函數(shù)中調(diào)用超類型的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的,這一種方法解決了不能向超類型傳遞參數(shù)的缺點(diǎn),但是它存在的一個(gè)問(wèn)題就是無(wú)法實(shí)現(xiàn)函數(shù)方法的復(fù)用,并且超類型原型定義的方法子類型也沒(méi)有辦法訪問(wèn)到。
(3)第三種方式是組合繼承,組合繼承是將原型鏈和借用構(gòu)造函數(shù)組合起來(lái)使用的一種方式。通過(guò)借用構(gòu)造函數(shù)的方式來(lái)實(shí)現(xiàn)類型的屬性的繼承,通過(guò)將子類型的原型設(shè)置為超類型的實(shí)例來(lái)實(shí)現(xiàn)方法的繼承。這種方式解決了上面的兩種模式單獨(dú)使用時(shí)的問(wèn)題,但是由于我們是以超類型的實(shí)例來(lái)作為子類型的原型,所以調(diào)用了兩次超類的構(gòu)造函數(shù),造成了子類型的原型中多了很多不必要的屬性。
(4)第四種方式是原型式繼承,原型式繼承的主要思路就是基于已有的對(duì)象來(lái)創(chuàng)建新的對(duì)象,實(shí)現(xiàn)的原理是,向函數(shù)中傳入一個(gè)對(duì)象,然后返回一個(gè)以這個(gè)對(duì)象為原型的對(duì)象。這種繼承的思路主要不是為了實(shí)現(xiàn)創(chuàng)造一種新的類型,只是對(duì)某個(gè)對(duì)象實(shí)現(xiàn)一種簡(jiǎn)單繼承,ES5 中定義的?Object.create()?方法就是原型式繼承的實(shí)現(xiàn)。缺點(diǎn)與原型鏈方式相同。
(5)第五種方式是寄生式繼承,寄生式繼承的思路是創(chuàng)建一個(gè)用于封裝繼承過(guò)程的函數(shù),通過(guò)傳入一個(gè)對(duì)象,然后復(fù)制一個(gè)對(duì)象的副本,然后對(duì)象進(jìn)行擴(kuò)展,最后返回這個(gè)對(duì)象。這個(gè)擴(kuò)展的過(guò)程就可以理解是一種繼承。這種繼承的優(yōu)點(diǎn)就是對(duì)一個(gè)簡(jiǎn)單對(duì)象實(shí)現(xiàn)繼承,如果這個(gè)對(duì)象不是我們的自定義類型時(shí)。缺點(diǎn)是沒(méi)有辦法實(shí)現(xiàn)函數(shù)的復(fù)用。
(6)第六種方式是寄生式組合繼承,組合繼承的缺點(diǎn)就是使用超類型的實(shí)例做為子類型的原型,導(dǎo)致添加了不必要的原型屬性。寄生式組合繼承的方式是使用超類型的原型的副本來(lái)作為子類型的原型,這樣就避免了創(chuàng)建不必要的屬性。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之繼承》[18]
36. 寄生式組合繼承的實(shí)現(xiàn)?
function?Person(name)?{
??this.name?=?name;
}
Person.prototype.sayName?=?function()?{
??console.log("My?name?is?"?+?this.name?+?".");
};
function?Student(name,?grade)?{
??Person.call(this,?name);
??this.grade?=?grade;
}
Student.prototype?=?Object.create(Person.prototype);
Student.prototype.constructor?=?Student;
Student.prototype.sayMyGrade?=?function()?{
??console.log("My?grade?is?"?+?this.grade?+?".");
};
復(fù)制代碼
37. Javascript 的作用域鏈?
作用域鏈的作用是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn),通過(guò)作用域鏈,我們可以訪問(wèn)到外層環(huán)境的變量和
函數(shù)。
作用域鏈的本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表。變量對(duì)象是一個(gè)包含了執(zhí)行環(huán)境中所有變量和函數(shù)的對(duì)象。作用域鏈的前
端始終都是當(dāng)前執(zhí)行上下文的變量對(duì)象。全局執(zhí)行上下文的變量對(duì)象(也就是全局對(duì)象)始終是作用域鏈的最后一個(gè)對(duì)象。
當(dāng)我們查找一個(gè)變量時(shí),如果當(dāng)前執(zhí)行環(huán)境中沒(méi)有找到,我們可以沿著作用域鏈向后查找。
作用域鏈的創(chuàng)建過(guò)程跟執(zhí)行上下文的建立有關(guān)....
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之作用域鏈》[19]
38. 談?wù)?This 對(duì)象的理解。
this?是執(zhí)行上下文中的一個(gè)屬性,它指向最后一次調(diào)用這個(gè)方法的對(duì)象。在實(shí)際開發(fā)中,this?的指向可以通過(guò)四種調(diào)用模
式來(lái)判斷。
復(fù)制代碼
1.第一種是函數(shù)調(diào)用模式,當(dāng)一個(gè)函數(shù)不是一個(gè)對(duì)象的屬性時(shí),直接作為函數(shù)來(lái)調(diào)用時(shí),this 指向全局對(duì)象。
2.第二種是方法調(diào)用模式,如果一個(gè)函數(shù)作為一個(gè)對(duì)象的方法來(lái)調(diào)用時(shí),this 指向這個(gè)對(duì)象。
3.第三種是構(gòu)造器調(diào)用模式,如果一個(gè)函數(shù)用 new 調(diào)用時(shí),函數(shù)執(zhí)行前會(huì)新創(chuàng)建一個(gè)對(duì)象,this 指向這個(gè)新創(chuàng)建的對(duì)象。
4.第四種是 apply 、 call 和 bind 調(diào)用模式,這三個(gè)方法都可以顯示的指定調(diào)用函數(shù)的 this 指向。其中 apply 方法接收兩個(gè)參數(shù):一個(gè)是 this 綁定的對(duì)象,一個(gè)是參數(shù)數(shù)組。call 方法接收的參數(shù),第一個(gè)是 this 綁定的對(duì)象,后面的其余參數(shù)是傳入函數(shù)執(zhí)行的參數(shù)。也就是說(shuō),在使用 call() 方法時(shí),傳遞給函數(shù)的參數(shù)必須逐個(gè)列舉出來(lái)。bind 方法通過(guò)傳入一個(gè)對(duì)象,返回一個(gè) this 綁定了傳入對(duì)象的新函數(shù)。這個(gè)函數(shù)的 this 指向除了使用 new 時(shí)會(huì)被改變,其他情況下都不會(huì)改變。
這四種方式,使用構(gòu)造器調(diào)用模式的優(yōu)先級(jí)最高,然后是?apply?、?call?和?bind?調(diào)用模式,然后是方法調(diào)用模式,然后
是函數(shù)調(diào)用模式。
復(fù)制代碼
《JavaScript 深入理解之 this 詳解》[20]
39. eval 是做什么的?
它的功能是把對(duì)應(yīng)的字符串解析成 JS 代碼并運(yùn)行。
應(yīng)該避免使用?eval,不安全,非常耗性能(2次,一次解析成 js 語(yǔ)句,一次執(zhí)行)。
復(fù)制代碼
詳細(xì)資料可以參考:《eval()》[21]
40. 什么是 DOM 和 BOM?
DOM 指的是文檔對(duì)象模型,它指的是把文檔當(dāng)做一個(gè)對(duì)象來(lái)對(duì)待,這個(gè)對(duì)象主要定義了處理網(wǎng)頁(yè)內(nèi)容的方法和接口。
BOM 指的是瀏覽器對(duì)象模型,它指的是把瀏覽器當(dāng)做一個(gè)對(duì)象來(lái)對(duì)待,這個(gè)對(duì)象主要定義了與瀏覽器進(jìn)行交互的法和接口。BOM
的核心是?window,而?window?對(duì)象具有雙重角色,它既是通過(guò)?js?訪問(wèn)瀏覽器窗口的一個(gè)接口,又是一個(gè)?Global(全局)
對(duì)象。這意味著在網(wǎng)頁(yè)中定義的任何對(duì)象,變量和函數(shù),都作為全局對(duì)象的一個(gè)屬性或者方法存在。window?對(duì)象含有?locati
on?對(duì)象、navigator?對(duì)象、screen?對(duì)象等子對(duì)象,并且?DOM?的最根本的對(duì)象?document?對(duì)象也是?BOM?的?window?對(duì)
象的子對(duì)象。
復(fù)制代碼
詳細(xì)資料可以參考:《DOM, DOCUMENT, BOM, WINDOW 有什么區(qū)別?》[22] 《Window 對(duì)象》[23] 《DOM 與 BOM 分別是什么,有何關(guān)聯(lián)?》[24] 《JavaScript 學(xué)習(xí)總結(jié)(三)BOM 和 DOM 詳解》[25]
41. 寫一個(gè)通用的事件偵聽(tīng)器函數(shù)。
const?EventUtils?=?{
??//?視能力分別使用dom0||dom2||IE方式?來(lái)綁定事件
??//?添加事件
??addEvent:?function(element,?type,?handler)?{
????if?(element.addEventListener)?{
??????element.addEventListener(type,?handler,?false);
????}?else?if?(element.attachEvent)?{
??????element.attachEvent("on"?+?type,?handler);
????}?else?{
??????element["on"?+?type]?=?handler;
????}
??},
??//?移除事件
??removeEvent:?function(element,?type,?handler)?{
????if?(element.removeEventListener)?{
??????element.removeEventListener(type,?handler,?false);
????}?else?if?(element.detachEvent)?{
??????element.detachEvent("on"?+?type,?handler);
????}?else?{
??????element["on"?+?type]?=?null;
????}
??},
??//?獲取事件目標(biāo)
??getTarget:?function(event)?{
????return?event.target?||?event.srcElement;
??},
??//?獲取?event?對(duì)象的引用,取到事件的所有信息,確保隨時(shí)能使用?event
??getEvent:?function(event)?{
????return?event?||?window.event;
??},
??//?阻止事件(主要是事件冒泡,因?yàn)?IE?不支持事件捕獲)
??stopPropagation:?function(event)?{
????if?(event.stopPropagation)?{
??????event.stopPropagation();
????}?else?{
??????event.cancelBubble?=?true;
????}
??},
??//?取消事件的默認(rèn)行為
??preventDefault:?function(event)?{
????if?(event.preventDefault)?{
??????event.preventDefault();
????}?else?{
??????event.returnValue?=?false;
????}
??}
};
復(fù)制代碼
詳細(xì)資料可以參考:《JS 事件模型》[26]
42. 事件是什么?IE 與火狐的事件機(jī)制有什么區(qū)別?如何阻止冒泡?
1.事件是用戶操作網(wǎng)頁(yè)時(shí)發(fā)生的交互動(dòng)作,比如 click/move, 事件除了用戶觸發(fā)的動(dòng)作外,還可以是文檔加載,窗口滾動(dòng)和大小調(diào)整。事件被封裝成一個(gè) event 對(duì)象,包含了該事件發(fā)生時(shí)的所有相關(guān)信息( event 的屬性)以及可以對(duì)事件進(jìn)行的操作( event 的方法)。
2.事件處理機(jī)制:IE 支持事件冒泡、Firefox 同時(shí)支持兩種事件模型,也就是:事件冒泡和事件捕獲。
3.event.stopPropagation() 或者 ie 下的方法 event.cancelBubble = true;
詳細(xì)資料可以參考:《Javascript 事件模型系列(一)事件及事件的三種模型》[27] 《Javascript 事件模型:事件捕獲和事件冒泡》[28]
43. 三種事件模型是什么?
事件是用戶操作網(wǎng)頁(yè)時(shí)發(fā)生的交互動(dòng)作或者網(wǎng)頁(yè)本身的一些操作,現(xiàn)代瀏覽器一共有三種事件模型。
第一種事件模型是最早的?DOM0?級(jí)模型,這種模型不會(huì)傳播,所以沒(méi)有事件流的概念,但是現(xiàn)在有的瀏覽器支持以冒泡的方式實(shí)
現(xiàn),它可以在網(wǎng)頁(yè)中直接定義監(jiān)聽(tīng)函數(shù),也可以通過(guò) js 屬性來(lái)指定監(jiān)聽(tīng)函數(shù)。這種方式是所有瀏覽器都兼容的。
第二種事件模型是 IE 事件模型,在該事件模型中,一次事件共有兩個(gè)過(guò)程,事件處理階段,和事件冒泡階段。事件處理階段會(huì)首先執(zhí)行目標(biāo)元素綁定的監(jiān)聽(tīng)事件。然后是事件冒泡階段,冒泡指的是事件從目標(biāo)元素冒泡到?document,依次檢查經(jīng)過(guò)的節(jié)點(diǎn)是否綁定了事件監(jiān)聽(tīng)函數(shù),如果有則執(zhí)行。這種模型通過(guò) attachEvent 來(lái)添加監(jiān)聽(tīng)函數(shù),可以添加多個(gè)監(jiān)聽(tīng)函數(shù),會(huì)按順序依次執(zhí)行。
第三種是 DOM2 級(jí)事件模型,在該事件模型中,一次事件共有三個(gè)過(guò)程,第一個(gè)過(guò)程是事件捕獲階段。捕獲指的是事件從?document?一直向下傳播到目標(biāo)元素,依次檢查經(jīng)過(guò)的節(jié)點(diǎn)是否綁定了事件監(jiān)聽(tīng)函數(shù),如果有則執(zhí)行。后面兩個(gè)階段和 IE 事件模型的兩個(gè)階段相同。這種事件模型,事件綁定的函數(shù)是 addEventListener,其中第三個(gè)參數(shù)可以指定事件是否在捕獲階段執(zhí)行。
復(fù)制代碼
詳細(xì)資料可以參考:《一個(gè) DOM 元素綁定多個(gè)事件時(shí),先執(zhí)行冒泡還是捕獲》[29]
44. 事件委托是什么?
事件委托本質(zhì)上是利用了瀏覽器事件冒泡的機(jī)制。因?yàn)槭录诿芭葸^(guò)程中會(huì)上傳到父節(jié)點(diǎn),并且父節(jié)點(diǎn)可以通過(guò)事件對(duì)象獲取到
目標(biāo)節(jié)點(diǎn),因此可以把子節(jié)點(diǎn)的監(jiān)聽(tīng)函數(shù)定義在父節(jié)點(diǎn)上,由父節(jié)點(diǎn)的監(jiān)聽(tīng)函數(shù)統(tǒng)一處理多個(gè)子元素的事件,這種方式稱為事件代理。
使用事件代理我們可以不必要為每一個(gè)子元素都綁定一個(gè)監(jiān)聽(tīng)事件,這樣減少了內(nèi)存上的消耗。并且使用事件代理我們還可以實(shí)現(xiàn)事件的動(dòng)態(tài)綁定,比如說(shuō)新增了一個(gè)子節(jié)點(diǎn),我們并不需要單獨(dú)地為它添加一個(gè)監(jiān)聽(tīng)事件,它所發(fā)生的事件會(huì)交給父元素中的監(jiān)聽(tīng)函數(shù)來(lái)處理。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 事件委托詳解》[30]
45. ["1", "2", "3"].map(parseInt) 答案是多少?
parseInt()?函數(shù)能解析一個(gè)字符串,并返回一個(gè)整數(shù),需要兩個(gè)參數(shù)?(val, radix),其中 radix 表示要解析的數(shù)字的基數(shù)。(該值介于?2?~?36?之間,并且字符串中的數(shù)字不能大于 radix 才能正確返回?cái)?shù)字結(jié)果值)。
此處?map?傳了?3?個(gè)參數(shù)?(element,?index,?array),默認(rèn)第三個(gè)參數(shù)被忽略掉,因此三次傳入的參數(shù)分別為?"1-0",?"2-1",?"3-2"
因?yàn)樽址闹挡荒艽笥诨鶖?shù),因此后面兩次調(diào)用均失敗,返回?NaN?,第一次基數(shù)為?0?,按十進(jìn)制解析返回?1。
復(fù)制代碼
詳細(xì)資料可以參考:《為什么 \["1", "2", "3"\].map(parseInt) 返回 \[1,NaN,NaN\]?》[31]
46. 什么是閉包,為什么要用它?
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù),創(chuàng)建閉包的最常見(jiàn)的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建另一個(gè)函數(shù),創(chuàng)建的函數(shù)可以
訪問(wèn)到當(dāng)前函數(shù)的局部變量。
閉包有兩個(gè)常用的用途。
閉包的第一個(gè)用途是使我們?cè)诤瘮?shù)外部能夠訪問(wèn)到函數(shù)內(nèi)部的變量。通過(guò)使用閉包,我們可以通過(guò)在外部調(diào)用閉包函數(shù),從而在外
部訪問(wèn)到函數(shù)內(nèi)部的變量,可以使用這種方法來(lái)創(chuàng)建私有變量。
函數(shù)的另一個(gè)用途是使已經(jīng)運(yùn)行結(jié)束的函數(shù)上下文中的變量對(duì)象繼續(xù)留在內(nèi)存中,因?yàn)殚]包函數(shù)保留了這個(gè)變量對(duì)象的引用,所以
這個(gè)變量對(duì)象不會(huì)被回收。
其實(shí)閉包的本質(zhì)就是作用域鏈的一個(gè)特殊的應(yīng)用,只要了解了作用域鏈的創(chuàng)建過(guò)程,就能夠理解閉包的實(shí)現(xiàn)原理。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之閉包》[32]
47. javascript 代碼中的 "use strict"; 是什么意思 ? 使用它區(qū)別是什么?
相關(guān)知識(shí)點(diǎn):
use strict 是一種 ECMAscript5 添加的(嚴(yán)格)運(yùn)行模式,這種模式使得 Javascript 在更嚴(yán)格的條件下運(yùn)行。
設(shè)立"嚴(yán)格模式"的目的,主要有以下幾個(gè):
復(fù)制代碼
消除 Javascript 語(yǔ)法的一些不合理、不嚴(yán)謹(jǐn)之處,減少一些怪異行為; 消除代碼運(yùn)行的一些不安全之處,保證代碼運(yùn)行的安全; 提高編譯器效率,增加運(yùn)行速度; 為未來(lái)新版本的 Javascript 做好鋪墊。
區(qū)別:
1.禁止使用 with 語(yǔ)句。 2.禁止 this 關(guān)鍵字指向全局對(duì)象。 3.對(duì)象不能有重名的屬性。
回答:
use strict 指的是嚴(yán)格運(yùn)行模式,在這種模式對(duì) js 的使用添加了一些限制。比如說(shuō)禁止?this?指向全局對(duì)象,還有禁止使
用?with?語(yǔ)句等。設(shè)立嚴(yán)格模式的目的,主要是為了消除代碼使用中的一些不安全的使用方式,也是為了消除 js 語(yǔ)法本身的一
些不合理的地方,以此來(lái)減少一些運(yùn)行時(shí)的怪異的行為。同時(shí)使用嚴(yán)格運(yùn)行模式也能夠提高編譯的效率,從而提高代碼的運(yùn)行速度。
我認(rèn)為嚴(yán)格模式代表了 js 一種更合理、更安全、更嚴(yán)謹(jǐn)?shù)陌l(fā)展方向。
復(fù)制代碼
詳細(xì)資料可以參考:《Javascript 嚴(yán)格模式詳解》[33]
48. 如何判斷一個(gè)對(duì)象是否屬于某個(gè)類?
第一種方式是使用?instanceof?運(yùn)算符來(lái)判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對(duì)象的原型鏈中的任何位置。
第二種方式可以通過(guò)對(duì)象的?constructor?屬性來(lái)判斷,對(duì)象的?constructor?屬性指向該對(duì)象的構(gòu)造函數(shù),但是這種方式不是很安全,因?yàn)?constructor?屬性可以被改寫。
第三種方式,如果需要判斷的是某個(gè)內(nèi)置的引用類型的話,可以使用?Object.prototype.toString()?方法來(lái)打印對(duì)象的
[[Class]]?屬性來(lái)進(jìn)行判斷。
復(fù)制代碼
詳細(xì)資料可以參考:《js 判斷一個(gè)對(duì)象是否屬于某一類》[34]
49. instanceof 的作用?
// instanceof 運(yùn)算符用于判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對(duì)象的原型鏈中的任何位置。
//?實(shí)現(xiàn):
function?myInstanceof(left,?right)?{
??let?proto?=?Object.getPrototypeOf(left),?//?獲取對(duì)象的原型
????prototype?=?right.prototype;?//?獲取構(gòu)造函數(shù)的?prototype?對(duì)象
??//?判斷構(gòu)造函數(shù)的?prototype?對(duì)象是否在對(duì)象的原型鏈上
??while?(true)?{
????if?(!proto)?return?false;
????if?(proto?===?prototype)?return?true;
????proto?=?Object.getPrototypeOf(proto);
??}
}
復(fù)制代碼
詳細(xì)資料可以參考:《instanceof》[35]
50. new 操作符具體干了什么呢?如何實(shí)現(xiàn)?
//?(1)首先創(chuàng)建了一個(gè)新的空對(duì)象
//?(2)設(shè)置原型,將對(duì)象的原型設(shè)置為函數(shù)的 prototype 對(duì)象。
//?(3)讓函數(shù)的?this?指向這個(gè)對(duì)象,執(zhí)行構(gòu)造函數(shù)的代碼(為這個(gè)新對(duì)象添加屬性)
//?(4)判斷函數(shù)的返回值類型,如果是值類型,返回創(chuàng)建的對(duì)象。如果是引用類型,就返回這個(gè)引用類型的對(duì)象。
//?實(shí)現(xiàn):
function?objectFactory()?{
??let?newObject?=?null,
????constructor?=?Array.prototype.shift.call(arguments),
????result?=?null;
??//?參數(shù)判斷
??if?(typeof?constructor?!==?"function")?{
????console.error("type?error");
????return;
??}
??//?新建一個(gè)空對(duì)象,對(duì)象的原型為構(gòu)造函數(shù)的?prototype?對(duì)象
??newObject?=?Object.create(constructor.prototype);
??//?將?this?指向新建對(duì)象,并執(zhí)行函數(shù)
??result?=?constructor.apply(newObject,?arguments);
??//?判斷返回對(duì)象
??let?flag?=
????result?&&?(typeof?result?===?"object"?||?typeof?result?===?"function");
??//?判斷返回結(jié)果
??return?flag???result?:?newObject;
}
//?使用方法
//?objectFactory(構(gòu)造函數(shù),?初始化參數(shù));
復(fù)制代碼
詳細(xì)資料可以參考:《new 操作符具體干了什么?》[36] 《JavaScript 深入之 new 的模擬實(shí)現(xiàn)》[37]
51. Javascript 中,有一個(gè)函數(shù),執(zhí)行時(shí)對(duì)象查找時(shí),永遠(yuǎn)不會(huì)去查找原型,這個(gè)函數(shù)是?
hasOwnProperty
所有繼承了?Object?的對(duì)象都會(huì)繼承到 hasOwnProperty 方法。這個(gè)方法可以用來(lái)檢測(cè)一個(gè)對(duì)象是否含有特定的自身屬性,和
in?運(yùn)算符不同,該方法會(huì)忽略掉那些從原型鏈上繼承到的屬性。
復(fù)制代碼
詳細(xì)資料可以參考:《Object.prototype.hasOwnProperty()》[38]
52. 對(duì)于 JSON 的了解?
相關(guān)知識(shí)點(diǎn):
JSON?是一種數(shù)據(jù)交換格式,基于文本,優(yōu)于輕量,用于交換數(shù)據(jù)。
JSON?可以表示數(shù)字、布爾值、字符串、null、數(shù)組(值的有序序列),以及由這些值(或數(shù)組、對(duì)象)所組成的對(duì)象(字符串與
值的映射)。
JSON?使用?JavaScript?語(yǔ)法,但是?JSON?格式僅僅是一個(gè)文本。文本可以被任何編程語(yǔ)言讀取及作為數(shù)據(jù)格式傳遞。
復(fù)制代碼
回答:
JSON?是一種基于文本的輕量級(jí)的數(shù)據(jù)交換格式。它可以被任何的編程語(yǔ)言讀取和作為數(shù)據(jù)格式來(lái)傳遞。
在項(xiàng)目開發(fā)中,我們使用?JSON?作為前后端數(shù)據(jù)交換的方式。在前端我們通過(guò)將一個(gè)符合?JSON?格式的數(shù)據(jù)結(jié)構(gòu)序列化為?JSON?字符串,然后將它傳遞到后端,后端通過(guò)?JSON?格式的字符串解析后生成對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),以此來(lái)實(shí)現(xiàn)前后端數(shù)據(jù)的一個(gè)傳遞。
因?yàn)?JSON?的語(yǔ)法是基于?js?的,因此很容易將?JSON?和?js?中的對(duì)象弄混,但是我們應(yīng)該注意的是?JSON?和?js?中的對(duì)象不是一回事,JSON?中對(duì)象格式更加嚴(yán)格,比如說(shuō)在?JSON?中屬性值不能為函數(shù),不能出現(xiàn)?NaN?這樣的屬性值等,因此大多數(shù)的?js?對(duì)象是不符合?JSON?對(duì)象的格式的。
在?js?中提供了兩個(gè)函數(shù)來(lái)實(shí)現(xiàn)?js?數(shù)據(jù)結(jié)構(gòu)和?JSON?格式的轉(zhuǎn)換處理,一個(gè)是?JSON.stringify?函數(shù),通過(guò)傳入一個(gè)符合?JSON?格式的數(shù)據(jù)結(jié)構(gòu),將其轉(zhuǎn)換為一個(gè)?JSON?字符串。如果傳入的數(shù)據(jù)結(jié)構(gòu)不符合?JSON?格式,那么在序列化的時(shí)候會(huì)對(duì)這些值進(jìn)行對(duì)應(yīng)的特殊處理,使其符合規(guī)范。在前端向后端發(fā)送數(shù)據(jù)時(shí),我們可以調(diào)用這個(gè)函數(shù)將數(shù)據(jù)對(duì)象轉(zhuǎn)化為?JSON?格式的字符串。
另一個(gè)函數(shù)?JSON.parse()?函數(shù),這個(gè)函數(shù)用來(lái)將?JSON?格式的字符串轉(zhuǎn)換為一個(gè)?js?數(shù)據(jù)結(jié)構(gòu),如果傳入的字符串不是標(biāo)準(zhǔn)的?JSON?格式的字符串的話,將會(huì)拋出錯(cuò)誤。當(dāng)我們從后端接收到?JSON?格式的字符串時(shí),我們可以通過(guò)這個(gè)方法來(lái)將其解析為一個(gè) js 數(shù)據(jù)結(jié)構(gòu),以此來(lái)進(jìn)行數(shù)據(jù)的訪問(wèn)。
復(fù)制代碼
詳細(xì)資料可以參考:《深入了解 JavaScript 中的 JSON 》[39]
53. [].forEach.call($$(""),function(a){a.style.outline="1px solid #"+(~~(Math.random()(1<<24))).toString(16)}) 能解釋一下這段代碼的意思嗎?
(1)選取頁(yè)面所有 DOM 元素。在瀏覽器的控制臺(tái)中可以使用$$()方法來(lái)獲取頁(yè)面中相應(yīng)的元素,這是現(xiàn)代瀏覽器提供的一個(gè)命令行 API 相當(dāng)于?document.querySelectorAll 方法。
(2)循環(huán)遍歷?DOM?元素
(3)給元素添加 outline 。由于渲染的 outline 是不在 CSS 盒模型中的,所以為元素添加 outline 并不會(huì)影響元素的大小和頁(yè)面的布局。
(4)生成隨機(jī)顏色函數(shù)。Math.random()*(1<<24)?可以得到?0~2^24?-?1?之間的隨機(jī)數(shù),因?yàn)榈玫降氖且粋€(gè)浮點(diǎn)數(shù),但我們只需要整數(shù)部分,使用取反操作符?~?連續(xù)兩次取反獲得整數(shù)部分,然后再用?toString(16)?的方式,轉(zhuǎn)換為一個(gè)十六進(jìn)制的字符串。
復(fù)制代碼
詳細(xì)資料可以參考:《通過(guò)一行代碼學(xué) JavaScript》[40]
54. js 延遲加載的方式有哪些?
相關(guān)知識(shí)點(diǎn):
js 延遲加載,也就是等頁(yè)面加載完成之后再加載 JavaScript 文件。js 延遲加載有助于提高頁(yè)面加載速度。
復(fù)制代碼
一般有以下幾種方式:
defer 屬性 async 屬性 動(dòng)態(tài)創(chuàng)建 DOM 方式 使用 setTimeout 延遲方法 讓 JS 最后加載
回答:
js 的加載、解析和執(zhí)行會(huì)阻塞頁(yè)面的渲染過(guò)程,因此我們希望 js 腳本能夠盡可能的延遲加載,提高頁(yè)面的渲染速度。
我了解到的幾種方式是:
第一種方式是我們一般采用的是將 js 腳本放在文檔的底部,來(lái)使 js 腳本盡可能的在最后來(lái)加載執(zhí)行。
第二種方式是給 js 腳本添加 defer 屬性,這個(gè)屬性會(huì)讓腳本的加載與文檔的解析同步解析,然后在文檔解析完成后再執(zhí)行這個(gè)腳本文件,這樣的話就能使頁(yè)面的渲染不被阻塞。多個(gè)設(shè)置了 defer 屬性的腳本按規(guī)范來(lái)說(shuō)最后是順序執(zhí)行的,但是在一些瀏覽器中可能不是這樣。
第三種方式是給?js?腳本添加?async?屬性,這個(gè)屬性會(huì)使腳本異步加載,不會(huì)阻塞頁(yè)面的解析過(guò)程,但是當(dāng)腳本加載完成后立即執(zhí)行 js 腳本,這個(gè)時(shí)候如果文檔沒(méi)有解析完成的話同樣會(huì)阻塞。多個(gè)?async?屬性的腳本的執(zhí)行順序是不可預(yù)測(cè)的,一般不會(huì)按照代碼的順序依次執(zhí)行。
第四種方式是動(dòng)態(tài)創(chuàng)建 DOM 標(biāo)簽的方式,我們可以對(duì)文檔的加載事件進(jìn)行監(jiān)聽(tīng),當(dāng)文檔加載完成后再動(dòng)態(tài)的創(chuàng)建 script 標(biāo)簽來(lái)引入 js 腳本。
復(fù)制代碼
55. Ajax 是什么? 如何創(chuàng)建一個(gè) Ajax?
相關(guān)知識(shí)點(diǎn):
2005 年 2 月,AJAX 這個(gè)詞第一次正式提出,它是 Asynchronous JavaScript and XML 的縮寫,指的是通過(guò) JavaScript 的 異步通信,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當(dāng)前網(wǎng)頁(yè)的對(duì)應(yīng)部分,而不用刷新整個(gè)網(wǎng)頁(yè)。
具體來(lái)說(shuō),AJAX 包括以下幾個(gè)步驟。
1.創(chuàng)建 XMLHttpRequest 對(duì)象,也就是創(chuàng)建一個(gè)異步調(diào)用對(duì)象 2.創(chuàng)建一個(gè)新的 HTTP 請(qǐng)求,并指定該 HTTP 請(qǐng)求的方法、URL 及驗(yàn)證信息 3.設(shè)置響應(yīng) HTTP 請(qǐng)求狀態(tài)變化的函數(shù) 4.發(fā)送 HTTP 請(qǐng)求 5.獲取異步調(diào)用返回的數(shù)據(jù) 6.使用 JavaScript 和 DOM 實(shí)現(xiàn)局部刷新
一般實(shí)現(xiàn):
const?SERVER_URL?=?"/server";
let?xhr?=?new?XMLHttpRequest();
//?創(chuàng)建?Http?請(qǐng)求
xhr.open("GET",?SERVER_URL,?true);
//?設(shè)置狀態(tài)監(jiān)聽(tīng)函數(shù)
xhr.onreadystatechange?=?function()?{
??if?(this.readyState?!==?4)?return;
??//?當(dāng)請(qǐng)求成功時(shí)
??if?(this.status?===?200)?{
????handle(this.response);
??}?else?{
????console.error(this.statusText);
??}
};
//?設(shè)置請(qǐng)求失敗時(shí)的監(jiān)聽(tīng)函數(shù)
xhr.onerror?=?function()?{
??console.error(this.statusText);
};
//?設(shè)置請(qǐng)求頭信息
xhr.responseType?=?"json";
xhr.setRequestHeader("Accept",?"application/json");
//?發(fā)送?Http?請(qǐng)求
xhr.send(null);
// promise 封裝實(shí)現(xiàn):
function?getJSON(url)?{
??//?創(chuàng)建一個(gè)?promise?對(duì)象
??let?promise?=?new?Promise(function(resolve,?reject)?{
????let?xhr?=?new?XMLHttpRequest();
????//?新建一個(gè)?http?請(qǐng)求
????xhr.open("GET",?url,?true);
????//?設(shè)置狀態(tài)的監(jiān)聽(tīng)函數(shù)
????xhr.onreadystatechange?=?function()?{
??????if?(this.readyState?!==?4)?return;
??????//?當(dāng)請(qǐng)求成功或失敗時(shí),改變?promise?的狀態(tài)
??????if?(this.status?===?200)?{
????????resolve(this.response);
??????}?else?{
????????reject(new?Error(this.statusText));
??????}
????};
????//?設(shè)置錯(cuò)誤監(jiān)聽(tīng)函數(shù)
????xhr.onerror?=?function()?{
??????reject(new?Error(this.statusText));
????};
????//?設(shè)置響應(yīng)的數(shù)據(jù)類型
????xhr.responseType?=?"json";
????//?設(shè)置請(qǐng)求頭信息
????xhr.setRequestHeader("Accept",?"application/json");
????//?發(fā)送?http?請(qǐng)求
????xhr.send(null);
??});
??return?promise;
}
復(fù)制代碼
回答:
我對(duì) ajax 的理解是,它是一種異步通信的方法,通過(guò)直接由 js 腳本向服務(wù)器發(fā)起 http 通信,然后根據(jù)服務(wù)器返回的數(shù)據(jù),更新網(wǎng)頁(yè)的相應(yīng)部分,而不用刷新整個(gè)頁(yè)面的一種方法。
創(chuàng)建一個(gè)?ajax?有這樣幾個(gè)步驟
首先是創(chuàng)建一個(gè) XMLHttpRequest 對(duì)象。
然后在這個(gè)對(duì)象上使用 open 方法創(chuàng)建一個(gè) http 請(qǐng)求,open 方法所需要的參數(shù)是請(qǐng)求的方法、請(qǐng)求的地址、是否異步和用戶的認(rèn)證信息。
在發(fā)起請(qǐng)求前,我們可以為這個(gè)對(duì)象添加一些信息和監(jiān)聽(tīng)函數(shù)。比如說(shuō)我們可以通過(guò) setRequestHeader 方法來(lái)為請(qǐng)求添加頭信息。我們還可以為這個(gè)對(duì)象添加一個(gè)狀態(tài)監(jiān)聽(tīng)函數(shù)。一個(gè) XMLHttpRequest 對(duì)象一共有?5?個(gè)狀態(tài),當(dāng)它的狀態(tài)變化時(shí)會(huì)觸發(fā)onreadystatechange 事件,我們可以通過(guò)設(shè)置監(jiān)聽(tīng)函數(shù),來(lái)處理請(qǐng)求成功后的結(jié)果。當(dāng)對(duì)象的 readyState 變?yōu)?4?的時(shí)候,代表服務(wù)器返回的數(shù)據(jù)接收完成,這個(gè)時(shí)候我們可以通過(guò)判斷請(qǐng)求的狀態(tài),如果狀態(tài)是?2xx?或者?304?的話則代表返回正常。這個(gè)時(shí)候我們就可以通過(guò) response 中的數(shù)據(jù)來(lái)對(duì)頁(yè)面進(jìn)行更新了。
當(dāng)對(duì)象的屬性和監(jiān)聽(tīng)函數(shù)設(shè)置完成后,最后我們調(diào)用 sent 方法來(lái)向服務(wù)器發(fā)起請(qǐng)求,可以傳入?yún)?shù)作為發(fā)送的數(shù)據(jù)體。
復(fù)制代碼
詳細(xì)資料可以參考:《XMLHttpRequest 對(duì)象》[41] 《從 ajax 到 fetch、axios》[42] 《Fetch 入門》[43] 《傳統(tǒng) Ajax 已死,F(xiàn)etch 永生》[44]
56. 談一談瀏覽器的緩存機(jī)制?
瀏覽器的緩存機(jī)制指的是通過(guò)在一段時(shí)間內(nèi)保留已接收到的 web 資源的一個(gè)副本,如果在資源的有效時(shí)間內(nèi),發(fā)起了對(duì)這個(gè)資源的再一次請(qǐng)求,那么瀏覽器會(huì)直接使用緩存的副本,而不是向服務(wù)器發(fā)起請(qǐng)求。使用 web 緩存可以有效地提高頁(yè)面的打開速度,減少不必要的網(wǎng)絡(luò)帶寬的消耗。
web 資源的緩存策略一般由服務(wù)器來(lái)指定,可以分為兩種,分別是強(qiáng)緩存策略和協(xié)商緩存策略。
使用強(qiáng)緩存策略時(shí),如果緩存資源有效,則直接使用緩存資源,不必再向服務(wù)器發(fā)起請(qǐng)求。強(qiáng)緩存策略可以通過(guò)兩種方式來(lái)設(shè)置,分別是 http 頭信息中的 Expires 屬性和 Cache-Control 屬性。
服務(wù)器通過(guò)在響應(yīng)頭中添加 Expires 屬性,來(lái)指定資源的過(guò)期時(shí)間。在過(guò)期時(shí)間以內(nèi),該資源可以被緩存使用,不必再向服務(wù)器發(fā)送請(qǐng)求。這個(gè)時(shí)間是一個(gè)絕對(duì)時(shí)間,它是服務(wù)器的時(shí)間,因此可能存在這樣的問(wèn)題,就是客戶端的時(shí)間和服務(wù)器端的時(shí)間不一致,或者用戶可以對(duì)客戶端時(shí)間進(jìn)行修改的情況,這樣就可能會(huì)影響緩存命中的結(jié)果。
Expires?是?http1.0?中的方式,因?yàn)樗囊恍┤秉c(diǎn),在?http?1.1?中提出了一個(gè)新的頭部屬性就是?Cache-Control?屬性,
它提供了對(duì)資源的緩存的更精確的控制。它有很多不同的值,常用的比如我們可以通過(guò)設(shè)置 max-age 來(lái)指定資源能夠被緩存的時(shí)間
的大小,這是一個(gè)相對(duì)的時(shí)間,它會(huì)根據(jù)這個(gè)時(shí)間的大小和資源第一次請(qǐng)求時(shí)的時(shí)間來(lái)計(jì)算出資源過(guò)期的時(shí)間,因此相對(duì)于?Expires
來(lái)說(shuō),這種方式更加有效一些。常用的還有比如 private ,用來(lái)規(guī)定資源只能被客戶端緩存,不能夠代理服務(wù)器所緩存。還有如 n
o-store ,用來(lái)指定資源不能夠被緩存,no-cache 代表該資源能夠被緩存,但是立即失效,每次都需要向服務(wù)器發(fā)起請(qǐng)求。
一般來(lái)說(shuō)只需要設(shè)置其中一種方式就可以實(shí)現(xiàn)強(qiáng)緩存策略,當(dāng)兩種方式一起使用時(shí),Cache-Control 的優(yōu)先級(jí)要高于 Expires 。
使用協(xié)商緩存策略時(shí),會(huì)先向服務(wù)器發(fā)送一個(gè)請(qǐng)求,如果資源沒(méi)有發(fā)生修改,則返回一個(gè)?304?狀態(tài),讓瀏覽器使用本地的緩存副本。
如果資源發(fā)生了修改,則返回修改后的資源。協(xié)商緩存也可以通過(guò)兩種方式來(lái)設(shè)置,分別是 http 頭信息中的 Etag 和 Last-Modified 屬性。
服務(wù)器通過(guò)在響應(yīng)頭中添加 Last-Modified 屬性來(lái)指出資源最后一次修改的時(shí)間,當(dāng)瀏覽器下一次發(fā)起請(qǐng)求時(shí),會(huì)在請(qǐng)求頭中添加一個(gè) If-Modified-Since 的屬性,屬性值為上一次資源返回時(shí)的 Last-Modified 的值。當(dāng)請(qǐng)求發(fā)送到服務(wù)器后服務(wù)器會(huì)通過(guò)這個(gè)屬性來(lái)和資源的最后一次的修改時(shí)間來(lái)進(jìn)行比較,以此來(lái)判斷資源是否做了修改。如果資源沒(méi)有修改,那么返回?304?狀態(tài),讓客戶端使用本地的緩存。如果資源已經(jīng)被修改了,則返回修改后的資源。使用這種方法有一個(gè)缺點(diǎn),就是 Last-Modified 標(biāo)注的最后修改時(shí)間只能精確到秒級(jí),如果某些文件在1秒鐘以內(nèi),被修改多次的話,那么文件已將改變了但是?Last-Modified?卻沒(méi)有改變,
這樣會(huì)造成緩存命中的不準(zhǔn)確。
因?yàn)?Last-Modified 的這種可能發(fā)生的不準(zhǔn)確性,http 中提供了另外一種方式,那就是 Etag 屬性。服務(wù)器在返回資源的時(shí)候,在頭信息中添加了 Etag 屬性,這個(gè)屬性是資源生成的唯一標(biāo)識(shí)符,當(dāng)資源發(fā)生改變的時(shí)候,這個(gè)值也會(huì)發(fā)生改變。在下一次資源請(qǐng)求時(shí),瀏覽器會(huì)在請(qǐng)求頭中添加一個(gè) If-None-Match 屬性,這個(gè)屬性的值就是上次返回的資源的 Etag 的值。服務(wù)接收到請(qǐng)求后會(huì)根據(jù)這個(gè)值來(lái)和資源當(dāng)前的 Etag 的值來(lái)進(jìn)行比較,以此來(lái)判斷資源是否發(fā)生改變,是否需要返回資源。通過(guò)這種方式,比 Last-Modified 的方式更加精確。
當(dāng) Last-Modified 和 Etag 屬性同時(shí)出現(xiàn)的時(shí)候,Etag 的優(yōu)先級(jí)更高。使用協(xié)商緩存的時(shí)候,服務(wù)器需要考慮負(fù)載平衡的問(wèn)題,因此多個(gè)服務(wù)器上資源的 Last-Modified 應(yīng)該保持一致,因?yàn)槊總€(gè)服務(wù)器上 Etag 的值都不一樣,因此在考慮負(fù)載平衡時(shí),最好不要設(shè)置 Etag 屬性。
強(qiáng)緩存策略和協(xié)商緩存策略在緩存命中時(shí)都會(huì)直接使用本地的緩存副本,區(qū)別只在于協(xié)商緩存會(huì)向服務(wù)器發(fā)送一次請(qǐng)求。它們緩存不命中時(shí),都會(huì)向服務(wù)器發(fā)送請(qǐng)求來(lái)獲取資源。在實(shí)際的緩存機(jī)制中,強(qiáng)緩存策略和協(xié)商緩存策略是一起合作使用的。瀏覽器首先會(huì)根據(jù)請(qǐng)求的信息判斷,強(qiáng)緩存是否命中,如果命中則直接使用資源。如果不命中則根據(jù)頭信息向服務(wù)器發(fā)起請(qǐng)求,使用協(xié)商緩存,如果協(xié)商緩存命中的話,則服務(wù)器不返回資源,瀏覽器直接使用本地資源的副本,如果協(xié)商緩存不命中,則瀏覽器返回最新的資源給瀏覽器。
復(fù)制代碼
詳細(xì)資料可以參考:《淺談瀏覽器緩存》[45] 《前端優(yōu)化:瀏覽器緩存技術(shù)介紹》[46] 《請(qǐng)求頭中的 Cache-Control》[47] 《Cache-Control 字段值詳解》[48]
57. Ajax 解決瀏覽器緩存問(wèn)題?
1.在 ajax 發(fā)送請(qǐng)求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
2.在 ajax 發(fā)送請(qǐng)求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
3.在 URL 后面加上一個(gè)隨機(jī)數(shù):"fresh=" + Math.random();。
4.在 URL 后面加上時(shí)間戳:"nowtime=" + new Date().getTime();。
5.如果是使用 jQuery,直接這樣就可以了$.ajaxSetup({cache:false})。這樣頁(yè)面的所有 ajax 都會(huì)執(zhí)行這條語(yǔ)句就是不需要保存緩存記錄。
詳細(xì)資料可以參考:《Ajax 中瀏覽器的緩存問(wèn)題解決方法》[49] 《淺談瀏覽器緩存》[50]
58. 同步和異步的區(qū)別?
相關(guān)知識(shí)點(diǎn):
同步,可以理解為在執(zhí)行完一個(gè)函數(shù)或方法之后,一直等待系統(tǒng)返回值或消息,這時(shí)程序是處于阻塞的,只有接收到返回的值或消息后才往下執(zhí)行其他的命令。??
異步,執(zhí)行完函數(shù)或方法后,不必阻塞性地等待返回值或消息,只需要向系統(tǒng)委托一個(gè)異步過(guò)程,那么當(dāng)系統(tǒng)接收到返回值或消息時(shí),系統(tǒng)會(huì)自動(dòng)觸發(fā)委托的異步過(guò)程,從而完成一個(gè)完整的流程。?
復(fù)制代碼
回答:
同步指的是當(dāng)一個(gè)進(jìn)程在執(zhí)行某個(gè)請(qǐng)求的時(shí)候,如果這個(gè)請(qǐng)求需要等待一段時(shí)間才能返回,那么這個(gè)進(jìn)程會(huì)一直等待下去,直到消息返
回為止再繼續(xù)向下執(zhí)行。
異步指的是當(dāng)一個(gè)進(jìn)程在執(zhí)行某個(gè)請(qǐng)求的時(shí)候,如果這個(gè)請(qǐng)求需要等待一段時(shí)間才能返回,這個(gè)時(shí)候進(jìn)程會(huì)繼續(xù)往下執(zhí)行,不會(huì)阻塞等
待消息的返回,當(dāng)消息返回時(shí)系統(tǒng)再通知進(jìn)程進(jìn)行處理。
復(fù)制代碼
詳細(xì)資料可以參考:《同步和異步的區(qū)別》[51]
59. 什么是瀏覽器的同源政策?
我對(duì)瀏覽器的同源政策的理解是,一個(gè)域下的 js 腳本在未經(jīng)允許的情況下,不能夠訪問(wèn)另一個(gè)域的內(nèi)容。這里的同源的指的是兩個(gè)
域的協(xié)議、域名、端口號(hào)必須相同,否則則不屬于同一個(gè)域。
同源政策主要限制了三個(gè)方面
第一個(gè)是當(dāng)前域下的 js 腳本不能夠訪問(wèn)其他域下的 cookie、localStorage 和 indexDB。
第二個(gè)是當(dāng)前域下的 js 腳本不能夠操作訪問(wèn)操作其他域下的 DOM。
第三個(gè)是當(dāng)前域下 ajax 無(wú)法發(fā)送跨域請(qǐng)求。
同源政策的目的主要是為了保證用戶的信息安全,它只是對(duì)?js?腳本的一種限制,并不是對(duì)瀏覽器的限制,對(duì)于一般的?img、或者
script 腳本請(qǐng)求都不會(huì)有跨域的限制,這是因?yàn)檫@些操作都不會(huì)通過(guò)響應(yīng)結(jié)果來(lái)進(jìn)行可能出現(xiàn)安全問(wèn)題的操作。
復(fù)制代碼
60. 如何解決跨域問(wèn)題?
相關(guān)知識(shí)點(diǎn):
1.通過(guò) jsonp 跨域 2.document.domain + iframe 跨域 3.location.hash + iframe 4.window.name + iframe 跨域 5.postMessage 跨域 6.跨域資源共享(CORS) 7.nginx 代理跨域 8.nodejs 中間件代理跨域 9.WebSocket 協(xié)議跨域
回答:
解決跨域的方法我們可以根據(jù)我們想要實(shí)現(xiàn)的目的來(lái)劃分。
首先我們?nèi)绻皇窍胍獙?shí)現(xiàn)主域名下的不同子域名的跨域操作,我們可以使用設(shè)置?document.domain 來(lái)解決。
(1)將?document.domain 設(shè)置為主域名,來(lái)實(shí)現(xiàn)相同子域名的跨域操作,這個(gè)時(shí)候主域名下的 cookie 就能夠被子域名所訪問(wèn)。同時(shí)如果文檔中含有主域名相同,子域名不同的 iframe 的話,我們也可以對(duì)這個(gè) iframe 進(jìn)行操作。
如果是想要解決不同跨域窗口間的通信問(wèn)題,比如說(shuō)一個(gè)頁(yè)面想要和頁(yè)面的中的不同源的?iframe?進(jìn)行通信的問(wèn)題,我們可以使用?location.hash?或者?window.name 或者 postMessage 來(lái)解決。
(2)使用 location.hash 的方法,我們可以在主頁(yè)面動(dòng)態(tài)的修改 iframe 窗口的 hash 值,然后在 iframe 窗口里實(shí)現(xiàn)監(jiān)聽(tīng)函數(shù)來(lái)實(shí)現(xiàn)這樣一個(gè)單向的通信。因?yàn)樵?iframe 是沒(méi)有辦法訪問(wèn)到不同源的父級(jí)窗口的,所以我們不能直接修改父級(jí)窗口的 hash 值來(lái)實(shí)現(xiàn)通信,我們可以在 iframe 中再加入一個(gè) iframe ,這個(gè) iframe 的內(nèi)容是和父級(jí)頁(yè)面同源的,所以我們可以?window.parent.parent 來(lái)修改最頂級(jí)頁(yè)面的 src,以此來(lái)實(shí)現(xiàn)雙向通信。
(3)使用?window.name?的方法,主要是基于同一個(gè)窗口中設(shè)置了?window.name?后不同源的頁(yè)面也可以訪問(wèn),所以不同源的子頁(yè)面可以首先在?window.name 中寫入數(shù)據(jù),然后跳轉(zhuǎn)到一個(gè)和父級(jí)同源的頁(yè)面。這個(gè)時(shí)候級(jí)頁(yè)面就可以訪問(wèn)同源的子頁(yè)面中?window.name 中的數(shù)據(jù)了,這種方式的好處是可以傳輸?shù)臄?shù)據(jù)量大。
(4)使用 postMessage 來(lái)解決的方法,這是一個(gè) h5 中新增的一個(gè) api。通過(guò)它我們可以實(shí)現(xiàn)多窗口間的信息傳遞,通過(guò)獲取到指定窗口的引用,然后調(diào)用 postMessage 來(lái)發(fā)送信息,在窗口中我們通過(guò)對(duì) message 信息的監(jiān)聽(tīng)來(lái)接收信息,以此來(lái)實(shí)現(xiàn)不同源間的信息交換。
如果是像解決 ajax 無(wú)法提交跨域請(qǐng)求的問(wèn)題,我們可以使用 jsonp、cors、websocket 協(xié)議、服務(wù)器代理來(lái)解決問(wèn)題。
(5)使用 jsonp 來(lái)實(shí)現(xiàn)跨域請(qǐng)求,它的主要原理是通過(guò)動(dòng)態(tài)構(gòu)建 script ?標(biāo)簽來(lái)實(shí)現(xiàn)跨域請(qǐng)求,因?yàn)闉g覽器對(duì) script 標(biāo)簽的引入沒(méi)有跨域的訪問(wèn)限制?。通過(guò)在請(qǐng)求的 url 后指定一個(gè)回調(diào)函數(shù),然后服務(wù)器在返回?cái)?shù)據(jù)的時(shí)候,構(gòu)建一個(gè) json 數(shù)據(jù)的包裝,這個(gè)包裝就是回調(diào)函數(shù),然后返回給前端,前端接收到數(shù)據(jù)后,因?yàn)檎?qǐng)求的是腳本文件,所以會(huì)直接執(zhí)行,這樣我們先前定義好的回調(diào)函數(shù)就可以被調(diào)用,從而實(shí)現(xiàn)了跨域請(qǐng)求的處理。這種方式只能用于?get?請(qǐng)求。
(6)使用 CORS 的方式,CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱是"跨域資源共享"。CORS 需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,因此我們只需要在服務(wù)器端配置就行。瀏覽器將 CORS 請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求。對(duì)于簡(jiǎn)單請(qǐng)求,瀏覽器直接發(fā)出 CORS 請(qǐng)求。具體來(lái)說(shuō),就是會(huì)在頭信息之中,增加一個(gè) Origin 字段。Origin 字段用來(lái)說(shuō)明本次請(qǐng)求來(lái)自哪個(gè)源。服務(wù)器根據(jù)這個(gè)值,決定是否同意這次請(qǐng)求。對(duì)于如果 Origin 指定的源,不在許可范圍內(nèi),服務(wù)器會(huì)返回一個(gè)正常的 HTTP 回應(yīng)。瀏覽器發(fā)現(xiàn),這個(gè)回應(yīng)的頭信息沒(méi)有包含 Access-Control-Allow-Origin 字段,就知道出錯(cuò)了,從而拋出一個(gè)錯(cuò)誤,ajax 不會(huì)收到響應(yīng)信息。如果成功的話會(huì)包含一些以 Access-Control-?開頭的字段。
非簡(jiǎn)單請(qǐng)求,瀏覽器會(huì)先發(fā)出一次預(yù)檢請(qǐng)求,來(lái)判斷該域名是否在服務(wù)器的白名單中,如果收到肯定回復(fù)后才會(huì)發(fā)起請(qǐng)求。
(7)使用 websocket 協(xié)議,這個(gè)協(xié)議沒(méi)有同源限制。
(8)使用服務(wù)器來(lái)代理跨域的訪問(wèn)請(qǐng)求,就是有跨域的請(qǐng)求操作時(shí)發(fā)送請(qǐng)求給后端,讓后端代為請(qǐng)求,然后最后將獲取的結(jié)果發(fā)返回。
復(fù)制代碼
詳細(xì)資料可以參考:《前端常見(jiàn)跨域解決方案(全)》[52] 《瀏覽器同源政策及其規(guī)避方法》[53] 《跨域,你需要知道的全在這里》[54] 《為什么 form 表單提交沒(méi)有跨域問(wèn)題,但 ajax 提交有跨域問(wèn)題?》[55]
61. 服務(wù)器代理轉(zhuǎn)發(fā)時(shí),該如何處理 cookie?
詳細(xì)資料可以參考:《深入淺出 Nginx》[56]
62. 簡(jiǎn)單談一下 cookie ?
我的理解是 cookie 是服務(wù)器提供的一種用于維護(hù)會(huì)話狀態(tài)信息的數(shù)據(jù),通過(guò)服務(wù)器發(fā)送到瀏覽器,瀏覽器保存在本地,當(dāng)下一次有同源的請(qǐng)求時(shí),將保存的 cookie 值添加到請(qǐng)求頭部,發(fā)送給服務(wù)端。這可以用來(lái)實(shí)現(xiàn)記錄用戶登錄狀態(tài)等功能。cookie 一般可以存儲(chǔ)?4k 大小的數(shù)據(jù),并且只能夠被同源的網(wǎng)頁(yè)所共享訪問(wèn)。
服務(wù)器端可以使用?Set-Cookie 的響應(yīng)頭部來(lái)配置 cookie 信息。一條cookie 包括了5個(gè)屬性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的時(shí)間,domain 是域名、path是路徑,domain 和 path 一起限制了 cookie 能夠被哪些 url 訪問(wèn)。secure 規(guī)定了 cookie 只能在確保安全的情況下傳輸,HttpOnly 規(guī)定了這個(gè) cookie 只能被服務(wù)器訪問(wèn),不能使用 js 腳本訪問(wèn)。
在發(fā)生 xhr 的跨域請(qǐng)求的時(shí)候,即使是同源下的 cookie,也不會(huì)被自動(dòng)添加到請(qǐng)求頭部,除非顯示地規(guī)定。
復(fù)制代碼
詳細(xì)資料可以參考:《HTTP cookies》[57] 《聊一聊 cookie》[58]
63. 模塊化開發(fā)怎么做?
我對(duì)模塊的理解是,一個(gè)模塊是實(shí)現(xiàn)一個(gè)特定功能的一組方法。在最開始的時(shí)候,js 只實(shí)現(xiàn)一些簡(jiǎn)單的功能,所以并沒(méi)有模塊的概念
,但隨著程序越來(lái)越復(fù)雜,代碼的模塊化開發(fā)變得越來(lái)越重要。
由于函數(shù)具有獨(dú)立作用域的特點(diǎn),最原始的寫法是使用函數(shù)來(lái)作為模塊,幾個(gè)函數(shù)作為一個(gè)模塊,但是這種方式容易造成全局變量的污
染,并且模塊間沒(méi)有聯(lián)系。
后面提出了對(duì)象寫法,通過(guò)將函數(shù)作為一個(gè)對(duì)象的方法來(lái)實(shí)現(xiàn),這樣解決了直接使用函數(shù)作為模塊的一些缺點(diǎn),但是這種辦法會(huì)暴露所
有的所有的模塊成員,外部代碼可以修改內(nèi)部屬性的值。
現(xiàn)在最常用的是立即執(zhí)行函數(shù)的寫法,通過(guò)利用閉包來(lái)實(shí)現(xiàn)模塊私有作用域的建立,同時(shí)不會(huì)對(duì)全局作用域造成污染。
復(fù)制代碼
詳細(xì)資料可以參考:《淺談模塊化開發(fā)》[59] 《Javascript 模塊化編程(一):模塊的寫法》[60] 《前端模塊化:CommonJS,AMD,CMD,ES6》[61] 《Module 的語(yǔ)法》[62]
64. js 的幾種模塊規(guī)范?
js 中現(xiàn)在比較成熟的有四種模塊加載方案。
第一種是?CommonJS?方案,它通過(guò)?require?來(lái)引入模塊,通過(guò)?module.exports 定義模塊的輸出接口。這種模塊加載方案是
服務(wù)器端的解決方案,它是以同步的方式來(lái)引入模塊的,因?yàn)樵诜?wù)端文件都存儲(chǔ)在本地磁盤,所以讀取非常快,所以以同步的方式
加載沒(méi)有問(wèn)題。但如果是在瀏覽器端,由于模塊的加載是使用網(wǎng)絡(luò)請(qǐng)求,因此使用異步加載的方式更加合適。
第二種是?AMD?方案,這種方案采用異步加載的方式來(lái)加載模塊,模塊的加載不影響后面語(yǔ)句的執(zhí)行,所有依賴這個(gè)模塊的語(yǔ)句都定
義在一個(gè)回調(diào)函數(shù)里,等到加載完成后再執(zhí)行回調(diào)函數(shù)。require.js 實(shí)現(xiàn)了 AMD 規(guī)范。
第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問(wèn)題,sea.js 實(shí)現(xiàn)了 CMD 規(guī)范。它和?require.js
的區(qū)別在于模塊定義時(shí)對(duì)依賴的處理不同和對(duì)依賴模塊的執(zhí)行時(shí)機(jī)的處理不同。參考60
第四種方案是?ES6?提出的方案,使用?import?和?export?的形式來(lái)導(dǎo)入導(dǎo)出模塊。這種方案和上面三種方案都不同。參考?61。
復(fù)制代碼
65. AMD 和 CMD 規(guī)范的區(qū)別?
它們之間的主要區(qū)別有兩個(gè)方面。
(1)第一個(gè)方面是在模塊定義時(shí)對(duì)依賴的處理不同。AMD 推崇依賴前置,在定義模塊的時(shí)候就要聲明其依賴的模塊。而 CMD 推崇 就近依賴,只有在用到某個(gè)模塊的時(shí)候再去 require。
(2)第二個(gè)方面是對(duì)依賴模塊的執(zhí)行時(shí)機(jī)處理不同。首先 AMD 和 CMD 對(duì)于模塊的加載方式都是異步加載,不過(guò)它們的區(qū)別在于 模塊的執(zhí)行時(shí)機(jī),AMD 在依賴模塊加載完成后就直接執(zhí)行依賴模塊,依賴模塊的執(zhí)行順序和我們書寫的順序不一定一致。而 CMD 在依賴模塊加載完成后并不執(zhí)行,只是下載而已,等到所有的依賴模塊都加載好后,進(jìn)入回調(diào)函數(shù)邏輯,遇到 require 語(yǔ)句 的時(shí)候才執(zhí)行對(duì)應(yīng)的模塊,這樣模塊的執(zhí)行順序就和我們書寫的順序保持一致了。
//?CMD
define(function(require,?exports,?module)?{
??var?a?=?require("./a");
??a.doSomething();
??//?此處略去?100?行
??var?b?=?require("./b");?//?依賴可以就近書寫
??b.doSomething();
??//?...
});
//?AMD?默認(rèn)推薦
define(["./a",?"./b"],?function(a,?b)?{
??//?依賴必須一開始就寫好
??a.doSomething();
??//?此處略去?100?行
??b.doSomething();
??//?...
});
復(fù)制代碼
詳細(xì)資料可以參考:《前端模塊化,AMD 與 CMD 的區(qū)別》[63]
66. ES6 模塊與 CommonJS 模塊、AMD、CMD 的差異。
1.CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用。CommonJS 模塊輸出的是值的拷貝,也就是說(shuō),一旦輸出一個(gè)值,模塊內(nèi)部的變化就影響不到這個(gè)值。ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。JS 引擎對(duì)腳本靜態(tài)分析的時(shí)候,遇到模塊加載命令 import,就會(huì)生成一個(gè)只讀引用。等到腳本真正執(zhí)行時(shí),再根據(jù)這個(gè)只讀引用,到被加載的那個(gè)模塊里面去取值。
2.CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。CommonJS 模塊就是對(duì)象,即在輸入時(shí)是先加載整個(gè)模塊,生成一個(gè)對(duì)象,然后再?gòu)倪@個(gè)對(duì)象上面讀取方法,這種加載稱為“運(yùn)行時(shí)加載”。而 ES6 模塊不是對(duì)象,它的對(duì)外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成。
67. requireJS 的核心原理是什么?(如何動(dòng)態(tài)加載的?如何避免多次加載的?如何 緩存的?)
require.js 的核心原理是通過(guò)動(dòng)態(tài)創(chuàng)建 script 腳本來(lái)異步引入模塊,然后對(duì)每個(gè)腳本的 load 事件進(jìn)行監(jiān)聽(tīng),如果每個(gè)腳本都加載完成了,再調(diào)用回調(diào)函數(shù)。
復(fù)制代碼
詳細(xì)資料可以參考:《requireJS 的用法和原理分析》[64] 《requireJS 的核心原理是什么?》[65] 《從 RequireJs 源碼剖析腳本加載原理》[66] 《requireJS 原理分析》[67]
68. JS 模塊加載器的輪子怎么造,也就是如何實(shí)現(xiàn)一個(gè)模塊加載器?
詳細(xì)資料可以參考:《JS 模塊加載器加載原理是怎么樣的?》[68]
69. ECMAScript6 怎么寫 class,為什么會(huì)出現(xiàn) class 這種東西?
在我看來(lái)?ES6?新添加的?class?只是為了補(bǔ)充?js?中缺少的一些面向?qū)ο笳Z(yǔ)言的特性,但本質(zhì)上來(lái)說(shuō)它只是一種語(yǔ)法糖,不是一個(gè)新的東西,其背后還是原型繼承的思想。通過(guò)加入?class?可以有利于我們更好的組織代碼。
在?class?中添加的方法,其實(shí)是添加在類的原型上的。
復(fù)制代碼
詳細(xì)資料可以參考:《ECMAScript 6 實(shí)現(xiàn)了 class,對(duì) JavaScript 前端開發(fā)有什么意義?》[69] 《Class 的基本語(yǔ)法》[70]
70. documen.write 和 innerHTML 的區(qū)別?
document.write 的內(nèi)容會(huì)代替整個(gè)文檔內(nèi)容,會(huì)重寫整個(gè)頁(yè)面。
innerHTML 的內(nèi)容只是替代指定元素的內(nèi)容,只會(huì)重寫頁(yè)面中的部分內(nèi)容。
復(fù)制代碼
詳細(xì)資料可以參考:《簡(jiǎn)述 document.write 和 innerHTML 的區(qū)別。》[71]
71. DOM 操作——怎樣添加、移除、移動(dòng)、復(fù)制、創(chuàng)建和查找節(jié)點(diǎn)?
(1)創(chuàng)建新節(jié)點(diǎn)
createDocumentFragment(node);
createElement(node);
createTextNode(text);
復(fù)制代碼
(2)添加、移除、替換、插入
appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)
復(fù)制代碼
(3)查找
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
復(fù)制代碼
(4)屬性操作
getAttribute(key);
setAttribute(key,?value);
hasAttribute(key);
removeAttribute(key);
復(fù)制代碼
詳細(xì)資料可以參考:《DOM 概述》[72] 《原生 JavaScript 的 DOM 操作匯總》[73] 《原生 JS 中 DOM 節(jié)點(diǎn)相關(guān) API 合集》[74]
72. innerHTML 與 outerHTML 的區(qū)別?
對(duì)于這樣一個(gè) HTML 元素:content
</div>。
innerHTML:內(nèi)部 HTML,content
>;
outerHTML:外部 HTML,content
</div>;
innerText:內(nèi)部文本,content ;
outerText:內(nèi)部文本,content ;
復(fù)制代碼
73. .call() 和 .apply() 的區(qū)別?
它們的作用一模一樣,區(qū)別僅在于傳入?yún)?shù)的形式的不同。
apply?接受兩個(gè)參數(shù),第一個(gè)參數(shù)指定了函數(shù)體內(nèi)?this?對(duì)象的指向,第二個(gè)參數(shù)為一個(gè)帶下標(biāo)的集合,這個(gè)集合可以為數(shù)組,也可以為類數(shù)組,apply 方法把這個(gè)集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)。
call?傳入的參數(shù)數(shù)量不固定,跟?apply?相同的是,第一個(gè)參數(shù)也是代表函數(shù)體內(nèi)的?this?指向,從第二個(gè)參數(shù)開始往后,每個(gè)參數(shù)被依次傳入函數(shù)。
復(fù)制代碼
詳細(xì)資料可以參考:《apply、call 的區(qū)別和用途》[75]
74. JavaScript 類數(shù)組對(duì)象的定義?
一個(gè)擁有 length 屬性和若干索引屬性的對(duì)象就可以被稱為類數(shù)組對(duì)象,類數(shù)組對(duì)象和數(shù)組類似,但是不能調(diào)用數(shù)組的方法。
常見(jiàn)的類數(shù)組對(duì)象有?arguments?和?DOM?方法的返回結(jié)果,還有一個(gè)函數(shù)也可以被看作是類數(shù)組對(duì)象,因?yàn)樗?length
屬性值,代表可接收的參數(shù)個(gè)數(shù)。
復(fù)制代碼
常見(jiàn)的類數(shù)組轉(zhuǎn)換為數(shù)組的方法有這樣幾種:
(1)通過(guò) call 調(diào)用數(shù)組的 slice 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.slice.call(arrayLike);
復(fù)制代碼
(2)通過(guò) call 調(diào)用數(shù)組的 splice 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.splice.call(arrayLike,?0);
復(fù)制代碼
(3)通過(guò) apply 調(diào)用數(shù)組的 concat 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.concat.apply([],?arrayLike);
復(fù)制代碼
(4)通過(guò) Array.from 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.from(arrayLike);
復(fù)制代碼
詳細(xì)的資料可以參考:《JavaScript 深入之類數(shù)組對(duì)象與 arguments》[76] 《javascript 類數(shù)組》[77] 《深入理解 JavaScript 類數(shù)組》[78]
75. 數(shù)組和對(duì)象有哪些原生方法,列舉一下?
數(shù)組和字符串的轉(zhuǎn)換方法:toString()、toLocalString()、join()?其中 join()?方法可以指定轉(zhuǎn)換為字符串時(shí)的分隔符。
數(shù)組尾部操作的方法 pop()?和 push(),push 方法可以傳入多個(gè)參數(shù)。
數(shù)組首部操作的方法 shift()?和 unshift()?重排序的方法 reverse()?和 sort(),sort()?方法可以傳入一個(gè)函數(shù)來(lái)進(jìn)行比較,傳入前后兩個(gè)值,如果返回值為正數(shù),則交換兩個(gè)參數(shù)的位置。
數(shù)組連接的方法 concat()?,返回的是拼接好的數(shù)組,不影響原數(shù)組。
數(shù)組截取辦法 slice(),用于截取數(shù)組中的一部分返回,不影響原數(shù)組。
數(shù)組插入方法?splice(),影響原數(shù)組查找特定項(xiàng)的索引的方法,indexOf()?和?lastIndexOf()?迭代方法?every()、some()、filter()、map()?和?forEach()?方法
數(shù)組歸并方法?reduce()?和?reduceRight()?方法
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之 Array 類型詳解》[79]
76. 數(shù)組的 fill 方法?
fill()?方法用一個(gè)固定值填充一個(gè)數(shù)組中從起始索引到終止索引內(nèi)的全部元素。不包括終止索引。
fill?方法接受三個(gè)參數(shù)?value,start?以及?end,start?和?end?參數(shù)是可選的,其默認(rèn)值分別為?0?和?this?對(duì)象的 length 屬性值。
復(fù)制代碼
詳細(xì)資料可以參考:《Array.prototype.fill()》[80]
77. [,,,] 的長(zhǎng)度?
尾后逗號(hào)?(有時(shí)叫做“終止逗號(hào)”)在向 JavaScript 代碼添加元素、參數(shù)、屬性時(shí)十分有用。如果你想要添加新的屬性,并且上一行已經(jīng)使用了尾后逗號(hào),你可以僅僅添加新的一行,而不需要修改上一行。這使得版本控制更加清晰,以及代碼維護(hù)麻煩更少。
JavaScript?一開始就支持?jǐn)?shù)組字面值中的尾后逗號(hào),隨后向?qū)ο笞置嬷担‥CMAScript?5)中添加了尾后逗號(hào)。最近(ECMAS
cript?2017),又將其添加到函數(shù)參數(shù)中。但是?JSON?不支持尾后逗號(hào)。
如果使用了多于一個(gè)尾后逗號(hào),會(huì)產(chǎn)生間隙。?帶有間隙的數(shù)組叫做稀疏數(shù)組(密致數(shù)組沒(méi)有間隙)。稀疏數(shù)組的長(zhǎng)度為逗號(hào)的數(shù)
量。
復(fù)制代碼
詳細(xì)資料可以參考:《尾后逗號(hào)》[81]
78. JavaScript 中的作用域與變量聲明提升?
變量提升的表現(xiàn)是,無(wú)論我們?cè)诤瘮?shù)中何處位置聲明的變量,好像都被提升到了函數(shù)的首部,我們可以在變量聲明前訪問(wèn)到而不會(huì)報(bào)錯(cuò)。
造成變量聲明提升的本質(zhì)原因是 js 引擎在代碼執(zhí)行前有一個(gè)解析的過(guò)程,創(chuàng)建了執(zhí)行上下文,初始化了一些代碼執(zhí)行時(shí)需要用到的對(duì)象。當(dāng)我們?cè)L問(wèn)一個(gè)變量時(shí),我們會(huì)到當(dāng)前執(zhí)行上下文中的作用域鏈中去查找,而作用域鏈的首端指向的是當(dāng)前執(zhí)行上下文的變量對(duì)象,這個(gè)變量對(duì)象是執(zhí)行上下文的一個(gè)屬性,它包含了函數(shù)的形參、所有的函數(shù)和變量聲明,這個(gè)對(duì)象的是在代碼解析的時(shí)候創(chuàng)建的。這就是會(huì)出現(xiàn)變量聲明提升的根本原因。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 深入理解之變量對(duì)象》[82]
79. 如何編寫高性能的 Javascript ?
1.使用位運(yùn)算代替一些簡(jiǎn)單的四則運(yùn)算。 2.避免使用過(guò)深的嵌套循環(huán)。 3.不要使用未定義的變量。 4.當(dāng)需要多次訪問(wèn)數(shù)組長(zhǎng)度時(shí),可以用變量保存起來(lái),避免每次都會(huì)去進(jìn)行屬性查找。
詳細(xì)資料可以參考:《如何編寫高性能的 Javascript?》[83]
80. 簡(jiǎn)單介紹一下 V8 引擎的垃圾回收機(jī)制
v8 的垃圾回收機(jī)制基于分代回收機(jī)制,這個(gè)機(jī)制又基于世代假說(shuō),這個(gè)假說(shuō)有兩個(gè)特點(diǎn),一是新生的對(duì)象容易早死,另一個(gè)是不死的對(duì)象會(huì)活得更久。基于這個(gè)假說(shuō),v8 引擎將內(nèi)存分為了新生代和老生代。
新創(chuàng)建的對(duì)象或者只經(jīng)歷過(guò)一次的垃圾回收的對(duì)象被稱為新生代。經(jīng)歷過(guò)多次垃圾回收的對(duì)象被稱為老生代。
新生代被分為 From 和 To 兩個(gè)空間,To 一般是閑置的。當(dāng) From 空間滿了的時(shí)候會(huì)執(zhí)行 Scavenge 算法進(jìn)行垃圾回收。當(dāng)我們執(zhí)行垃圾回收算法的時(shí)候應(yīng)用邏輯將會(huì)停止,等垃圾回收結(jié)束后再繼續(xù)執(zhí)行。這個(gè)算法分為三步:
(1)首先檢查 From 空間的存活對(duì)象,如果對(duì)象存活則判斷對(duì)象是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動(dòng) To 空間。
(2)如果對(duì)象不存活,則釋放對(duì)象的空間。
(3)最后將 From 空間和 To 空間角色進(jìn)行交換。
新生代對(duì)象晉升到老生代有兩個(gè)條件:
(1)第一個(gè)是判斷是對(duì)象否已經(jīng)經(jīng)過(guò)一次 Scavenge 回收。若經(jīng)歷過(guò),則將對(duì)象從 From 空間復(fù)制到老生代中;若沒(méi)有經(jīng)歷,則復(fù)制到 To 空間。
(2)第二個(gè)是 To 空間的內(nèi)存使用占比是否超過(guò)限制。當(dāng)對(duì)象從 From 空間復(fù)制到 To 空間時(shí),若 To 空間使用超過(guò)?25%,則對(duì)象直接晉升到老生代中。設(shè)置?25%?的原因主要是因?yàn)樗惴ńY(jié)束后,兩個(gè)空間結(jié)束后會(huì)交換位置,如果 To 空間的內(nèi)存太小,會(huì)影響后續(xù)的內(nèi)存分配。
老生代采用了標(biāo)記清除法和標(biāo)記壓縮法。標(biāo)記清除法首先會(huì)對(duì)內(nèi)存中存活的對(duì)象進(jìn)行標(biāo)記,標(biāo)記結(jié)束后清除掉那些沒(méi)有標(biāo)記的對(duì)象。由于標(biāo)記清除后會(huì)造成很多的內(nèi)存碎片,不便于后面的內(nèi)存分配。所以了解決內(nèi)存碎片的問(wèn)題引入了標(biāo)記壓縮法。
由于在進(jìn)行垃圾回收的時(shí)候會(huì)暫停應(yīng)用的邏輯,對(duì)于新生代方法由于內(nèi)存小,每次停頓的時(shí)間不會(huì)太長(zhǎng),但對(duì)于老生代來(lái)說(shuō)每次垃圾回收的時(shí)間長(zhǎng),停頓會(huì)造成很大的影響。?為了解決這個(gè)問(wèn)題 V8 引入了增量標(biāo)記的方法,將一次停頓進(jìn)行的過(guò)程分為了多步,每次執(zhí)行完一小步就讓運(yùn)行邏輯執(zhí)行一會(huì),就這樣交替運(yùn)行。
復(fù)制代碼
詳細(xì)資料可以參考:《深入理解 V8 的垃圾回收原理》[84] 《JavaScript 中的垃圾回收》[85]
81. 哪些操作會(huì)造成內(nèi)存泄漏?
相關(guān)知識(shí)點(diǎn):
1.意外的全局變量 2.被遺忘的計(jì)時(shí)器或回調(diào)函數(shù) 3.脫離 DOM 的引用 4.閉包
回答:
第一種情況是我們由于使用未聲明的變量,而意外的創(chuàng)建了一個(gè)全局變量,而使這個(gè)變量一直留在內(nèi)存中無(wú)法被回收。
第二種情況是我們?cè)O(shè)置了?setInterval?定時(shí)器,而忘記取消它,如果循環(huán)函數(shù)有對(duì)外部變量的引用的話,那么這個(gè)變量會(huì)被一直留
在內(nèi)存中,而無(wú)法被回收。
第三種情況是我們獲取一個(gè)?DOM?元素的引用,而后面這個(gè)元素被刪除,由于我們一直保留了對(duì)這個(gè)元素的引用,所以它也無(wú)法被回
收。
第四種情況是不合理的使用閉包,從而導(dǎo)致某些變量一直被留在內(nèi)存當(dāng)中。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 內(nèi)存泄漏教程》[86] 《4 類 JavaScript 內(nèi)存泄漏及如何避免》[87] 《杜絕 js 中四種內(nèi)存泄漏類型的發(fā)生》[88] 《javascript 典型內(nèi)存泄漏及 chrome 的排查方法》[89]
82. 需求:實(shí)現(xiàn)一個(gè)頁(yè)面操作不會(huì)整頁(yè)刷新的網(wǎng)站,并且能在瀏覽器前進(jìn)、后退時(shí)正確響應(yīng)。給出你的技術(shù)實(shí)現(xiàn)方案?
通過(guò)使用?pushState?+?ajax?實(shí)現(xiàn)瀏覽器無(wú)刷新前進(jìn)后退,當(dāng)一次?ajax?調(diào)用成功后我們將一條?state?記錄加入到?history
對(duì)象中。一條 state 記錄包含了 url、title 和 content 屬性,在 popstate 事件中可以獲取到這個(gè) state 對(duì)象,我們可
以使用 content 來(lái)傳遞數(shù)據(jù)。最后我們通過(guò)對(duì)?window.onpopstate 事件監(jiān)聽(tīng)來(lái)響應(yīng)瀏覽器的前進(jìn)后退操作。
使用?pushState?來(lái)實(shí)現(xiàn)有兩個(gè)問(wèn)題,一個(gè)是打開首頁(yè)時(shí)沒(méi)有記錄,我們可以使用?replaceState?來(lái)將首頁(yè)的記錄替換,另一個(gè)問(wèn)
題是當(dāng)一個(gè)頁(yè)面刷新的時(shí)候,仍然會(huì)向服務(wù)器端請(qǐng)求數(shù)據(jù),因此如果請(qǐng)求的 url 需要后端的配合將其重定向到一個(gè)頁(yè)面。
復(fù)制代碼
詳細(xì)資料可以參考:《pushState + ajax 實(shí)現(xiàn)瀏覽器無(wú)刷新前進(jìn)后退》[90] 《Manipulating the browser history》[91]
83. 如何判斷當(dāng)前腳本運(yùn)行在瀏覽器還是 node 環(huán)境中?(阿里)
this?===?window???'browser'?:?'node';
通過(guò)判斷?Global?對(duì)象是否為?window,如果不為?window,當(dāng)前腳本沒(méi)有運(yùn)行在瀏覽器中。
復(fù)制代碼
84. 把 script 標(biāo)簽放在頁(yè)面的最底部的 body 封閉之前和封閉之后有什么區(qū)別?瀏覽器會(huì)如何解析它們?
詳細(xì)資料可以參考:《為什么把 script 標(biāo)簽放在 body 結(jié)束標(biāo)簽之后 html 結(jié)束標(biāo)簽之前?》[92] 《從 Chrome 源碼看瀏覽器如何加載資源》[93]
85. 移動(dòng)端的點(diǎn)擊事件的有延遲,時(shí)間是多久,為什么會(huì)有?怎么解決這個(gè)延時(shí)?
移動(dòng)端點(diǎn)擊有?300ms?的延遲是因?yàn)橐苿?dòng)端會(huì)有雙擊縮放的這個(gè)操作,因此瀏覽器在?click?之后要等待?300ms,看用戶有沒(méi)有下一次點(diǎn)擊,來(lái)判斷這次操作是不是雙擊。
復(fù)制代碼
有三種辦法來(lái)解決這個(gè)問(wèn)題:
1.通過(guò) meta 標(biāo)簽禁用網(wǎng)頁(yè)的縮放。 2.通過(guò) meta 標(biāo)簽將網(wǎng)頁(yè)的 viewport 設(shè)置為 ideal viewport。 3.調(diào)用一些 js 庫(kù),比如 FastClick
click 延時(shí)問(wèn)題還可能引起點(diǎn)擊穿透的問(wèn)題,就是如果我們?cè)谝粋€(gè)元素上注冊(cè)了 touchStart 的監(jiān)聽(tīng)事件,這個(gè)事件會(huì)將這個(gè)元素隱藏掉,我們發(fā)現(xiàn)當(dāng)這個(gè)元素隱藏后,觸發(fā)了這個(gè)元素下的一個(gè)元素的點(diǎn)擊事件,這就是點(diǎn)擊穿透。
復(fù)制代碼
詳細(xì)資料可以參考:《移動(dòng)端 300ms 點(diǎn)擊延遲和點(diǎn)擊穿透》[94]
86. 什么是“前端路由”?什么時(shí)候適合使用“前端路由”?“前端路由”有哪些優(yōu)點(diǎn)和缺點(diǎn)?
(1)什么是前端路由?
前端路由就是把不同路由對(duì)應(yīng)不同的內(nèi)容或頁(yè)面的任務(wù)交給前端來(lái)做,之前是通過(guò)服務(wù)端根據(jù) url 的不同返回不同的頁(yè)面實(shí)現(xiàn)的。
(2)什么時(shí)候使用前端路由?
在單頁(yè)面應(yīng)用,大部分頁(yè)面結(jié)構(gòu)不變,只改變部分內(nèi)容的使用
(3)前端路由有什么優(yōu)點(diǎn)和缺點(diǎn)?
優(yōu)點(diǎn):用戶體驗(yàn)好,不需要每次都從服務(wù)器全部獲取,快速展現(xiàn)給用戶
缺點(diǎn):?jiǎn)雾?yè)面無(wú)法記住之前滾動(dòng)的位置,無(wú)法在前進(jìn),后退的時(shí)候記住滾動(dòng)的位置
前端路由一共有兩種實(shí)現(xiàn)方式,一種是通過(guò) hash 的方式,一種是通過(guò)使用 pushState 的方式。
復(fù)制代碼
詳細(xì)資料可以參考:《什么是“前端路由”》[95] 《淺談前端路由》[96] 《前端路由是什么東西?》[97]
87. 如何測(cè)試前端代碼么?知道 BDD, TDD, Unit Test 么?知道怎么測(cè)試你的前端工程么(mocha, sinon, jasmin, qUnit..)?
詳細(xì)資料可以參考:《淺談前端單元測(cè)試》[98]
88. 檢測(cè)瀏覽器版本版本有哪些方式?
檢測(cè)瀏覽器版本一共有兩種方式:
一種是檢測(cè)?window.navigator.userAgent 的值,但這種方式很不可靠,因?yàn)?userAgent 可以被改寫,并且早期的瀏覽器如 ie,會(huì)通過(guò)偽裝自己的 userAgent 的值為 Mozilla 來(lái)躲過(guò)服務(wù)器的檢測(cè)。
第二種方式是功能檢測(cè),根據(jù)每個(gè)瀏覽器獨(dú)有的特性來(lái)進(jìn)行判斷,如 ie 下獨(dú)有的 ActiveXObject。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 判斷瀏覽器類型》[99]
89. 什么是 Polyfill ?
Polyfill 指的是用于實(shí)現(xiàn)瀏覽器并不支持的原生 API 的代碼。
比如說(shuō) querySelectorAll 是很多現(xiàn)代瀏覽器都支持的原生 Web API,但是有些古老的瀏覽器并不支持,那么假設(shè)有人寫了一段代碼來(lái)實(shí)現(xiàn)這個(gè)功能使這些瀏覽器也支持了這個(gè)功能,那么這就可以成為一個(gè) Polyfill。
一個(gè) shim 是一個(gè)庫(kù),有自己的 API,而不是單純實(shí)現(xiàn)原生不支持的 API。
復(fù)制代碼
詳細(xì)資料可以參考:《Web 開發(fā)中的“黑話”》[100] 《Polyfill 為何物》[101]
90. 使用 JS 實(shí)現(xiàn)獲取文件擴(kuò)展名?
// String.lastIndexOf()?方法返回指定值(本例中的'.')在調(diào)用該方法的字符串中最后出現(xiàn)的位置,如果沒(méi)找到則返回?-1。
//?對(duì)于?'filename'?和?'.hiddenfile'?,lastIndexOf 的返回值分別為?0?和?-1 無(wú)符號(hào)右移操作符(>>>)?將?-1 轉(zhuǎn)換為 4294967295 ,將?-2 轉(zhuǎn)換為 4294967294 ,這個(gè)方法可以保證邊緣情況時(shí)文件名不變。
// String.prototype.slice()?從上面計(jì)算的索引處提取文件的擴(kuò)展名。如果索引比文件名的長(zhǎng)度大,結(jié)果為""。
function?getFileExtension(filename)?{
??return?filename.slice(((filename.lastIndexOf(".")?-?1)?>>>?0)?+?2);
}
復(fù)制代碼
詳細(xì)資料可以參考:《如何更有效的獲取文件擴(kuò)展名》[102]
91. 介紹一下 js 的節(jié)流與防抖?
相關(guān)知識(shí)點(diǎn):
//?函數(shù)防抖:?在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時(shí)。
//?函數(shù)節(jié)流:?規(guī)定一個(gè)單位時(shí)間,在這個(gè)單位時(shí)間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個(gè)單位時(shí)間內(nèi)某事件被觸發(fā)多次,只有一次能生效。
//?函數(shù)防抖的實(shí)現(xiàn)
function?debounce(fn,?wait)?{
??var?timer?=?null;
??return?function()?{
????var?context?=?this,
??????args?=?arguments;
????//?如果此時(shí)存在定時(shí)器的話,則取消之前的定時(shí)器重新記時(shí)
????if?(timer)?{
??????clearTimeout(timer);
??????timer?=?null;
????}
????//?設(shè)置定時(shí)器,使事件間隔指定事件后執(zhí)行
????timer?=?setTimeout(()?=>?{
??????fn.apply(context,?args);
????},?wait);
??};
}
//?函數(shù)節(jié)流的實(shí)現(xiàn);
function?throttle(fn,?delay)?{
??var?preTime?=?Date.now();
??return?function()?{
????var?context?=?this,
??????args?=?arguments,
??????nowTime?=?Date.now();
????//?如果兩次時(shí)間間隔超過(guò)了指定時(shí)間,則執(zhí)行函數(shù)。
????if?(nowTime?-?preTime?>=?delay)?{
??????preTime?=?Date.now();
??????return?fn.apply(context,?args);
????}
??};
}
復(fù)制代碼
回答:
函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時(shí)。這可以使用在一些點(diǎn)擊請(qǐng)求的事件上,避免因?yàn)橛脩舻亩啻吸c(diǎn)擊向后端發(fā)送多次請(qǐng)求。
函數(shù)節(jié)流是指規(guī)定一個(gè)單位時(shí)間,在這個(gè)單位時(shí)間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個(gè)單位時(shí)間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽(tīng)上,通過(guò)事件節(jié)流來(lái)降低事件調(diào)用的頻率。
復(fù)制代碼
詳細(xì)資料可以參考:《輕松理解 JS 函數(shù)節(jié)流和函數(shù)防抖》[103] 《JavaScript 事件節(jié)流和事件防抖》[104] 《JS 的防抖與節(jié)流》[105]
92. Object.is() 與原來(lái)的比較操作符 “===”、“==” 的區(qū)別?
相關(guān)知識(shí)點(diǎn):
兩等號(hào)判等,會(huì)在比較時(shí)進(jìn)行類型轉(zhuǎn)換。
三等號(hào)判等(判斷嚴(yán)格),比較時(shí)不進(jìn)行隱式類型轉(zhuǎn)換,(類型不同則會(huì)返回false)。
Object.is?在三等號(hào)判等的基礎(chǔ)上特別處理了?NaN?、-0?和?+0?,保證?-0?和?+0?不再相同,但?Object.is(NaN,?NaN)?會(huì)返回?true.
Object.is 應(yīng)被認(rèn)為有其特殊的用途,而不能用它認(rèn)為它比其它的相等對(duì)比更寬松或嚴(yán)格。
復(fù)制代碼
回答:
使用雙等號(hào)進(jìn)行相等判斷時(shí),如果兩邊的類型不一致,則會(huì)進(jìn)行強(qiáng)制類型轉(zhuǎn)化后再進(jìn)行比較。
使用三等號(hào)進(jìn)行相等判斷時(shí),如果兩邊的類型不一致時(shí),不會(huì)做強(qiáng)制類型準(zhǔn)換,直接返回?false。
使用?Object.is?來(lái)進(jìn)行相等判斷時(shí),一般情況下和三等號(hào)的判斷相同,它處理了一些特殊的情況,比如?-0?和?+0?不再相等,兩個(gè)?NaN?認(rèn)定為是相等的。
復(fù)制代碼
93. escape,encodeURI,encodeURIComponent 有什么區(qū)別?
相關(guān)知識(shí)點(diǎn):
escape?和?encodeURI?都屬于 Percent-encoding,基本功能都是把 URI 非法字符轉(zhuǎn)化成合法字符,轉(zhuǎn)化后形式類似「%*」。
它們的根本區(qū)別在于,escape?在處理?0xff?之外字符的時(shí)候,是直接使用字符的?unicode?在前面加上一個(gè)「%u」,而?encode?URI?則是先進(jìn)行?UTF-8,再在?UTF-8?的每個(gè)字節(jié)碼前加上一個(gè)「%」;在處理?0xff?以內(nèi)字符時(shí),編碼方式是一樣的(都是「%XX」,XX?為字符的?16?進(jìn)制?unicode,同時(shí)也是字符的?UTF-8),只是范圍(即哪些字符編碼哪些字符不編碼)不一樣。
復(fù)制代碼
回答:
encodeURI?是對(duì)整個(gè) URI 進(jìn)行轉(zhuǎn)義,將 URI 中的非法字符轉(zhuǎn)換為合法字符,所以對(duì)于一些在 URI 中有特殊意義的字符不會(huì)進(jìn)行轉(zhuǎn)義。
encodeURIComponent?是對(duì) URI 的組成部分進(jìn)行轉(zhuǎn)義,所以一些特殊字符也會(huì)得到轉(zhuǎn)義。
escape?和?encodeURI?的作用相同,不過(guò)它們對(duì)于?unicode?編碼為?0xff?之外字符的時(shí)候會(huì)有區(qū)別,escape?是直接在字符的?unicode?編碼前加上?%u,而?encodeURI?首先會(huì)將字符轉(zhuǎn)換為?UTF-8?的格式,再在每個(gè)字節(jié)前加上?%。
復(fù)制代碼
詳細(xì)資料可以參考:《escape,encodeURI,encodeURIComponent 有什么區(qū)別?》[106]
94. Unicode 和 UTF-8 之間的關(guān)系?
Unicode?是一種字符集合,現(xiàn)在可容納?100?多萬(wàn)個(gè)字符。每個(gè)字符對(duì)應(yīng)一個(gè)不同的 Unicode 編碼,它只規(guī)定了符號(hào)的二進(jìn)制代碼,卻沒(méi)有規(guī)定這個(gè)二進(jìn)制代碼在計(jì)算機(jī)中如何編碼傳輸。
UTF-8?是一種對(duì)?Unicode?的編碼方式,它是一種變長(zhǎng)的編碼方式,可以用?1~4?個(gè)字節(jié)來(lái)表示一個(gè)字符。
復(fù)制代碼
詳細(xì)資料可以參考:《字符編碼詳解》[107] 《字符編碼筆記:ASCII,Unicode 和 UTF-8》[108]
95. js 的事件循環(huán)是什么?
相關(guān)知識(shí)點(diǎn):
事件隊(duì)列是一個(gè)存儲(chǔ)著待執(zhí)行任務(wù)的隊(duì)列,其中的任務(wù)嚴(yán)格按照時(shí)間先后順序執(zhí)行,排在隊(duì)頭的任務(wù)將會(huì)率先執(zhí)行,而排在隊(duì)尾的任務(wù)會(huì)最后執(zhí)行。事件隊(duì)列每次僅執(zhí)行一個(gè)任務(wù),在該任務(wù)執(zhí)行完畢之后,再執(zhí)行下一個(gè)任務(wù)。執(zhí)行棧則是一個(gè)類似于函數(shù)調(diào)用棧的運(yùn)行容器,當(dāng)執(zhí)行棧為空時(shí),JS 引擎便檢查事件隊(duì)列,如果不為空的話,事件隊(duì)列便將第一個(gè)任務(wù)壓入執(zhí)行棧中運(yùn)行。
復(fù)制代碼
回答:
因?yàn)?js 是單線程運(yùn)行的,在代碼執(zhí)行的時(shí)候,通過(guò)將不同函數(shù)的執(zhí)行上下文壓入執(zhí)行棧中來(lái)保證代碼的有序執(zhí)行。在執(zhí)行同步代碼的時(shí)候,如果遇到了異步事件,js 引擎并不會(huì)一直等待其返回結(jié)果,而是會(huì)將這個(gè)事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。當(dāng)異步事件執(zhí)行完畢后,再將異步事件對(duì)應(yīng)的回調(diào)加入到與當(dāng)前執(zhí)行棧中不同的另一個(gè)任務(wù)隊(duì)列中等待執(zhí)行。任務(wù)隊(duì)列可以分為宏任務(wù)對(duì)列和微任務(wù)對(duì)列,當(dāng)當(dāng)前執(zhí)行棧中的事件執(zhí)行完畢后,js 引擎首先會(huì)判斷微任務(wù)對(duì)列中是否有任務(wù)可以執(zhí)行,如果有就將微任務(wù)隊(duì)首的事件壓入棧中執(zhí)行。當(dāng)微任務(wù)對(duì)列中的任務(wù)都執(zhí)行完成后再去判斷宏任務(wù)對(duì)列中的任務(wù)。
微任務(wù)包括了 promise 的回調(diào)、node 中的 process.nextTick 、對(duì) Dom 變化監(jiān)聽(tīng)的 MutationObserver。
宏任務(wù)包括了?script?腳本的執(zhí)行、setTimeout?,setInterval?,setImmediate?一類的定時(shí)事件,還有如?I/O?操作、UI?渲
染等。
復(fù)制代碼
詳細(xì)資料可以參考:《瀏覽器事件循環(huán)機(jī)制(event loop)》[109] 《詳解 JavaScript 中的 Event Loop(事件循環(huán))機(jī)制》[110] 《什么是 Event Loop?》[111] 《這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制》[112]
96. js 中的深淺拷貝實(shí)現(xiàn)?
相關(guān)資料:
//?淺拷貝的實(shí)現(xiàn);
function?shallowCopy(object)?{
??//?只拷貝對(duì)象
??if?(!object?||?typeof?object?!==?"object")?return;
??//?根據(jù)?object?的類型判斷是新建一個(gè)數(shù)組還是對(duì)象
??let?newObject?=?Array.isArray(object)???[]?:?{};
??//?遍歷?object,并且判斷是?object?的屬性才拷貝
??for?(let?key?in?object)?{
????if?(object.hasOwnProperty(key))?{
??????newObject[key]?=?object[key];
????}
??}
??return?newObject;
}
//?深拷貝的實(shí)現(xiàn);
function?deepCopy(object)?{
??if?(!object?||?typeof?object?!==?"object")?return;
??let?newObject?=?Array.isArray(object)???[]?:?{};
??for?(let?key?in?object)?{
????if?(object.hasOwnProperty(key))?{
??????newObject[key]?=
????????typeof?object[key]?===?"object"???deepCopy(object[key])?:?object[key];
????}
??}
??return?newObject;
}
復(fù)制代碼
回答:
淺拷貝指的是將一個(gè)對(duì)象的屬性值復(fù)制到另一個(gè)對(duì)象,如果有的屬性的值為引用類型的話,那么會(huì)將這個(gè)引用的地址復(fù)制給對(duì)象,因此兩個(gè)對(duì)象會(huì)有同一個(gè)引用類型的引用。淺拷貝可以使用??Object.assign 和展開運(yùn)算符來(lái)實(shí)現(xiàn)。
深拷貝相對(duì)淺拷貝而言,如果遇到屬性值為引用類型的時(shí)候,它新建一個(gè)引用類型并將對(duì)應(yīng)的值復(fù)制給它,因此對(duì)象獲得的一個(gè)新的引用類型而不是一個(gè)原有類型的引用。深拷貝對(duì)于一些對(duì)象可以使用?JSON?的兩個(gè)函數(shù)來(lái)實(shí)現(xiàn),但是由于?JSON?的對(duì)象格式比?js?的對(duì)象格式更加嚴(yán)格,所以如果屬性值里邊出現(xiàn)函數(shù)或者?Symbol?類型的值時(shí),會(huì)轉(zhuǎn)換失敗。
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 專題之深淺拷貝》[113] 《前端面試之道》[114]
97. 手寫 call、apply 及 bind 函數(shù)
相關(guān)資料:
//?call函數(shù)實(shí)現(xiàn)
Function.prototype.myCall?=?function(context)?{
??//?判斷調(diào)用對(duì)象
??if?(typeof?this?!==?"function")?{
????console.error("type?error");
??}
??//?獲取參數(shù)
??let?args?=?[...arguments].slice(1),
????result?=?null;
??//?判斷?context?是否傳入,如果未傳入則設(shè)置為?window
??context?=?context?||?window;
??//?將調(diào)用函數(shù)設(shè)為對(duì)象的方法
??context.fn?=?this;
??//?調(diào)用函數(shù)
??result?=?context.fn(...args);
??//?將屬性刪除
??delete?context.fn;
??return?result;
};
//?apply?函數(shù)實(shí)現(xiàn)
Function.prototype.myApply?=?function(context)?{
??//?判斷調(diào)用對(duì)象是否為函數(shù)
??if?(typeof?this?!==?"function")?{
????throw?new?TypeError("Error");
??}
??let?result?=?null;
??//?判斷?context?是否存在,如果未傳入則為?window
??context?=?context?||?window;
??//?將函數(shù)設(shè)為對(duì)象的方法
??context.fn?=?this;
??//?調(diào)用方法
??if?(arguments[1])?{
????result?=?context.fn(...arguments[1]);
??}?else?{
????result?=?context.fn();
??}
??//?將屬性刪除
??delete?context.fn;
??return?result;
};
//?bind?函數(shù)實(shí)現(xiàn)
Function.prototype.myBind?=?function(context)?{
??//?判斷調(diào)用對(duì)象是否為函數(shù)
??if?(typeof?this?!==?"function")?{
????throw?new?TypeError("Error");
??}
??//?獲取參數(shù)
??var?args?=?[...arguments].slice(1),
????fn?=?this;
??return?function?Fn()?{
????//?根據(jù)調(diào)用方式,傳入不同綁定值
????return?fn.apply(
??????this?instanceof?Fn???this?:?context,
??????args.concat(...arguments)
????);
??};
};
復(fù)制代碼
回答:
call 函數(shù)的實(shí)現(xiàn)步驟:
1.判斷調(diào)用對(duì)象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 2.判斷傳入上下文對(duì)象是否存在,如果不存在,則設(shè)置為 window 。 3.處理傳入的參數(shù),截取第一個(gè)參數(shù)后的所有參數(shù)。 4.將函數(shù)作為上下文對(duì)象的一個(gè)屬性。 5.使用上下文對(duì)象來(lái)調(diào)用這個(gè)方法,并保存返回結(jié)果。 6.刪除剛才新增的屬性。 7.返回結(jié)果。
apply 函數(shù)的實(shí)現(xiàn)步驟:
1.判斷調(diào)用對(duì)象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 2.判斷傳入上下文對(duì)象是否存在,如果不存在,則設(shè)置為 window 。 3.將函數(shù)作為上下文對(duì)象的一個(gè)屬性。 4.判斷參數(shù)值是否傳入 4.使用上下文對(duì)象來(lái)調(diào)用這個(gè)方法,并保存返回結(jié)果。 5.刪除剛才新增的屬性 6.返回結(jié)果
bind 函數(shù)的實(shí)現(xiàn)步驟:
1.判斷調(diào)用對(duì)象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 2.保存當(dāng)前函數(shù)的引用,獲取其余傳入?yún)?shù)值。 3.創(chuàng)建一個(gè)函數(shù)返回 4.函數(shù)內(nèi)部使用 apply 來(lái)綁定函數(shù)調(diào)用,需要判斷函數(shù)作為構(gòu)造函數(shù)的情況,這個(gè)時(shí)候需要傳入當(dāng)前函數(shù)的 this 給 apply 調(diào)用,其余情況都傳入指定的上下文對(duì)象。
詳細(xì)資料可以參考:《手寫 call、apply 及 bind 函數(shù)》[115] 《JavaScript 深入之 call 和 apply 的模擬實(shí)現(xiàn)》[116]
98. 函數(shù)柯里化的實(shí)現(xiàn)
//?函數(shù)柯里化指的是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。
function?curry(fn,?args)?{
??//?獲取函數(shù)需要的參數(shù)長(zhǎng)度
??let?length?=?fn.length;
??args?=?args?||?[];
??return?function()?{
????let?subArgs?=?args.slice(0);
????//?拼接得到現(xiàn)有的所有參數(shù)
????for?(let?i?=?0;?i?arguments.length;?i++)?{
??????subArgs.push(arguments[i]);
????}
????//?判斷參數(shù)的長(zhǎng)度是否已經(jīng)滿足函數(shù)所需參數(shù)的長(zhǎng)度
????if?(subArgs.length?>=?length)?{
??????//?如果滿足,執(zhí)行函數(shù)
??????return?fn.apply(this,?subArgs);
????}?else?{
??????//?如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入
??????return?curry.call(this,?fn,?subArgs);
????}
??};
}
//?es6?實(shí)現(xiàn)
function?curry(fn,?...args)?{
??return?fn.length?<=?args.length???fn(...args)?:?curry.bind(null,?fn,?...args);
}
復(fù)制代碼
詳細(xì)資料可以參考:《JavaScript 專題之函數(shù)柯里化》[117]
99. 為什么 0.1 + 0.2 != 0.3?如何解決這個(gè)問(wèn)題?
當(dāng)計(jì)算機(jī)計(jì)算?0.1+0.2?的時(shí)候,實(shí)際上計(jì)算的是這兩個(gè)數(shù)字在計(jì)算機(jī)里所存儲(chǔ)的二進(jìn)制,0.1?和?0.2?在轉(zhuǎn)換為二進(jìn)制表示的時(shí)候會(huì)出現(xiàn)位數(shù)無(wú)限循環(huán)的情況。js 中是以?64?位雙精度格式來(lái)存儲(chǔ)數(shù)字的,只有?53?位的有效數(shù)字,超過(guò)這個(gè)長(zhǎng)度的位數(shù)會(huì)被截取掉這樣就造成了精度丟失的問(wèn)題。這是第一個(gè)會(huì)造成精度丟失的地方。在對(duì)兩個(gè)以?64?位雙精度格式的數(shù)據(jù)進(jìn)行計(jì)算的時(shí)候,首先會(huì)進(jìn)行對(duì)階的處理,對(duì)階指的是將階碼對(duì)齊,也就是將小數(shù)點(diǎn)的位置對(duì)齊后,再進(jìn)行計(jì)算,一般是小階向大階對(duì)齊,因此小階的數(shù)在對(duì)齊的過(guò)程中,有效數(shù)字會(huì)向右移動(dòng),移動(dòng)后超過(guò)有效位數(shù)的位會(huì)被截取掉,這是第二個(gè)可能會(huì)出現(xiàn)精度丟失的地方。當(dāng)兩個(gè)數(shù)據(jù)階碼對(duì)齊后,進(jìn)行相加運(yùn)算后,得到的結(jié)果可能會(huì)超過(guò)?53?位有效數(shù)字,因此超過(guò)的位數(shù)也會(huì)被截取掉,這是可能發(fā)生精度丟失的第三個(gè)地方。
對(duì)于這樣的情況,我們可以將其轉(zhuǎn)換為整數(shù)后再進(jìn)行運(yùn)算,運(yùn)算后再轉(zhuǎn)換為對(duì)應(yīng)的小數(shù),以這種方式來(lái)解決這個(gè)問(wèn)題。
我們還可以將兩個(gè)數(shù)相加的結(jié)果和右邊相減,如果相減的結(jié)果小于一個(gè)極小數(shù),那么我們就可以認(rèn)定結(jié)果是相等的,這個(gè)極小數(shù)可以
使用?es6?的?Number.EPSILON
復(fù)制代碼
詳細(xì)資料可以參考:《十進(jìn)制的 0.1 為什么不能用二進(jìn)制很好的表示?》[118] 《十進(jìn)制浮點(diǎn)數(shù)轉(zhuǎn)成二進(jìn)制》[119] 《浮點(diǎn)數(shù)的二進(jìn)制表示》[120] 《js 浮點(diǎn)數(shù)存儲(chǔ)精度丟失原理》[121] 《浮點(diǎn)數(shù)精度之謎》[122] 《JavaScript 浮點(diǎn)數(shù)陷阱及解法》[123] 《0.1+0.2 !== 0.3?》[124] 《JavaScript 中奇特的~運(yùn)算符》[125]
100. 原碼、反碼和補(bǔ)碼的介紹
原碼是計(jì)算機(jī)中對(duì)數(shù)字的二進(jìn)制的定點(diǎn)表示方法,最高位表示符號(hào)位,其余位表示數(shù)值位。優(yōu)點(diǎn)是易于分辨,缺點(diǎn)是不能夠直接參與運(yùn)算。
正數(shù)的反碼和其原碼一樣;負(fù)數(shù)的反碼,符號(hào)位為1,數(shù)值部分按原碼取反。
如?[+7]原?=?00000111,[+7]反?=?00000111;?[-7]原?=?10000111,[-7]反?=?11111000。
正數(shù)的補(bǔ)碼和其原碼一樣;負(fù)數(shù)的補(bǔ)碼為其反碼加1。
例如?[+7]原?=?00000111,[+7]反?=?00000111,[+7]補(bǔ)?=?00000111;
[-7]原?=?10000111,[-7]反?=?11111000,[-7]補(bǔ)?=?11111001
之所以在計(jì)算機(jī)中使用補(bǔ)碼來(lái)表示負(fù)數(shù)的原因是,這樣可以將加法運(yùn)算擴(kuò)展到所有的數(shù)值計(jì)算上,因此在數(shù)字電路中我們只需要考慮加法器的設(shè)計(jì)就行了,而不用再為減法設(shè)置新的數(shù)字電路。
復(fù)制代碼
詳細(xì)資料可以參考:《關(guān)于 2 的補(bǔ)碼》[126]
最后
最后如果文章和筆記能帶您一絲幫助或者啟發(fā),請(qǐng)不要吝嗇你的贊和收藏,你的肯定是我前進(jìn)的最大動(dòng)力 ?? 來(lái)自:EnoYao
https://juejin.cn/post/6992525007716876325
祝 您:2022 年暴富!萬(wàn)事如意!
點(diǎn)贊和在看就是最大的支持,
比心??
