最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

前端經(jīng)典面試題(有答案)

2023-03-07 14:18 作者:晨曦箘  | 我要投稿

代碼輸出結(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è)mixinproperty存在變量命名沖突的風(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

  • 所有元素可繼承:visibilitycursor。

  • 內(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-image`。

transition和animation的區(qū)別

Animationtransition大部分屬性是相同的,他們都是隨時(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,HTTPSFTP,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é)議的。

  • SMTPsimple 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選擇器 > 類選擇器 = 偽類選擇器 = 屬性選擇器 > 元素選擇器 = 偽元素選擇器 > 通配選擇器 = 后代選擇器 = 兄弟選擇器
  1. 屬性后面加!import會(huì)覆蓋頁面內(nèi)任何位置定義的元素樣式

  2. 作為style屬性寫在元素內(nèi)的樣式

  3. id選擇器

  4. 類選擇器

  5. 標(biāo)簽選擇器

  6. 通配符選擇器(*

  7. 瀏覽器自定義或繼承

同一級(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、Symboles6新增,表示獨(dú)一無二的值)和BigIntes10新增);

  • 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 nullobject?原因是對(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); ? ? }; ? } };


前端經(jīng)典面試題(有答案)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
图们市| 城固县| 菏泽市| 衢州市| 康定县| 武清区| 盐边县| 浪卡子县| 化德县| 新郑市| 西峡县| 蓬莱市| 旺苍县| 炉霍县| 正定县| 壤塘县| 纳雍县| 交口县| 柳林县| 军事| 拜泉县| 从化市| 鄄城县| 蓬溪县| 华安县| 岳阳县| 蒙自县| 汉寿县| 托克逊县| 鱼台县| 蕉岭县| 南郑县| 南华县| 随州市| 江安县| 仪征市| 贺州市| 姜堰市| 平谷区| 寿宁县| 福安市|