前端經(jīng)典面試題(有答案)
代碼輸出結(jié)果
var a = 10var obj = { ?a: 20, ?say: () => { ? ?console.log(this.a) ?} } obj.say() var anotherObj = { a: 30 } obj.say.apply(anotherObj)
輸出結(jié)果:10 10
我么知道,箭頭函數(shù)時(shí)不綁定this的,它的this來自原其父級(jí)所處的上下文,所以首先會(huì)打印全局中的 a 的值10。后面雖然讓say方法指向了另外一個(gè)對(duì)象,但是仍不能改變箭頭函數(shù)的特性,它的this仍然是指向全局的,所以依舊會(huì)輸出10。
但是,如果是普通函數(shù),那么就會(huì)有完全不一樣的結(jié)果:
var a = 10 ?var obj = { ? ?a: 20, ? ?say(){ ? ?console.log(this.a) ? ?} ? } ? obj.say() ? var anotherObj={a:30} ? obj.say.apply(anotherObj)
輸出結(jié)果:20 30
這時(shí),say方法中的this就會(huì)指向他所在的對(duì)象,輸出其中的a的值。
Compositon api
Composition API
也叫組合式API,是Vue3.x的新特性。
通過創(chuàng)建 Vue 組件,我們可以將接口的可重復(fù)部分及其功能提取到可重用的代碼段中。僅此一項(xiàng)就可以使我們的應(yīng)用程序在可維護(hù)性和靈活性方面走得更遠(yuǎn)。然而,我們的經(jīng)驗(yàn)已經(jīng)證明,光靠這一點(diǎn)可能是不夠的,尤其是當(dāng)你的應(yīng)用程序變得非常大的時(shí)候——想想幾百個(gè)組件。在處理如此大的應(yīng)用程序時(shí),共享和重用代碼變得尤為重要
Vue2.0中,隨著功能的增加,組件變得越來越復(fù)雜,越來越難維護(hù),而難以維護(hù)的根本原因是Vue的API設(shè)計(jì)迫使開發(fā)者使用
watch,computed,methods
選項(xiàng)組織代碼,而不是實(shí)際的業(yè)務(wù)邏輯。另外Vue2.0缺少一種較為簡潔的低成本的機(jī)制來完成邏輯復(fù)用,雖然可以
minxis
完成邏輯復(fù)用,但是當(dāng)mixin
變多的時(shí)候,會(huì)使得難以找到對(duì)應(yīng)的data、computed
或者method
來源于哪個(gè)mixin
,使得類型推斷難以進(jìn)行。所以
Composition API
的出現(xiàn),主要是也是為了解決Option API帶來的問題,第一個(gè)是代碼組織問題,Compostion API
可以讓開發(fā)者根據(jù)業(yè)務(wù)邏輯組織自己的代碼,讓代碼具備更好的可讀性和可擴(kuò)展性,也就是說當(dāng)下一個(gè)開發(fā)者接觸這一段不是他自己寫的代碼時(shí),他可以更好的利用代碼的組織反推出實(shí)際的業(yè)務(wù)邏輯,或者根據(jù)業(yè)務(wù)邏輯更好的理解代碼。第二個(gè)是實(shí)現(xiàn)代碼的邏輯提取與復(fù)用,當(dāng)然
mixin
也可以實(shí)現(xiàn)邏輯提取與復(fù)用,但是像前面所說的,多個(gè)mixin
作用在同一個(gè)組件時(shí),很難看出property
是來源于哪個(gè)mixin
,來源不清楚,另外,多個(gè)mixin
的property
存在變量命名沖突的風(fēng)險(xiǎn)。而Composition API
剛好解決了這兩個(gè)問題。
通俗的講:
沒有Composition API
之前vue相關(guān)業(yè)務(wù)的代碼需要配置到option的特定的區(qū)域,中小型項(xiàng)目是沒有問題的,但是在大型項(xiàng)目中會(huì)導(dǎo)致后期的維護(hù)性比較復(fù)雜,同時(shí)代碼可復(fù)用性不高。Vue3.x中的composition-api就是為了解決這個(gè)問題而生的
compositon api提供了以下幾個(gè)函數(shù):
setup
ref
reactive
watchEffect
watch
computed
toRefs
生命周期的
hooks
都說Composition API與React Hook很像,說說區(qū)別
從React Hook的實(shí)現(xiàn)角度看,React Hook是根據(jù)useState調(diào)用的順序來確定下一次重渲染時(shí)的state是來源于哪個(gè)useState,所以出現(xiàn)了以下限制
不能在循環(huán)、條件、嵌套函數(shù)中調(diào)用Hook
必須確保總是在你的React函數(shù)的頂層調(diào)用Hook
useEffect、useMemo
等函數(shù)必須手動(dòng)確定依賴關(guān)系
而Composition API是基于Vue的響應(yīng)式系統(tǒng)實(shí)現(xiàn)的,與React Hook的相比
聲明在
setup
函數(shù)內(nèi),一次組件實(shí)例化只調(diào)用一次setup
,而React Hook每次重渲染都需要調(diào)用Hook,使得React的GC比Vue更有壓力,性能也相對(duì)于Vue來說也較慢Compositon API
的調(diào)用不需要顧慮調(diào)用順序,也可以在循環(huán)、條件、嵌套函數(shù)中使用響應(yīng)式系統(tǒng)自動(dòng)實(shí)現(xiàn)了依賴收集,進(jìn)而組件的部分的性能優(yōu)化由Vue內(nèi)部自己完成,而
React Hook
需要手動(dòng)傳入依賴,而且必須必須保證依賴的順序,讓useEffect
、useMemo
等函數(shù)正確的捕獲依賴變量,否則會(huì)由于依賴不正確使得組件性能下降。
雖然Compositon API
看起來比React Hook
好用,但是其設(shè)計(jì)思想也是借鑒React Hook
的。
垃圾回收
對(duì)于在JavaScript中的字符串,對(duì)象,數(shù)組是沒有固定大小的,只有當(dāng)對(duì)他們進(jìn)行動(dòng)態(tài)分配存儲(chǔ)時(shí),解釋器就會(huì)分配內(nèi)存來存儲(chǔ)這些數(shù)據(jù),當(dāng)JavaScript的解釋器消耗完系統(tǒng)中所有可用的內(nèi)存時(shí),就會(huì)造成系統(tǒng)崩潰。
內(nèi)存泄漏,在某些情況下,不再使用到的變量所占用內(nèi)存沒有及時(shí)釋放,導(dǎo)致程序運(yùn)行中,內(nèi)存越占越大,極端情況下可以導(dǎo)致系統(tǒng)崩潰,服務(wù)器宕機(jī)。
JavaScript有自己的一套垃圾回收機(jī)制,JavaScript的解釋器可以檢測(cè)到什么時(shí)候程序不再使用這個(gè)對(duì)象了(數(shù)據(jù)),就會(huì)把它所占用的內(nèi)存釋放掉。
針對(duì)JavaScript的來及回收機(jī)制有以下兩種方法(常用):標(biāo)記清除,引用計(jì)數(shù)
標(biāo)記清除
v8 的垃圾回收機(jī)制基于分代回收機(jī)制,這個(gè)機(jī)制又基于世代假說,這個(gè)假說有兩個(gè)特點(diǎn),一是新生的對(duì)象容易早死,另一個(gè)是不死的對(duì)象會(huì)活得更久?;谶@個(gè)假說,v8 引擎將內(nèi)存分為了新生代和老生代。
新創(chuàng)建的對(duì)象或者只經(jīng)歷過一次的垃圾回收的對(duì)象被稱為新生代。經(jīng)歷過多次垃圾回收的對(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è)算法分為三步:
首先檢查 From 空間的存活對(duì)象,如果對(duì)象存活則判斷對(duì)象是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動(dòng) To 空間。
如果對(duì)象不存活,則釋放對(duì)象的空間。
最后將 From 空間和 To 空間角色進(jìn)行交換。
新生代對(duì)象晉升到老生代有兩個(gè)條件:
第一個(gè)是判斷是對(duì)象否已經(jīng)經(jīng)過一次 Scavenge 回收。若經(jīng)歷過,則將對(duì)象從 From 空間復(fù)制到老生代中;若沒有經(jīng)歷,則復(fù)制到 To 空間。
第二個(gè)是 To 空間的內(nèi)存使用占比是否超過限制。當(dāng)對(duì)象從 From 空間復(fù)制到 To 空間時(shí),若 To 空間使用超過 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é)束后清除掉那些沒有標(biāo)記的對(duì)象。由于標(biāo)記清除后會(huì)造成很多的內(nèi)存碎片,不便于后面的內(nèi)存分配。所以了解決內(nèi)存碎片的問題引入了標(biāo)記壓縮法。
由于在進(jìn)行垃圾回收的時(shí)候會(huì)暫停應(yīng)用的邏輯,對(duì)于新生代方法由于內(nèi)存小,每次停頓的時(shí)間不會(huì)太長,但對(duì)于老生代來說每次垃圾回收的時(shí)間長,停頓會(huì)造成很大的影響。 為了解決這個(gè)問題 V8 引入了增量標(biāo)記的方法,將一次停頓進(jìn)行的過程分為了多步,每次執(zhí)行完一小步就讓運(yùn)行邏輯執(zhí)行一會(huì),就這樣交替運(yùn)行
Proxy代理
proxy在目標(biāo)對(duì)象的外層搭建了一層攔截,外界對(duì)目標(biāo)對(duì)象的某些操作,必須通過這層攔截
var proxy = new Proxy(target, handler);
new Proxy()
表示生成一個(gè)Proxy實(shí)例,target
參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler
參數(shù)也是一個(gè)對(duì)象,用來定制攔截行為
var target = { ? name: 'poetries' }; var logHandler = { ? get: function(target, key) { ? ? console.log(`${key} 被讀取`); ? ? return target[key]; ? }, ? set: function(target, key, value) { ? ? console.log(`${key} 被設(shè)置為 ${value}`); ? ? target[key] = value; ? } } var targetWithLog = new Proxy(target, logHandler); targetWithLog.name; // 控制臺(tái)輸出:name 被讀取 targetWithLog.name = 'others'; // 控制臺(tái)輸出:name 被設(shè)置為 others console.log(target.name); // 控制臺(tái)輸出: others
targetWithLog
?讀取屬性的值時(shí),實(shí)際上執(zhí)行的是?logHandler.get
?:在控制臺(tái)輸出信息,并且讀取被代理對(duì)象?target
?的屬性。在?
targetWithLog
?設(shè)置屬性值時(shí),實(shí)際上執(zhí)行的是?logHandler.set
?:在控制臺(tái)輸出信息,并且設(shè)置被代理對(duì)象?target
?的屬性的值
// 由于攔截函數(shù)總是返回35,所以訪問任何屬性都得到35var proxy = new Proxy({}, { ?get: function(target, property) { ? ?return 35; ?} }); proxy.time // 35proxy.name // 35proxy.title // 35
Proxy 實(shí)例也可以作為其他對(duì)象的原型對(duì)象
var proxy = new Proxy({}, { ?get: function(target, property) { ? ?return 35; ?} });let obj = Object.create(proxy); obj.time // 35
proxy
對(duì)象是obj
對(duì)象的原型,obj
對(duì)象本身并沒有time
屬性,所以根據(jù)原型鏈,會(huì)在proxy
對(duì)象上讀取該屬性,導(dǎo)致被攔截
Proxy的作用
對(duì)于代理模式?Proxy
?的作用主要體現(xiàn)在三個(gè)方面
攔截和監(jiān)視外部對(duì)對(duì)象的訪問
降低函數(shù)或類的復(fù)雜度
在復(fù)雜操作前對(duì)操作進(jìn)行校驗(yàn)或?qū)λ栀Y源進(jìn)行管理
Proxy所能代理的范圍--handler
實(shí)際上 handler 本身就是ES6所新設(shè)計(jì)的一個(gè)對(duì)象.它的作用就是用來 自定義代理對(duì)象的各種可代理操作 。它本身一共有13中方法,每種方法都可以代理一種操作.其13種方法如下
// 在讀取代理對(duì)象的原型時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getPrototypeOf(proxy) 時(shí)。handler.getPrototypeOf()// 在設(shè)置代理對(duì)象的原型時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.setPrototypeOf(proxy, null) 時(shí)。handler.setPrototypeOf()// 在判斷一個(gè)代理對(duì)象是否是可擴(kuò)展時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.isExtensible(proxy) 時(shí)。handler.isExtensible()// 在讓一個(gè)代理對(duì)象不可擴(kuò)展時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.preventExtensions(proxy) 時(shí)。handler.preventExtensions()// 在獲取代理對(duì)象某個(gè)屬性的屬性描述時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyDescriptor(proxy, "foo") 時(shí)。handler.getOwnPropertyDescriptor()// 在定義代理對(duì)象某個(gè)屬性時(shí)的屬性描述時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.defineProperty(proxy, "foo", {}) 時(shí)。andler.defineProperty()// 在判斷代理對(duì)象是否擁有某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 "foo" in proxy 時(shí)。handler.has()// 在讀取代理對(duì)象的某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy.foo 時(shí)。handler.get()// 在給代理對(duì)象的某個(gè)屬性賦值時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy.foo = 1 時(shí)。handler.set()// 在刪除代理對(duì)象的某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 delete proxy.foo 時(shí)。handler.deleteProperty()// 在獲取代理對(duì)象的所有屬性鍵時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyNames(proxy) 時(shí)。handler.ownKeys()// 在調(diào)用一個(gè)目標(biāo)對(duì)象為函數(shù)的代理對(duì)象時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy() 時(shí)。handler.apply()// 在給一個(gè)目標(biāo)對(duì)象為構(gòu)造函數(shù)的代理對(duì)象構(gòu)造實(shí)例時(shí)觸發(fā)該操作,比如在執(zhí)行new proxy() 時(shí)。handler.construct()
為何Proxy不能被Polyfill
如class可以用
function
模擬;promise
可以用callback
模擬但是proxy不能用
Object.defineProperty
模擬
目前谷歌的polyfill只能實(shí)現(xiàn)部分的功能,如get、set?https://github.com/GoogleChro...
// commonJS requireconst proxyPolyfill = require('proxy-polyfill/src/proxy')();// Your environment may also support transparent rewriting of commonJS to ES6:import ProxyPolyfillBuilder from 'proxy-polyfill/src/proxy';const proxyPolyfill = ProxyPolyfillBuilder();// Then use...const myProxy = new proxyPolyfill(...);
列舉幾個(gè)css中可繼承和不可繼承的元素
不可繼承的:
display、margin、border、padding、background、height、min-height、max-height、width、min-width、max-width、overflow、position、left、right、top、bottom、z-index、float、clear、table-layout、vertical-align
所有元素可繼承:
visibility
和cursor
。內(nèi)聯(lián)元素可繼承:
letter-spacing、word-spacing、white-space、line-height、color、font、font-family、font-size、font-style、font-variant、font-weight、text-decoration、text-transform、direction
。終端塊狀元素可繼承:
text-indent和text-align
。列表元素可繼承:
list-style、list-style-type、list-style-position、list-style-imag
e`。
transition和animation的區(qū)別
Animation
和transition
大部分屬性是相同的,他們都是隨時(shí)間改變?cè)氐膶傩灾?,他們的主要區(qū)別是transition
需要觸發(fā)一個(gè)事件才能改變屬性,而animation
不需要觸發(fā)任何事件的情況下才會(huì)隨時(shí)間改變屬性值,并且transition
為2幀,從from .... to
,而animation
可以一幀一幀的
OSI七層模型
ISO
為了更好的使網(wǎng)絡(luò)應(yīng)用更為普及,推出了OSI
參考模型。
(1)應(yīng)用層
OSI
參考模型中最靠近用戶的一層,是為計(jì)算機(jī)用戶提供應(yīng)用接口,也為用戶直接提供各種網(wǎng)絡(luò)服務(wù)。我們常見應(yīng)用層的網(wǎng)絡(luò)服務(wù)協(xié)議有:HTTP
,HTTPS
,FTP
,POP3
、SMTP
等。
在客戶端與服務(wù)器中經(jīng)常會(huì)有數(shù)據(jù)的請(qǐng)求,這個(gè)時(shí)候就是會(huì)用到
http(hyper text transfer protocol)(超文本傳輸協(xié)議)
或者https
.在后端設(shè)計(jì)數(shù)據(jù)接口時(shí),我們常常使用到這個(gè)協(xié)議。FTP
是文件傳輸協(xié)議,在開發(fā)過程中,個(gè)人并沒有涉及到,但是我想,在一些資源網(wǎng)站,比如百度網(wǎng)盤
`迅雷`應(yīng)該是基于此協(xié)議的。SMTP
是simple mail transfer protocol(簡單郵件傳輸協(xié)議)
。在一個(gè)項(xiàng)目中,在用戶郵箱驗(yàn)證碼登錄的功能時(shí),使用到了這個(gè)協(xié)議。
(2)表示層
表示層提供各種用于應(yīng)用層數(shù)據(jù)的編碼和轉(zhuǎn)換功能,確保一個(gè)系統(tǒng)的應(yīng)用層發(fā)送的數(shù)據(jù)能被另一個(gè)系統(tǒng)的應(yīng)用層識(shí)別。如果必要,該層可提供一種標(biāo)準(zhǔn)表示形式,用于將計(jì)算機(jī)內(nèi)部的多種數(shù)據(jù)格式轉(zhuǎn)換成通信中采用的標(biāo)準(zhǔn)表示形式。數(shù)據(jù)壓縮和加密也是表示層可提供的轉(zhuǎn)換功能之一。
在項(xiàng)目開發(fā)中,為了方便數(shù)據(jù)傳輸,可以使用base64
對(duì)數(shù)據(jù)進(jìn)行編解碼。如果按功能來劃分,base64
應(yīng)該是工作在表示層。
(3)會(huì)話層
會(huì)話層就是負(fù)責(zé)建立、管理和終止表示層實(shí)體之間的通信會(huì)話。該層的通信由不同設(shè)備中的應(yīng)用程序之間的服務(wù)請(qǐng)求和響應(yīng)組成。
(4)傳輸層
傳輸層建立了主機(jī)端到端的鏈接,傳輸層的作用是為上層協(xié)議提供端到端的可靠和透明的數(shù)據(jù)傳輸服務(wù),包括處理差錯(cuò)控制和流量控制等問題。該層向高層屏蔽了下層數(shù)據(jù)通信的細(xì)節(jié),使高層用戶看到的只是在兩個(gè)傳輸實(shí)體間的一條主機(jī)到主機(jī)的、可由用戶控制和設(shè)定的、可靠的數(shù)據(jù)通路。我們通常說的,TCP
?UDP
就是在這一層。端口號(hào)既是這里的“端”。
(5)網(wǎng)絡(luò)層
本層通過IP
尋址來建立兩個(gè)節(jié)點(diǎn)之間的連接,為源端的運(yùn)輸層送來的分組,選擇合適的路由和交換節(jié)點(diǎn),正確無誤地按照地址傳送給目的端的運(yùn)輸層。就是通常說的IP
層。這一層就是我們經(jīng)常說的IP
協(xié)議層。IP
協(xié)議是Internet
的基礎(chǔ)。我們可以這樣理解,網(wǎng)絡(luò)層規(guī)定了數(shù)據(jù)包的傳輸路線,而傳輸層則規(guī)定了數(shù)據(jù)包的傳輸方式。
(6)數(shù)據(jù)鏈路層
將比特組合成字節(jié),再將字節(jié)組合成幀,使用鏈路層地址 (以太網(wǎng)使用MAC地址)來訪問介質(zhì),并進(jìn)行差錯(cuò)檢測(cè)。
網(wǎng)絡(luò)層與數(shù)據(jù)鏈路層的對(duì)比,通過上面的描述,我們或許可以這樣理解,網(wǎng)絡(luò)層是規(guī)劃了數(shù)據(jù)包的傳輸路線,而數(shù)據(jù)鏈路層就是傳輸路線。不過,在數(shù)據(jù)鏈路層上還增加了差錯(cuò)控制的功能。
(7)物理層
實(shí)際最終信號(hào)的傳輸是通過物理層實(shí)現(xiàn)的。通過物理介質(zhì)傳輸比特流。規(guī)定了電平、速度和電纜針腳。常用設(shè)備有(各種物理設(shè)備)集線器、中繼器、調(diào)制解調(diào)器、網(wǎng)線、雙絞線、同軸電纜。這些都是物理層的傳輸介質(zhì)。
OSI七層模型通信特點(diǎn):對(duì)等通信?對(duì)等通信,為了使數(shù)據(jù)分組從源傳送到目的地,源端OSI模型的每一層都必須與目的端的對(duì)等層進(jìn)行通信,這種通信方式稱為對(duì)等層通信。在每一層通信過程中,使用本層自己協(xié)議進(jìn)行通信。
參考?前端進(jìn)階面試題詳細(xì)解答
定時(shí)器與requestAnimationFrame、requestIdleCallback
1. setTimeout
setTimeout的運(yùn)行機(jī)制:執(zhí)行該語句時(shí),是立即把當(dāng)前定時(shí)器代碼推入事件隊(duì)列,當(dāng)定時(shí)器在事件列表中滿足設(shè)置的時(shí)間值時(shí)將傳入的函數(shù)加入任務(wù)隊(duì)列,之后的執(zhí)行就交給任務(wù)隊(duì)列負(fù)責(zé)。但是如果此時(shí)任務(wù)隊(duì)列不為空,則需等待,所以執(zhí)行定時(shí)器內(nèi)代碼的時(shí)間可能會(huì)大于設(shè)置的時(shí)間
setTimeout(() => { ? ?console.log(1); }, 0)console.log(2);
輸出 2, 1;
setTimeout
的第二個(gè)參數(shù)表示在執(zhí)行代碼前等待的毫秒數(shù)。上面代碼中,設(shè)置為0,表面意思為 執(zhí)行代碼前等待的毫秒數(shù)為0,即立即執(zhí)行。但實(shí)際上的運(yùn)行結(jié)果我們也看到了,并不是表面上看起來的樣子,千萬不要被欺騙了。
實(shí)際上,上面的代碼并不是立即執(zhí)行的,這是因?yàn)?code>setTimeout有一個(gè)最小執(zhí)行時(shí)間,HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()
的第二個(gè)參數(shù)的最小值(最短間隔)不得低于4毫秒
。 當(dāng)指定的時(shí)間低于該時(shí)間時(shí),瀏覽器會(huì)用最小允許的時(shí)間作為setTimeout
的時(shí)間間隔,也就是說即使我們把setTimeout
的延遲時(shí)間設(shè)置為0,實(shí)際上可能為?4毫秒后才事件推入任務(wù)隊(duì)列
。
定時(shí)器代碼在被推送到任務(wù)隊(duì)列前,會(huì)先被推入到事件列表中,當(dāng)定時(shí)器在事件列表中滿足設(shè)置的時(shí)間值時(shí)會(huì)被推到任務(wù)隊(duì)列,但是如果此時(shí)任務(wù)隊(duì)列不為空,則需等待,所以執(zhí)行定時(shí)器內(nèi)代碼的時(shí)間可能會(huì)大于設(shè)置的時(shí)間
setTimeout(() => { ? ?console.log(111); }, 100);
上面代碼表示100ms
后執(zhí)行console.log(111)
,但實(shí)際上實(shí)行的時(shí)間肯定是大于100ms后的, 100ms 只是表示 100ms 后將任務(wù)加入到"任務(wù)隊(duì)列"中,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時(shí)很長,有可能要等很久,所以并沒有辦法保證,回調(diào)函數(shù)一定會(huì)在setTimeout()
指定的時(shí)間執(zhí)行。
2. setTimeout 和 setInterval區(qū)別
setTimeout
: 指定延期后調(diào)用函數(shù),每次setTimeout
計(jì)時(shí)到后就會(huì)去執(zhí)行,然后執(zhí)行一段時(shí)間后才繼續(xù)setTimeout
,中間就多了誤差,(誤差多少與代碼的執(zhí)行時(shí)間有關(guān))。setInterval
:以指定周期調(diào)用函數(shù),而setInterval
則是每次都精確的隔一段時(shí)間推入一個(gè)事件(但是,事件的執(zhí)行時(shí)間不一定就不準(zhǔn)確,還有可能是這個(gè)事件還沒執(zhí)行完畢,下一個(gè)事件就來了).
btn.onclick = function(){ ? ?setTimeout(function(){ ? ? ? ?console.log(1); ? ?},250); }
擊該按鈕后,首先將onclick
事件處理程序加入隊(duì)列。該程序執(zhí)行后才設(shè)置定時(shí)器,再有250ms
后,指定的代碼才被添加到隊(duì)列中等待執(zhí)行。 如果上面代碼中的onclick
事件處理程序執(zhí)行了300ms
,那么定時(shí)器的代碼至少要在定時(shí)器設(shè)置之后的300ms
后才會(huì)被執(zhí)行。隊(duì)列中所有的代碼都要等到j(luò)avascript進(jìn)程空閑之后才能執(zhí)行,而不管它們是如何添加到隊(duì)列中的。
如圖所示,盡管在255ms
處添加了定時(shí)器代碼,但這時(shí)候還不能執(zhí)行,因?yàn)?code>onclick事件處理程序仍在運(yùn)行。定時(shí)器代碼最早能執(zhí)行的時(shí)機(jī)是在300ms
處,即onclick
事件處理程序結(jié)束之后。
3. setInterval存在的一些問題:
JavaScript中使用?setInterval
?開啟輪詢。定時(shí)器代碼可能在代碼再次被添加到隊(duì)列之前還沒有完成執(zhí)行,結(jié)果導(dǎo)致定時(shí)器代碼連續(xù)運(yùn)行好幾次,而之間沒有任何停頓。而javascript引擎對(duì)這個(gè)問題的解決是:當(dāng)使用setInterval()
時(shí),僅當(dāng)沒有該定時(shí)器的任何其他代碼實(shí)例時(shí),才將定時(shí)器代碼添加到隊(duì)列中。這確保了定時(shí)器代碼加入到隊(duì)列中的最小時(shí)間間隔為指定間隔。
但是,這樣會(huì)導(dǎo)致兩個(gè)問題:
某些間隔被跳過;
多個(gè)定時(shí)器的代碼執(zhí)行之間的間隔可能比預(yù)期的小
假設(shè),某個(gè)onclick
事件處理程序使用setInterval()
設(shè)置了200ms
間隔的定時(shí)器。如果事件處理程序花了300ms
多一點(diǎn)時(shí)間完成,同時(shí)定時(shí)器代碼也花了差不多的時(shí)間,就會(huì)同時(shí)出現(xiàn)跳過某間隔的情況
例子中的第一個(gè)定時(shí)器是在205ms
處添加到隊(duì)列中的,但是直到過了300ms
處才能執(zhí)行。當(dāng)執(zhí)行這個(gè)定時(shí)器代碼時(shí),在405ms處又給隊(duì)列添加了另一個(gè)副本。在下一個(gè)間隔,即605ms處,第一個(gè)定時(shí)器代碼仍在運(yùn)行,同時(shí)在隊(duì)列中已經(jīng)有了一個(gè)定時(shí)器代碼的實(shí)例。結(jié)果是,在這個(gè)時(shí)間點(diǎn)上的定時(shí)器代碼不會(huì)被添加到隊(duì)列中
使用setTimeout
構(gòu)造輪詢能保證每次輪詢的間隔。
setTimeout(function () { console.log('我被調(diào)用了'); setTimeout(arguments.callee, 100); }, 100);
callee
?是?arguments
?對(duì)象的一個(gè)屬性。它可以用于引用該函數(shù)的函數(shù)體內(nèi)當(dāng)前正在執(zhí)行的函數(shù)。在嚴(yán)格模式下,第5版 ECMAScript (ES5) 禁止使用arguments.callee()
。當(dāng)一個(gè)函數(shù)必須調(diào)用自身的時(shí)候, 避免使用?arguments.callee()
, 通過要么給函數(shù)表達(dá)式一個(gè)名字,要么使用一個(gè)函數(shù)聲明.
setTimeout(function fn(){ ? ?console.log('我被調(diào)用了'); ? ?setTimeout(fn, 100); },100);
這個(gè)模式鏈?zhǔn)秸{(diào)用了setTimeout()
,每次函數(shù)執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)新的定時(shí)器。第二個(gè)setTimeout()
調(diào)用當(dāng)前執(zhí)行的函數(shù),并為其設(shè)置另外一個(gè)定時(shí)器。這樣做的好處是,在前一個(gè)定時(shí)器代碼執(zhí)行完之前,不會(huì)向隊(duì)列插入新的定時(shí)器代碼,確保不會(huì)有任何缺失的間隔。而且,它可以保證在下一次定時(shí)器代碼執(zhí)行之前,至少要等待指定的間隔,避免了連續(xù)的運(yùn)行。
4. requestAnimationFrame
4.1?60fps
與設(shè)備刷新率
目前大多數(shù)設(shè)備的屏幕刷新率為60次/秒
,如果在頁面中有一個(gè)動(dòng)畫或者漸變效果,或者用戶正在滾動(dòng)頁面,那么瀏覽器渲染動(dòng)畫或頁面的每一幀的速率也需要跟設(shè)備屏幕的刷新率保持一致。
卡頓:其中每個(gè)幀的預(yù)算時(shí)間僅比16毫秒
多一點(diǎn)(1秒/ 60 = 16.6毫秒
)。但實(shí)際上,瀏覽器有整理工作要做,因此您的所有工作是需要在10毫秒
內(nèi)完成。如果無法符合此預(yù)算,幀率將下降,并且內(nèi)容會(huì)在屏幕上抖動(dòng)。此現(xiàn)象通常稱為卡頓,會(huì)對(duì)用戶體驗(yàn)產(chǎn)生負(fù)面影響。
跳幀: 假如動(dòng)畫切換在 16ms, 32ms, 48ms時(shí)分別切換,跳幀就是假如到了32ms,其他任務(wù)還未執(zhí)行完成,沒有去執(zhí)行動(dòng)畫切幀,等到開始進(jìn)行動(dòng)畫的切幀,已經(jīng)到了該執(zhí)行48ms的切幀。就好比你玩游戲的時(shí)候卡了,過了一會(huì),你再看畫面,它不會(huì)停留你卡的地方,或者這時(shí)你的角色已經(jīng)掛掉了。必須在下一幀開始之前就已經(jīng)繪制完畢;
Chrome devtool 查看實(shí)時(shí) FPS, 打開 More tools => Rendering, 勾選 FPS meter
4.2?requestAnimationFrame
實(shí)現(xiàn)動(dòng)畫
requestAnimationFrame
是瀏覽器用于定時(shí)循環(huán)操作的一個(gè)接口,類似于setTimeout,主要用途是按幀對(duì)網(wǎng)頁進(jìn)行重繪。
在?requestAnimationFrame
?之前,主要借助?setTimeout/ setInterval
?來編寫 JS 動(dòng)畫,而動(dòng)畫的關(guān)鍵在于動(dòng)畫幀之間的時(shí)間間隔設(shè)置,這個(gè)時(shí)間間隔的設(shè)置有講究,一方面要足夠小,這樣動(dòng)畫幀之間才有連貫性,動(dòng)畫效果才顯得平滑流暢;另一方面要足夠大,確保瀏覽器有足夠的時(shí)間及時(shí)完成渲染。
顯示器有固定的刷新頻率(60Hz或75Hz),也就是說,每秒最多只能重繪60次或75次,requestAnimationFrame
的基本思想就是與這個(gè)刷新頻率保持同步,利用這個(gè)刷新頻率進(jìn)行頁面重繪。此外,使用這個(gè)API,一旦頁面不處于瀏覽器的當(dāng)前標(biāo)簽,就會(huì)自動(dòng)停止刷新。這就節(jié)省了CPU、GPU和電力。
requestAnimationFrame
?是在主線程上完成。這意味著,如果主線程非常繁忙,requestAnimationFrame
的動(dòng)畫效果會(huì)大打折扣。
requestAnimationFrame
?使用一個(gè)回調(diào)函數(shù)作為參數(shù)。這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器重繪之前調(diào)用。
requestID = window.requestAnimationFrame(callback); window.requestAnimFrame = (function(){ ? ?return ?window.requestAnimationFrame ? ? ? || ? ? ? ? ? ?window.webkitRequestAnimationFrame || ? ? ? ? ? ?window.mozRequestAnimationFrame ? ?|| ? ? ? ? ? ?window.oRequestAnimationFrame ? ? ?|| ? ? ? ? ? ?window.msRequestAnimationFrame ? ? || ? ? ? ? ? ?function( callback ){ ? ? ? ? ? ?window.setTimeout(callback, 1000 / 60); ? ? ? ?}; })();
上面的代碼按照1秒鐘60次(大約每16.7毫秒一次),來模擬requestAnimationFrame
。
5. requestIdleCallback()
MDN上的解釋:requestIdleCallback()
方法將在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì)。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)行后臺(tái)和低優(yōu)先級(jí)工作,而不會(huì)影響延遲關(guān)鍵事件,如動(dòng)畫和輸入響應(yīng)。函數(shù)一般會(huì)按先進(jìn)先調(diào)用的順序執(zhí)行,然而,如果回調(diào)函數(shù)指定了執(zhí)行超時(shí)時(shí)間timeout,則有可能為了在超時(shí)前執(zhí)行函數(shù)而打亂執(zhí)行順序。
requestAnimationFrame
會(huì)在每次屏幕刷新的時(shí)候被調(diào)用,而requestIdleCallback
則會(huì)在每次屏幕刷新時(shí),判斷當(dāng)前幀是否還有多余的時(shí)間,如果有,則會(huì)調(diào)用requestAnimationFrame
的回調(diào)函數(shù),
圖片中是兩個(gè)連續(xù)的執(zhí)行幀,大致可以理解為兩個(gè)幀的持續(xù)時(shí)間大概為16.67,圖中黃色部分就是空閑時(shí)間。所以,requestIdleCallback
?中的回調(diào)函數(shù)僅會(huì)在每次屏幕刷新并且有空閑時(shí)間時(shí)才會(huì)被調(diào)用.
利用這個(gè)特性,我們可以在動(dòng)畫執(zhí)行的期間,利用每幀的空閑時(shí)間來進(jìn)行數(shù)據(jù)發(fā)送的操作,或者一些優(yōu)先級(jí)比較低的操作,此時(shí)不會(huì)使影響到動(dòng)畫的性能,或者和requestAnimationFrame
搭配,可以實(shí)現(xiàn)一些頁面性能方面的的優(yōu)化,
react 的?fiber
?架構(gòu)也是基于?requestIdleCallback
?實(shí)現(xiàn)的, 并且在不支持的瀏覽器中提供了?polyfill
總結(jié)
從
單線程模型和任務(wù)隊(duì)列
出發(fā)理解?setTimeout(fn, 0)
,并不是立即執(zhí)行。JS 動(dòng)畫, 用
requestAnimationFrame
?會(huì)比?setInterval
?效果更好requestIdleCallback()
常用來切割長任務(wù),利用空閑時(shí)間執(zhí)行,避免主線程長時(shí)間阻塞
ES6模塊與CommonJS模塊有什么異同?
ES6 Module和CommonJS模塊的區(qū)別:
CommonJS是對(duì)模塊的淺拷?,ES6 Module是對(duì)模塊的引?,即ES6 Module只存只讀,不能改變其值,也就是指針指向不能變,類似const;
import的接?是read-only(只讀狀態(tài)),不能修改其變量值。 即不能修改其變量的指針指向,但可以改變變量內(nèi)部指針指向,可以對(duì)commonJS對(duì)重新賦值(改變指針指向),但是對(duì)ES6 Module賦值會(huì)編譯報(bào)錯(cuò)。
ES6 Module和CommonJS模塊的共同點(diǎn):
CommonJS和ES6 Module都可以對(duì)引?的對(duì)象進(jìn)?賦值,即對(duì)對(duì)象內(nèi)部屬性的值進(jìn)?改變。
選擇器權(quán)重計(jì)算方式
!important > 內(nèi)聯(lián)樣式 = 外聯(lián)樣式 > ID選擇器 > 類選擇器 = 偽類選擇器 = 屬性選擇器 > 元素選擇器 = 偽元素選擇器 > 通配選擇器 = 后代選擇器 = 兄弟選擇器
屬性后面加
!import
會(huì)覆蓋頁面內(nèi)任何位置定義的元素樣式作為
style
屬性寫在元素內(nèi)的樣式id
選擇器類選擇器
標(biāo)簽選擇器
通配符選擇器(
*
)瀏覽器自定義或繼承
同一級(jí)別:后寫的會(huì)覆蓋先寫的
css選擇器的解析原則:選擇器定位DOM元素是從右往左的方向,這樣可以盡早的過濾掉一些不必要的樣式規(guī)則和元素
類型及檢測(cè)方式
1. JS內(nèi)置類型
JavaScript 的數(shù)據(jù)類型有下圖所示
其中,前 7 種類型為基礎(chǔ)類型,最后?1 種(Object)為引用類型
,也是你需要重點(diǎn)關(guān)注的,因?yàn)樗谌粘9ぷ髦惺鞘褂玫米铑l繁,也是需要關(guān)注最多技術(shù)細(xì)節(jié)的數(shù)據(jù)類型
JavaScript
一共有8種數(shù)據(jù)類型,其中有7種基本數(shù)據(jù)類型:Undefined
、Null
、Boolean
、Number
、String
、Symbol
(es6
新增,表示獨(dú)一無二的值)和BigInt
(es10
新增);1種引用數(shù)據(jù)類型——
Object
(Object本質(zhì)上是由一組無序的名值對(duì)組成的)。里面包含?function、Array、Date
等。JavaScript不支持任何創(chuàng)建自定義類型的機(jī)制,而所有值最終都將是上述 8 種數(shù)據(jù)類型之一。引用數(shù)據(jù)類型:?對(duì)象
Object
(包含普通對(duì)象-Object
,數(shù)組對(duì)象-Array
,正則對(duì)象-RegExp
,日期對(duì)象-Date
,數(shù)學(xué)函數(shù)-Math
,函數(shù)對(duì)象-Function
)
在這里,我想先請(qǐng)你重點(diǎn)了解下面兩點(diǎn),因?yàn)楦鞣N JavaScript 的數(shù)據(jù)類型最后都會(huì)在初始化之后放在不同的內(nèi)存中,因此上面的數(shù)據(jù)類型大致可以分成兩類來進(jìn)行存儲(chǔ):
原始數(shù)據(jù)類型?:基礎(chǔ)類型存儲(chǔ)在棧內(nèi)存,被引用或拷貝時(shí),會(huì)創(chuàng)建一個(gè)完全相等的變量;占據(jù)空間小、大小固定,屬于被頻繁使用數(shù)據(jù),所以放入棧中存儲(chǔ)。
引用數(shù)據(jù)類型?:引用類型存儲(chǔ)在堆內(nèi)存,存儲(chǔ)的是地址,多個(gè)引用指向同一個(gè)地址,這里會(huì)涉及一個(gè)“共享”的概念;占據(jù)空間大、大小不固定。引用數(shù)據(jù)類型在棧中存儲(chǔ)了指針,該指針指向堆中該實(shí)體的起始地址。當(dāng)解釋器尋找引用值時(shí),會(huì)首先檢索其在棧中的地址,取得地址后從堆中獲得實(shí)體。
JavaScript 中的數(shù)據(jù)是如何存儲(chǔ)在內(nèi)存中的?
在 JavaScript 中,原始類型的賦值會(huì)完整復(fù)制變量值,而引用類型的賦值是復(fù)制引用地址。
在 JavaScript 的執(zhí)行過程中, 主要有三種類型內(nèi)存空間,分別是代碼空間
、棧空間
、堆空間
。其中的代碼空間主要是存儲(chǔ)可執(zhí)行代碼的,原始類型(Number、String、Null、Undefined、Boolean、Symbol、BigInt
)的數(shù)據(jù)值都是直接保存在“?!敝械模妙愋?Object)的值是存放在“堆”中的。因此在??臻g中(執(zhí)行上下文),原始類型存儲(chǔ)的是變量的值,而引用類型存儲(chǔ)的是其在"堆空間"中的地址,當(dāng) JavaScript 需要訪問該數(shù)據(jù)的時(shí)候,是通過棧中的引用地址來訪問的,相當(dāng)于多了一道轉(zhuǎn)手流程。
在編譯過程中,如果 JavaScript 引擎判斷到一個(gè)閉包,也會(huì)在堆空間創(chuàng)建換一個(gè)“closure(fn)”
的對(duì)象(這是一個(gè)內(nèi)部對(duì)象,JavaScript 是無法訪問的),用來保存閉包中的變量。所以閉包中的變量是存儲(chǔ)在“堆空間”中的。
JavaScript 引擎需要用棧來維護(hù)程序執(zhí)行期間上下文的狀態(tài),如果棧空間大了話,所有的數(shù)據(jù)都存放在??臻g里面,那么會(huì)影響到上下文切換的效率,進(jìn)而又影響到整個(gè)程序的執(zhí)行效率。通常情況下,??臻g都不會(huì)設(shè)置太大,主要用來存放一些原始類型的小數(shù)據(jù)。而引用類型的數(shù)據(jù)占用的空間都比較大,所以這一類數(shù)據(jù)會(huì)被存放到堆中,堆空間很大,能存放很多大的數(shù)據(jù),不過缺點(diǎn)是分配內(nèi)存和回收內(nèi)存都會(huì)占用一定的時(shí)間。因此需要“?!焙汀岸选眱煞N空間。
題目一:初出茅廬
let a = { ?name: 'lee', ?age: 18}let b = a;console.log(a.name); ?//第一個(gè)consoleb.name = 'son';console.log(a.name); ?//第二個(gè)consoleconsole.log(b.name); ?//第三個(gè)console
這道題比較簡單,我們可以看到第一個(gè) console 打出來 name 是 'lee',這應(yīng)該沒什么疑問;但是在執(zhí)行了 b.name='son' 之后,結(jié)果你會(huì)發(fā)現(xiàn) a 和 b 的屬性 name 都是 'son',第二個(gè)和第三個(gè)打印結(jié)果是一樣的,這里就體現(xiàn)了引用類型的“共享”的特性,即這兩個(gè)值都存在同一塊內(nèi)存中共享,一個(gè)發(fā)生了改變,另外一個(gè)也隨之跟著變化。
你可以直接在 Chrome 控制臺(tái)敲一遍,深入理解一下這部分概念。下面我們?cè)倏匆欢未a,它是比題目一稍復(fù)雜一些的對(duì)象屬性變化問題。
題目二:漸入佳境
let a = { ?name: 'Julia', ?age: 20}function change(o) { ?o.age = 24; ?o = { ? ?name: 'Kath', ? ?age: 30 ?} ?return o; }let b = change(a); ? ? // 注意這里沒有new,后面new相關(guān)會(huì)有專門文章講解console.log(b.age); ? ?// 第一個(gè)consoleconsole.log(a.age); ? ?// 第二個(gè)console
這道題涉及了?function
,你通過上述代碼可以看到第一個(gè)?console
?的結(jié)果是?30
,b
?最后打印結(jié)果是?{name: "Kath", age: 30}
;第二個(gè)?console
?的返回結(jié)果是?24
,而?a
?最后的打印結(jié)果是?{name: "Julia", age: 24}
。
是不是和你預(yù)想的有些區(qū)別?你要注意的是,這里的?function
?和?return
?帶來了不一樣的東西。
原因在于:函數(shù)傳參進(jìn)來的?o
,傳遞的是對(duì)象在堆中的內(nèi)存地址值,通過調(diào)用?o.age = 24
(第 7 行代碼)確實(shí)改變了?a
?對(duì)象的?age
?屬性;但是第 12 行代碼的?return
?卻又把?o
?變成了另一個(gè)內(nèi)存地址,將?{name: "Kath", age: 30}
?存入其中,最后返回?b
?的值就變成了?{name: "Kath", age: 30}
。而如果把第 12 行去掉,那么?b
?就會(huì)返回?undefined
2. 數(shù)據(jù)類型檢測(cè)
(1)typeof
typeof 對(duì)于原始類型來說,除了 null 都可以顯示正確的類型
console.log(typeof 2); ? ? ? ? ? ? ? // numberconsole.log(typeof true); ? ? ? ? ? ?// booleanconsole.log(typeof 'str'); ? ? ? ? ? // stringconsole.log(typeof []); ? ? ? ? ? ? ?// object ? ? []數(shù)組的數(shù)據(jù)類型在 typeof 中被解釋為 objectconsole.log(typeof function(){}); ? ?// functionconsole.log(typeof {}); ? ? ? ? ? ? ?// objectconsole.log(typeof undefined); ? ? ? // undefinedconsole.log(typeof null); ? ? ? ? ? ?// object ? ? null 的數(shù)據(jù)類型被 typeof 解釋為 object
typeof
?對(duì)于對(duì)象來說,除了函數(shù)都會(huì)顯示?object
,所以說?typeof
?并不能準(zhǔn)確判斷變量到底是什么類型,所以想判斷一個(gè)對(duì)象的正確類型,這時(shí)候可以考慮使用?instanceof
(2)instanceof
instanceof
?可以正確的判斷對(duì)象的類型,因?yàn)閮?nèi)部機(jī)制是通過判斷對(duì)象的原型鏈中是不是能找到類型的?prototype
console.log(2 instanceof Number); ? ? ? ? ? ? ? ? ? ?// falseconsole.log(true instanceof Boolean); ? ? ? ? ? ? ? ?// false console.log('str' instanceof String); ? ? ? ? ? ? ? ?// false ?console.log([] instanceof Array); ? ? ? ? ? ? ? ? ? ?// trueconsole.log(function(){} instanceof Function); ? ? ? // trueconsole.log({} instanceof Object); ? ? ? ? ? ? ? ? ? // true ? ?// console.log(undefined instanceof Undefined);// console.log(null instanceof Null);
instanceof
?可以準(zhǔn)確地判斷復(fù)雜引用數(shù)據(jù)類型,但是不能正確判斷基礎(chǔ)數(shù)據(jù)類型;而?
typeof
?也存在弊端,它雖然可以判斷基礎(chǔ)數(shù)據(jù)類型(null
?除外),但是引用數(shù)據(jù)類型中,除了?function
?類型以外,其他的也無法判斷
// 我們也可以試著實(shí)現(xiàn)一下 instanceoffunction _instanceof(left, right) { ? ?// 由于instance要檢測(cè)的是某對(duì)象,需要有一個(gè)前置判斷條件 ? ?//基本數(shù)據(jù)類型直接返回false ? ?if(typeof left !== 'object' || left === null) return false; ? ?// 獲得類型的原型 ? ?let prototype = right.prototype ? ?// 獲得對(duì)象的原型 ? ?left = left.__proto__ ? ?// 判斷對(duì)象的類型是否等于類型的原型 ? ?while (true) { ? ? ? ?if (left === null) ? ? ? ? ? ?return false ? ? ? ?if (prototype === left) ? ? ? ? ? ?return true ? ? ? ?left = left.__proto__ ? ?} }console.log('test', _instanceof(null, Array)) // falseconsole.log('test', _instanceof([], Array)) // trueconsole.log('test', _instanceof('', Array)) // falseconsole.log('test', _instanceof({}, Object)) // true
(3)constructor
console.log((2).constructor === Number); // trueconsole.log((true).constructor === Boolean); // trueconsole.log(('str').constructor === String); // trueconsole.log(([]).constructor === Array); // trueconsole.log((function() {}).constructor === Function); // trueconsole.log(({}).constructor === Object); // true
這里有一個(gè)坑,如果我創(chuàng)建一個(gè)對(duì)象,更改它的原型,constructor
就會(huì)變得不可靠了
function Fn(){};Fn.prototype=new Array();var f=new Fn();console.log(f.constructor===Fn); ? ?// falseconsole.log(f.constructor===Array); // true
(4)Object.prototype.toString.call()
toString()
?是?Object
?的原型方法,調(diào)用該方法,可以統(tǒng)一返回格式為?“[object Xxx]”
?的字符串,其中?Xxx
?就是對(duì)象的類型。對(duì)于?Object
?對(duì)象,直接調(diào)用?toString()
?就能返回?[object Object]
;而對(duì)于其他對(duì)象,則需要通過?call
?來調(diào)用,才能返回正確的類型信息。我們來看一下代碼。
Object.prototype.toString({}) ? ? ? // "[object Object]"Object.prototype.toString.call({}) ?// 同上結(jié)果,加上call也okObject.prototype.toString.call(1) ? ?// "[object Number]"Object.prototype.toString.call('1') ?// "[object String]"Object.prototype.toString.call(true) ?// "[object Boolean]"Object.prototype.toString.call(function(){}) ?// "[object Function]"Object.prototype.toString.call(null) ? //"[object Null]"Object.prototype.toString.call(undefined) //"[object Undefined]"Object.prototype.toString.call(/123/g) ? ?//"[object RegExp]"Object.prototype.toString.call(new Date()) //"[object Date]"Object.prototype.toString.call([]) ? ? ? //"[object Array]"Object.prototype.toString.call(document) ?//"[object HTMLDocument]"Object.prototype.toString.call(window) ? //"[object Window]"// 從上面這段代碼可以看出,Object.prototype.toString.call() 可以很好地判斷引用類型,甚至可以把 document 和 window 都區(qū)分開來。
實(shí)現(xiàn)一個(gè)全局通用的數(shù)據(jù)類型判斷方法,來加深你的理解,代碼如下
function getType(obj){ ?let type ?= typeof obj; ?if (type !== "object") { ? ?// 先進(jìn)行typeof判斷,如果是基礎(chǔ)數(shù)據(jù)類型,直接返回 ? ?return type; ?} ?// 對(duì)于typeof返回結(jié)果是object的,再進(jìn)行如下的判斷,正則返回結(jié)果 ?return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); ?// 注意正則中間有個(gè)空格}/* 代碼驗(yàn)證,需要注意大小寫,哪些是typeof判斷,哪些是toString判斷?思考下 */getType([]) ? ? // "Array" typeof []是object,因此toString返回getType('123') ?// "string" typeof 直接返回getType(window) // "Window" toString返回getType(null) ? // "Null"首字母大寫,typeof null是object,需toString來判斷getType(undefined) ? // "undefined" typeof 直接返回getType() ? ? ? ? ? ?// "undefined" typeof 直接返回getType(function(){}) // "function" typeof能判斷,因此首字母小寫getType(/123/g) ? ? ?//"RegExp" toString返回
小結(jié)
typeof
直接在計(jì)算機(jī)底層基于數(shù)據(jù)類型的值(二進(jìn)制)進(jìn)行檢測(cè)
typeof null
為object
?原因是對(duì)象存在在計(jì)算機(jī)中,都是以000
開始的二進(jìn)制存儲(chǔ),所以檢測(cè)出來的結(jié)果是對(duì)象typeof
?普通對(duì)象/數(shù)組對(duì)象/正則對(duì)象/日期對(duì)象 都是object
typeof NaN === 'number'
instanceof
檢測(cè)當(dāng)前實(shí)例是否屬于這個(gè)類的
底層機(jī)制:只要當(dāng)前類出現(xiàn)在實(shí)例的原型上,結(jié)果都是true
不能檢測(cè)基本數(shù)據(jù)類型
constructor
支持基本類型
constructor可以隨便改,也不準(zhǔn)
Object.prototype.toString.call([val])
返回當(dāng)前實(shí)例所屬類信息
判斷?Target
?的類型,單單用?typeof
?并無法完全滿足,這其實(shí)并不是?bug
,本質(zhì)原因是?JS
?的萬物皆對(duì)象的理論。因此要真正完美判斷時(shí),我們需要區(qū)分對(duì)待:
基本類型(
null
): 使用?String(null)
基本類型(
string / number / boolean / undefined
) +?function
: - 直接使用?typeof
即可其余引用類型(
Array / Date / RegExp Error
): 調(diào)用toString
后根據(jù)[object XXX]
進(jìn)行判斷
3. 數(shù)據(jù)類型轉(zhuǎn)換
我們先看一段代碼,了解下大致的情況。
'123' == 123 ? // false or true?'' == null ? ?// false or true?'' == 0 ? ? ? ?// false or true?[] == 0 ? ? ? ?// false or true?[] == '' ? ? ? // false or true?[] == ![] ? ? ?// false or true?null == undefined // ?false or true?Number(null) ? ? // 返回什么?Number('') ? ? ?// 返回什么?parseInt(''); ? ?// 返回什么?{}+10 ? ? ? ? ? // 返回什么?let obj = { ? ?[Symbol.toPrimitive]() { ? ? ? ?return 200; ? ?}, ? ?valueOf() { ? ? ? ?return 300; ? ?}, ? ?toString() { ? ? ? ?return 'Hello'; ? ?} }console.log(obj + 200); // 這里打印出來是多少?
首先我們要知道,在?JS
?中類型轉(zhuǎn)換只有三種情況,分別是:
轉(zhuǎn)換為布爾值
轉(zhuǎn)換為數(shù)字
轉(zhuǎn)換為字符串
轉(zhuǎn)Boolean
在條件判斷時(shí),除了?undefined
,null
,?false
,?NaN
,?''
,?0
,?-0
,其他所有值都轉(zhuǎn)為?true
,包括所有對(duì)象
Boolean(0) ? ? ? ? ?//falseBoolean(null) ? ? ? //falseBoolean(undefined) ?//falseBoolean(NaN) ? ? ? ?//falseBoolean(1) ? ? ? ? ?//trueBoolean(13) ? ? ? ? //trueBoolean('12') ? ? ? //true
對(duì)象轉(zhuǎn)原始類型
對(duì)象在轉(zhuǎn)換類型的時(shí)候,會(huì)調(diào)用內(nèi)置的?[[ToPrimitive]]
?函數(shù),對(duì)于該函數(shù)來說,算法邏輯一般來說如下
如果已經(jīng)是原始類型了,那就不需要轉(zhuǎn)換了
調(diào)用?
x.valueOf()
,如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值調(diào)用?
x.toString()
,如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值如果都沒有返回原始類型,就會(huì)報(bào)錯(cuò)
當(dāng)然你也可以重寫?Symbol.toPrimitive
,該方法在轉(zhuǎn)原始類型時(shí)調(diào)用優(yōu)先級(jí)最高。
let a = { ?valueOf() { ? ?return 0 ?}, ?toString() { ? ?return '1' ?}, ?[Symbol.toPrimitive]() { ? ?return 2 ?} }1 + a // => 3
四則運(yùn)算符
它有以下幾個(gè)特點(diǎn):
運(yùn)算中其中一方為字符串,那么就會(huì)把另一方也轉(zhuǎn)換為字符串
如果一方不是字符串或者數(shù)字,那么會(huì)將它轉(zhuǎn)換為數(shù)字或者字符串
1 + '1' // '11'true + true // 24 + [1,2,3] // "41,2,3"
對(duì)于第一行代碼來說,觸發(fā)特點(diǎn)一,所以將數(shù)字?
1
?轉(zhuǎn)換為字符串,得到結(jié)果?'11'
對(duì)于第二行代碼來說,觸發(fā)特點(diǎn)二,所以將?
true
?轉(zhuǎn)為數(shù)字?1
對(duì)于第三行代碼來說,觸發(fā)特點(diǎn)二,所以將數(shù)組通過?
toString
轉(zhuǎn)為字符串?1,2,3
,得到結(jié)果?41,2,3
另外對(duì)于加法還需要注意這個(gè)表達(dá)式?'a' + + 'b'
'a' + + 'b' // -> "aNaN"
因?yàn)?
+ 'b'
?等于?NaN
,所以結(jié)果為?"aNaN"
,你可能也會(huì)在一些代碼中看到過?+ '1'
的形式來快速獲取?number
?類型。那么對(duì)于除了加法的運(yùn)算符來說,只要其中一方是數(shù)字,那么另一方就會(huì)被轉(zhuǎn)為數(shù)字
4 * '3' // 124 * [] // 04 * [1, 2] // NaN
比較運(yùn)算符
如果是對(duì)象,就通過?
toPrimitive
?轉(zhuǎn)換對(duì)象如果是字符串,就通過?
unicode
?字符索引來比較
let a = { ?valueOf() { ? ?return 0 ?}, ?toString() { ? ?return '1' ?} } a > -1 // true
在以上代碼中,因?yàn)?a
?是對(duì)象,所以會(huì)通過?valueOf
?轉(zhuǎn)換為原始類型再比較值。
強(qiáng)制類型轉(zhuǎn)換
強(qiáng)制類型轉(zhuǎn)換方式包括?Number()
、parseInt()
、parseFloat()
、toString()
、String()
、Boolean()
,這幾種方法都比較類似
Number()
?方法的強(qiáng)制轉(zhuǎn)換規(guī)則如果是布爾值,
true
?和?false
?分別被轉(zhuǎn)換為?1
?和?0
;如果是數(shù)字,返回自身;
如果是?
null
,返回?0
;如果是?
undefined
,返回?NaN
;如果是字符串,遵循以下規(guī)則:如果字符串中只包含數(shù)字(或者是?
0X / 0x
?開頭的十六進(jìn)制數(shù)字字符串,允許包含正負(fù)號(hào)),則將其轉(zhuǎn)換為十進(jìn)制;如果字符串中包含有效的浮點(diǎn)格式,將其轉(zhuǎn)換為浮點(diǎn)數(shù)值;如果是空字符串,將其轉(zhuǎn)換為?0
;如果不是以上格式的字符串,均返回 NaN;如果是?
Symbol
,拋出錯(cuò)誤;如果是對(duì)象,并且部署了?
[Symbol.toPrimitive]
?,那么調(diào)用此方法,否則調(diào)用對(duì)象的?valueOf()
?方法,然后依據(jù)前面的規(guī)則轉(zhuǎn)換返回的值;如果轉(zhuǎn)換的結(jié)果是?NaN
?,則調(diào)用對(duì)象的?toString()
?方法,再次依照前面的順序轉(zhuǎn)換返回對(duì)應(yīng)的值。
Number(true); ? ? ? ?// 1Number(false); ? ? ? // 0Number('0111'); ? ? ?//111Number(null); ? ? ? ?//0Number(''); ? ? ? ? ?//0Number('1a'); ? ? ? ?//NaNNumber(-0X11); ? ? ? //-17Number('0X11') ? ? ? //17
Object 的轉(zhuǎn)換規(guī)則
對(duì)象轉(zhuǎn)換的規(guī)則,會(huì)先調(diào)用內(nèi)置的?[ToPrimitive]
?函數(shù),其規(guī)則邏輯如下:
如果部署了?
Symbol.toPrimitive
?方法,優(yōu)先調(diào)用再返回;調(diào)用?
valueOf()
,如果轉(zhuǎn)換為基礎(chǔ)類型,則返回;調(diào)用?
toString()
,如果轉(zhuǎn)換為基礎(chǔ)類型,則返回;如果都沒有返回基礎(chǔ)類型,會(huì)報(bào)錯(cuò)。
var obj = { ?value: 1, ?valueOf() { ? ?return 2; ?}, ?toString() { ? ?return '3' ?}, ?[Symbol.toPrimitive]() { ? ?return 4 ?} }console.log(obj + 1); // 輸出5// 因?yàn)橛蠸ymbol.toPrimitive,就優(yōu)先執(zhí)行這個(gè);如果Symbol.toPrimitive這段代碼刪掉,則執(zhí)行valueOf打印結(jié)果為3;如果valueOf也去掉,則調(diào)用toString返回'31'(字符串拼接)// 再看兩個(gè)特殊的case:10 + {}// "10[object Object]",注意:{}會(huì)默認(rèn)調(diào)用valueOf是{},不是基礎(chǔ)類型繼續(xù)轉(zhuǎn)換,調(diào)用toString,返回結(jié)果"[object Object]",于是和10進(jìn)行'+'運(yùn)算,按照字符串拼接規(guī)則來,參考'+'的規(guī)則C[1,2,undefined,4,5] + 10// "1,2,,4,510",注意[1,2,undefined,4,5]會(huì)默認(rèn)先調(diào)用valueOf結(jié)果還是這個(gè)數(shù)組,不是基礎(chǔ)數(shù)據(jù)類型繼續(xù)轉(zhuǎn)換,也還是調(diào)用toString,返回"1,2,,4,5",然后再和10進(jìn)行運(yùn)算,還是按照字符串拼接規(guī)則,參考'+'的第3條規(guī)則
'==' 的隱式類型轉(zhuǎn)換規(guī)則
如果類型相同,無須進(jìn)行類型轉(zhuǎn)換;
如果其中一個(gè)操作值是?
null
?或者?undefined
,那么另一個(gè)操作符必須為?null
?或者?undefined
,才會(huì)返回?true
,否則都返回?false
;如果其中一個(gè)是?
Symbol
?類型,那么返回?false
;兩個(gè)操作值如果為?
string
?和 number 類型,那么就會(huì)將字符串轉(zhuǎn)換為?number
;如果一個(gè)操作值是?
boolean
,那么轉(zhuǎn)換成?number
;如果一個(gè)操作值為?
object
?且另一方為?string
、number
?或者?symbol
,就會(huì)把?object
?轉(zhuǎn)為原始類型再進(jìn)行判斷(調(diào)用?object
?的?valueOf/toString
?方法進(jìn)行轉(zhuǎn)換)。
null == undefined ? ? ? // true ?規(guī)則2null == 0 ? ? ? ? ? ? ? // false 規(guī)則2'' == null ? ? ? ? ? ? ?// false 規(guī)則2'' == 0 ? ? ? ? ? ? ? ? // true ?規(guī)則4 字符串轉(zhuǎn)隱式轉(zhuǎn)換成Number之后再對(duì)比'123' == 123 ? ? ? ? ? ?// true ?規(guī)則4 字符串轉(zhuǎn)隱式轉(zhuǎn)換成Number之后再對(duì)比0 == false ? ? ? ? ? ? ?// true ?e規(guī)則 布爾型隱式轉(zhuǎn)換成Number之后再對(duì)比1 == true ? ? ? ? ? ? ? // true ?e規(guī)則 布爾型隱式轉(zhuǎn)換成Number之后再對(duì)比var a = { ?value: 0, ?valueOf: function() { ? ?this.value++; ? ?return this.value; ?} };// 注意這里a又可以等于1、2、3console.log(a == 1 && a == 2 && a ==3); ?//true f規(guī)則 Object隱式轉(zhuǎn)換// 注:但是執(zhí)行過3遍之后,再重新執(zhí)行a==3或之前的數(shù)字就是false,因?yàn)関alue已經(jīng)加上去了,這里需要注意一下
'+' 的隱式類型轉(zhuǎn)換規(guī)則
'+' 號(hào)操作符,不僅可以用作數(shù)字相加,還可以用作字符串拼接。僅當(dāng) '+' 號(hào)兩邊都是數(shù)字時(shí),進(jìn)行的是加法運(yùn)算;如果兩邊都是字符串,則直接拼接,無須進(jìn)行隱式類型轉(zhuǎn)換。
如果其中有一個(gè)是字符串,另外一個(gè)是?
undefined
、null
?或布爾型,則調(diào)用?toString()
?方法進(jìn)行字符串拼接;如果是純對(duì)象、數(shù)組、正則等,則默認(rèn)調(diào)用對(duì)象的轉(zhuǎn)換方法會(huì)存在優(yōu)先級(jí),然后再進(jìn)行拼接。如果其中有一個(gè)是數(shù)字,另外一個(gè)是?
undefined
、null
、布爾型或數(shù)字,則會(huì)將其轉(zhuǎn)換成數(shù)字進(jìn)行加法運(yùn)算,對(duì)象的情況還是參考上一條規(guī)則。如果其中一個(gè)是字符串、一個(gè)是數(shù)字,則按照字符串規(guī)則進(jìn)行拼接
1 + 2 ? ? ? ?// 3 ?常規(guī)情況'1' + '2' ? ?// '12' 常規(guī)情況// 下面看一下特殊情況'1' + undefined ? // "1undefined" 規(guī)則1,undefined轉(zhuǎn)換字符串'1' + null ? ? ? ?// "1null" 規(guī)則1,null轉(zhuǎn)換字符串'1' + true ? ? ? ?// "1true" 規(guī)則1,true轉(zhuǎn)換字符串'1' + 1n ? ? ? ? ?// '11' 比較特殊字符串和BigInt相加,BigInt轉(zhuǎn)換為字符串1 + undefined ? ? // NaN ?規(guī)則2,undefined轉(zhuǎn)換數(shù)字相加NaN1 + null ? ? ? ? ?// 1 ? ?規(guī)則2,null轉(zhuǎn)換為01 + true ? ? ? ? ?// 2 ? ?規(guī)則2,true轉(zhuǎn)換為1,二者相加為21 + 1n ? ? ? ? ? ?// 錯(cuò)誤 ?不能把BigInt和Number類型直接混合相加'1' + 3 ? ? ? ? ? // '13' 規(guī)則3,字符串拼接
整體來看,如果數(shù)據(jù)中有字符串,JavaScript 類型轉(zhuǎn)換還是更傾向于轉(zhuǎn)換成字符串,因?yàn)榈谌龡l規(guī)則中可以看到,在字符串和數(shù)字相加的過程中最后返回的還是字符串,這里需要關(guān)注一下
null 和 undefined 的區(qū)別?
首先?
Undefined
?和?Null
?都是基本數(shù)據(jù)類型,這兩個(gè)基本數(shù)據(jù)類型分別都只有一個(gè)值,就是?undefined
?和?null
。undefined
?代表的含義是未定義,?null
?代表的含義是空對(duì)象(其實(shí)不是真的對(duì)象,請(qǐng)看下面的注意?。R话阕兞柯暶髁说€沒有定義的時(shí)候會(huì)返回?undefined
,null
?主要用于賦值給一些可能會(huì)返回對(duì)象的變量,作為初始化。
其實(shí) null 不是對(duì)象,雖然 typeof null 會(huì)輸出 object,但是這只是 JS 存在的一個(gè)悠久 Bug。在 JS 的最初版本中使用的是 32 位系統(tǒng),為了性能考慮使用低位存儲(chǔ)變量的類型信息,000 開頭代表是對(duì)象,然而 null 表示為全零,所以將它錯(cuò)誤的判斷為 object 。雖然現(xiàn)在的內(nèi)部類型判斷代碼已經(jīng)改變了,但是對(duì)于這個(gè) Bug 卻是一直流傳下來。
undefined 在 js 中不是一個(gè)保留字,這意味著我們可以使用?
undefined
?來作為一個(gè)變量名,這樣的做法是非常危險(xiǎn)的,它會(huì)影響我們對(duì) undefined 值的判斷。但是我們可以通過一些方法獲得安全的?undefined
?值,比如說?void 0
。當(dāng)我們對(duì)兩種類型使用 typeof 進(jìn)行判斷的時(shí)候,Null 類型化會(huì)返回 “object”,這是一個(gè)歷史遺留的問題。當(dāng)我們使用雙等號(hào)對(duì)兩種類型的值進(jìn)行比較時(shí)會(huì)返回 true,使用三個(gè)等號(hào)時(shí)會(huì)返回 false。
TCP/IP五層協(xié)議
TCP/IP
五層協(xié)議和OSI
的七層協(xié)議對(duì)應(yīng)關(guān)系如下:
應(yīng)用層 (application layer):直接為應(yīng)用進(jìn)程提供服務(wù)。應(yīng)用層協(xié)議定義的是應(yīng)用進(jìn)程間通訊和交互的規(guī)則,不同的應(yīng)用有著不同的應(yīng)用層協(xié)議,如 HTTP協(xié)議(萬維網(wǎng)服務(wù))、FTP協(xié)議(文件傳輸)、SMTP協(xié)議(電子郵件)、DNS(域名查詢)等。
傳輸層 (transport layer):有時(shí)也譯為運(yùn)輸層,它負(fù)責(zé)為兩臺(tái)主機(jī)中的進(jìn)程提供通信服務(wù)。該層主要有以下兩種協(xié)議:
傳輸控制協(xié)議 (Transmission Control Protocol,TCP):提供面向連接的、可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)傳輸?shù)幕締挝皇菆?bào)文段(segment);
用戶數(shù)據(jù)報(bào)協(xié)議 (User Datagram Protocol,UDP):提供無連接的、盡最大努力的數(shù)據(jù)傳輸服務(wù),但不保證數(shù)據(jù)傳輸?shù)目煽啃?,?shù)據(jù)傳輸?shù)幕締挝皇怯脩魯?shù)據(jù)報(bào)。
網(wǎng)絡(luò)層 (internet layer):有時(shí)也譯為網(wǎng)際層,它負(fù)責(zé)為兩臺(tái)主機(jī)提供通信服務(wù),并通過選擇合適的路由將數(shù)據(jù)傳遞到目標(biāo)主機(jī)。
數(shù)據(jù)鏈路層 (data link layer):負(fù)責(zé)將網(wǎng)絡(luò)層交下來的 IP 數(shù)據(jù)報(bào)封裝成幀,并在鏈路的兩個(gè)相鄰節(jié)點(diǎn)間傳送幀,每一幀都包含數(shù)據(jù)和必要的控制信息(如同步信息、地址信息、差錯(cuò)控制等)。
物理層 (physical Layer):確保數(shù)據(jù)可以在各種物理媒介上進(jìn)行傳輸,為數(shù)據(jù)的傳輸提供可靠的環(huán)境。
從上圖中可以看出,TCP/IP
模型比OSI
模型更加簡潔,它把應(yīng)用層/表示層/會(huì)話層
全部整合為了應(yīng)用層
。
在每一層都工作著不同的設(shè)備,比如我們常用的交換機(jī)就工作在數(shù)據(jù)鏈路層的,一般的路由器是工作在網(wǎng)絡(luò)層的。 在每一層實(shí)現(xiàn)的協(xié)議也各不同,即每一層的服務(wù)也不同,下圖列出了每層主要的傳輸協(xié)議:
同樣,TCP/IP
五層協(xié)議的通信方式也是對(duì)等通信:
TCP和UDP的使用場(chǎng)景
TCP應(yīng)用場(chǎng)景:?效率要求相對(duì)低,但對(duì)準(zhǔn)確性要求相對(duì)高的場(chǎng)景。因?yàn)閭鬏斨行枰獙?duì)數(shù)據(jù)確認(rèn)、重發(fā)、排序等操作,相比之下效率沒有UDP高。例如:文件傳輸(準(zhǔn)確高要求高、但是速度可以相對(duì)慢)、接受郵件、遠(yuǎn)程登錄。
UDP應(yīng)用場(chǎng)景:?效率要求相對(duì)高,對(duì)準(zhǔn)確性要求相對(duì)低的場(chǎng)景。例如:QQ聊天、在線視頻、網(wǎng)絡(luò)語音電話(即時(shí)通訊,速度要求高,但是出現(xiàn)偶爾斷續(xù)不是太大問題,并且此處完全不可以使用重發(fā)機(jī)制)、廣播通信(廣播、多播)。
左右兩邊定寬,中間自適應(yīng)
float,float + calc
, 圣杯布局(設(shè)置BFC,margin負(fù)值法),flex
.wrap { ?width: 100%; ?height: 200px; }.wrap > div { ?height: 100%; }/* 方案1 */.left { ?width: 120px; ?float: left; }.right { ?float: right; ?width: 120px; }.center { ?margin: 0 120px; }/* 方案2 */.left { ?width: 120px; ?float: left; }.right { ?float: right; ?width: 120px; }.center { ?width: calc(100% - 240px); ?margin-left: 120px; }/* 方案3 */.wrap { ?display: flex; }.left { ?width: 120px; }.right { ?width: 120px; }.center { ?flex: 1; }
intanceof 操作符的實(shí)現(xiàn)原理及實(shí)現(xiàn)
instanceof 運(yùn)算符用于判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對(duì)象的原型鏈中的任何位置。
function myInstanceof(left, right) { ?// 獲取對(duì)象的原型 ?let proto = Object.getPrototypeOf(left) ?// 獲取構(gòu)造函數(shù)的 prototype 對(duì)象 ?let prototype = right.prototype; ?// 判斷構(gòu)造函數(shù)的 prototype 對(duì)象是否在對(duì)象的原型鏈上 ?while (true) { ? ?if (!proto) return false; ? ?if (proto === prototype) return true; ? ?// 如果沒有找到,就繼續(xù)從其原型上找,Object.getPrototypeOf方法用來獲取指定對(duì)象的原型 ? ?proto = Object.getPrototypeOf(proto); ?} }
UDP協(xié)議為什么不可靠?
UDP在傳輸數(shù)據(jù)之前不需要先建立連接,遠(yuǎn)地主機(jī)的運(yùn)輸層在接收到UDP報(bào)文后,不需要確認(rèn),提供不可靠交付??偨Y(jié)就以下四點(diǎn):
不保證消息交付:不確認(rèn),不重傳,無超時(shí)
不保證交付順序:不設(shè)置包序號(hào),不重排,不會(huì)發(fā)生隊(duì)首阻塞
不跟蹤連接狀態(tài):不必建立連接或重啟狀態(tài)機(jī)
不進(jìn)行擁塞控制:不內(nèi)置客戶端或網(wǎng)絡(luò)反饋機(jī)制
說一下 HTML5 drag API
dragstart:事件主體是被拖放元素,在開始拖放被拖放元素時(shí)觸發(fā)。
darg:事件主體是被拖放元素,在正在拖放被拖放元素時(shí)觸發(fā)。
dragenter:事件主體是目標(biāo)元素,在被拖放元素進(jìn)入某元素時(shí)觸發(fā)。
dragover:事件主體是目標(biāo)元素,在被拖放在某元素內(nèi)移動(dòng)時(shí)觸發(fā)。
dragleave:事件主體是目標(biāo)元素,在被拖放元素移出目標(biāo)元素是觸發(fā)。
drop:事件主體是目標(biāo)元素,在目標(biāo)元素完全接受被拖放元素時(shí)觸發(fā)。
dragend:事件主體是被拖放元素,在整個(gè)拖放操作結(jié)束時(shí)觸發(fā)。
實(shí)現(xiàn)一個(gè)三角形
CSS繪制三角形主要用到的是border屬性,也就是邊框。
平時(shí)在給盒子設(shè)置邊框時(shí),往往都設(shè)置很窄,就可能誤以為邊框是由矩形組成的。實(shí)際上,border屬性是右三角形組成的,下面看一個(gè)例子:
div { ? ?width: 0; ? ?height: 0; ? ?border: 100px solid; ? ?border-color: orange blue red green; }
將元素的長寬都設(shè)置為0
(1)三角1
div { ? ?width: 0; ? ?height: 0; ? ?border-top: 50px solid red; ? ?border-right: 50px solid transparent; ? ?border-left: 50px solid transparent;}
(2)三角2
div { ? ?width: 0; ? ?height: 0; ? ?border-bottom: 50px solid red; ? ?border-right: 50px solid transparent; ? ?border-left: 50px solid transparent; }
(3)三角3
div { ? ?width: 0; ? ?height: 0; ? ?border-left: 50px solid red; ? ?border-top: 50px solid transparent; ? ?border-bottom: 50px solid transparent; }
(4)三角4
div { ? ?width: 0; ? ?height: 0; ? ?border-right: 50px solid red; ? ?border-top: 50px solid transparent; ? ?border-bottom: 50px solid transparent; }
(5)三角5
div { ? ?width: 0; ? ?height: 0; ? ?border-top: 100px solid red; ? ?border-right: 100px solid transparent; }
還有很多,就不一一實(shí)現(xiàn)了,總體的原則就是通過上下左右邊框來控制三角形的方向,用邊框的寬度比來控制三角形的角度。
對(duì)this對(duì)象的理解
this 是執(zhí)行上下文中的一個(gè)屬性,它指向最后一次調(diào)用這個(gè)方法的對(duì)象。在實(shí)際開發(fā)中,this 的指向可以通過四種調(diào)用模式來判斷。
第一種是函數(shù)調(diào)用模式,當(dāng)一個(gè)函數(shù)不是一個(gè)對(duì)象的屬性時(shí),直接作為函數(shù)來調(diào)用時(shí),this 指向全局對(duì)象。
第二種是方法調(diào)用模式,如果一個(gè)函數(shù)作為一個(gè)對(duì)象的方法來調(diào)用時(shí),this 指向這個(gè)對(duì)象。
第三種是構(gòu)造器調(diào)用模式,如果一個(gè)函數(shù)用 new 調(diào)用時(shí),函數(shù)執(zhí)行前會(huì)新創(chuàng)建一個(gè)對(duì)象,this 指向這個(gè)新創(chuàng)建的對(duì)象。
第四種是?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ù)。也就是說,在使用 call() 方法時(shí),傳遞給函數(shù)的參數(shù)必須逐個(gè)列舉出來。bind 方法通過傳入一個(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)用模式。
new操作符的實(shí)現(xiàn)原理
new操作符的執(zhí)行過程:
(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; ?let constructor = Array.prototype.shift.call(arguments); ?let result = null; ?// 判斷參數(shù)是否是一個(gè)函數(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ù));
箭頭函數(shù)的this指向哪??
箭頭函數(shù)不同于傳統(tǒng)JavaScript中的函數(shù),箭頭函數(shù)并沒有屬于??的this,它所謂的this是捕獲其所在上下?的 this 值,作為??的 this 值,并且由于沒有屬于??的this,所以是不會(huì)被new調(diào)?的,這個(gè)所謂的this也不會(huì)被改變。
可以?Babel理解?下箭頭函數(shù):
// ES6 const obj = { ?getArrow() { ? ?return () => { ? ? ?console.log(this === obj); ? ?}; ?} }
轉(zhuǎn)化后:
// ES5,由 Babel 轉(zhuǎn)譯var obj = { ? getArrow: function getArrow() { ? ? var _this = this; ? ? return function () { ? ? ? ?console.log(_this === obj); ? ? }; ? } };