174道JavaScript 面試知識(shí)點(diǎn)總結(jié)(下)

來(lái)源 | https://github.com/CavsZhouyou/
接上昨天的《174道JavaScript 面試知識(shí)點(diǎn)總結(jié)(上)》,昨天分享了前面100道題,今天分享后面的74道。正文如下:
101、toPrecision 和 toFixed 和 Math.round 的區(qū)別?
toPrecision 用于處理精度,精度是從左至右第一個(gè)不為 0 的數(shù)開始數(shù)起。toFixed 是對(duì)小數(shù)點(diǎn)后指定位數(shù)取整,從小數(shù)點(diǎn)開始數(shù)起。是將一個(gè)數(shù)字四舍五入到一個(gè)整數(shù)。
102、什么是 XSS 攻擊?如何防范 XSS 攻擊?
XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者通過(guò)在網(wǎng)站注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行,從而盜取用戶的信息如 cookie 等。XSS 的本質(zhì)是因?yàn)榫W(wǎng)站沒(méi)有對(duì)惡意代碼進(jìn)行過(guò)濾,與正常的代碼混合在一起了,瀏覽器沒(méi)有辦法分辨哪些腳本是可信的,從而導(dǎo)致了惡意代碼的執(zhí)行。XSS 一般分為存儲(chǔ)型、反射型和 DOM 型。HTML 后返回給了用戶,從而導(dǎo)致了惡意代碼的執(zhí)行。URL,當(dāng)服務(wù)器接收到請(qǐng)求后,從 URL 中獲取數(shù)據(jù),拼接到 HTML 后返回,從而導(dǎo)致了惡意代碼的執(zhí)行。DOM 型指的是攻擊者構(gòu)建了特殊的 URL,用戶打開網(wǎng)站后,js 腳本從 URL 中獲取數(shù)據(jù),從而導(dǎo)致了惡意代碼的執(zhí)行。XSS 攻擊的預(yù)防可以從兩個(gè)方面入手,一個(gè)是惡意代碼提交的時(shí)候,一個(gè)是瀏覽器執(zhí)行惡意代碼的時(shí)候。對(duì)于第一個(gè)方面,如果我們對(duì)存入數(shù)據(jù)庫(kù)的數(shù)據(jù)都進(jìn)行的轉(zhuǎn)義處理,但是一個(gè)數(shù)據(jù)可能在多個(gè)地方使用,有的地方可能不需要轉(zhuǎn)義,由于我們沒(méi)有辦法判斷數(shù)據(jù)最后的使用場(chǎng)景,所以直接在輸入端進(jìn)行惡意代碼的處理,其實(shí)是不太可靠的。HTML 中的代碼做好充分的轉(zhuǎn)義。對(duì)于 DOM 型的攻擊,主要是前端腳本的不可靠而造成的,我們對(duì)于數(shù)據(jù)獲取渲染和字符串拼接的時(shí)候應(yīng)該對(duì)可能出現(xiàn)的惡意代碼情況進(jìn)行判斷。CSP ,CSP 的本質(zhì)是建立一個(gè)白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行,從而防止惡意代碼的注入攻擊。cookie 使用 http-only ,使得腳本無(wú)法獲取。也可以使用驗(yàn)證碼,避免腳本偽裝成用戶執(zhí)行一些操作。
詳細(xì)資料可以參考:?《前端安全系列(一):如何防止 XSS 攻擊?》
103、?什么是 CSP?
CSP 指的是內(nèi)容安全策略,它的本質(zhì)是建立一個(gè)白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行。我們只需要配置規(guī)則,如何攔截由瀏覽器自己來(lái)實(shí)現(xiàn)。通常有兩種方式來(lái)開啟 CSP,一種是設(shè)置 HTTP 首部中的 Content-Security-Policy,一種是設(shè)置 meta 標(biāo)簽的方式 <metahttp-equiv="Content-Security-Policy">
詳細(xì)資料可以參考:?《內(nèi)容安全策略(CSP)》?《前端面試之道》
104、什么是 CSRF 攻擊?如何防范 CSRF 攻擊?
CSRF 攻擊指的是跨站請(qǐng)求偽造攻擊,攻擊者誘導(dǎo)用戶進(jìn)入一個(gè)第三方網(wǎng)站,然后該網(wǎng)站向被攻擊網(wǎng)站發(fā)送跨站請(qǐng)求。如果用戶在被攻擊網(wǎng)站中保存了登錄狀態(tài),那么攻擊者就可以利用這個(gè)登錄狀態(tài),繞過(guò)后臺(tái)的用戶驗(yàn)證,冒充用戶向服務(wù)器執(zhí)行一些操作。CSRF 攻擊的本質(zhì)是利用了 cookie 會(huì)在同源請(qǐng)求中攜帶發(fā)送給服務(wù)器的特點(diǎn),以此來(lái)實(shí)現(xiàn)用戶的冒充。CSRF 攻擊類型有三種:GET 類型的 CSRF 攻擊,比如在網(wǎng)站中的一個(gè) img 標(biāo)簽里構(gòu)建一個(gè)請(qǐng)求,當(dāng)用戶打開這個(gè)網(wǎng)站的時(shí)候就會(huì)自動(dòng)發(fā)起提交。POST 類型的 CSRF 攻擊,比如說(shuō)構(gòu)建一個(gè)表單,然后隱藏它,當(dāng)用戶進(jìn)入頁(yè)面時(shí),自動(dòng)提交這個(gè)表單。CSRF 攻擊,比如說(shuō)在 a 標(biāo)簽的 href 屬性里構(gòu)建一個(gè)請(qǐng)求,然后誘導(dǎo)用戶去點(diǎn)擊。CSRF 可以用下面幾種方法來(lái)防護(hù):http 請(qǐng)求頭中 origin 或者 referer 信息來(lái)判斷請(qǐng)求是否為允許訪問(wèn)的站點(diǎn),從而對(duì)請(qǐng)求進(jìn)行過(guò)濾。當(dāng) origin 或者 referer 信息都不存在的時(shí)候,直接阻止。這種方式的缺點(diǎn)是有些情況下 referer 可以被偽造。還有就是我們這種方法同時(shí)把搜索引擎的鏈接也給屏蔽了,所以一般網(wǎng)站會(huì)允許搜索引擎的頁(yè)面請(qǐng)求,但是相應(yīng)的頁(yè)面請(qǐng)求這種請(qǐng)求方式也可能被攻擊者給利用。CSRF Token 來(lái)進(jìn)行驗(yàn)證,服務(wù)器向用戶返回一個(gè)隨機(jī)數(shù) Token ,當(dāng)網(wǎng)站再次發(fā)起請(qǐng)求時(shí),在請(qǐng)求參數(shù)中加入服務(wù)器端返回的 token ,然后服務(wù)器對(duì)這個(gè) token 進(jìn)行驗(yàn)證。這種方法解決了使用 cookie 單一驗(yàn)證方式時(shí),可能會(huì)被冒用的問(wèn)題,但是這種方法存在一個(gè)缺點(diǎn)就是,我們需要給網(wǎng)站中的所有請(qǐng)求都添加上這個(gè) token,操作比較繁瑣。還有一個(gè)問(wèn)題是一般不會(huì)只有一臺(tái)網(wǎng)站服務(wù)器,如果我們的請(qǐng)求經(jīng)過(guò)負(fù)載平衡轉(zhuǎn)移到了其他的服務(wù)器,但是這個(gè)服務(wù)器的 session 中沒(méi)有保留這個(gè) token 的話,就沒(méi)有辦法驗(yàn)證了。這種情況我們可以通過(guò)改變 token 的構(gòu)建方式來(lái)解決。Cookie 驗(yàn)證的辦法,服務(wù)器在用戶訪問(wèn)網(wǎng)站頁(yè)面時(shí),向請(qǐng)求域名注入一個(gè)Cookie,內(nèi)容為隨機(jī)字符串,然后當(dāng)用戶再次向服務(wù)器發(fā)送請(qǐng)求的時(shí)候,從 cookie 中取出這個(gè)字符串,添加到 URL 參數(shù)中,然后服務(wù)器通過(guò)對(duì) cookie 中的數(shù)據(jù)和參數(shù)中的數(shù)據(jù)進(jìn)行比較,來(lái)進(jìn)行驗(yàn)證。使用這種方式是利用了攻擊者只能利用 cookie,但是不能訪問(wèn)獲取 cookie 的特點(diǎn)。并且這種方法比 CSRF Token 的方法更加方便,并且不涉及到分布式訪問(wèn)的問(wèn)題。這種方法的缺點(diǎn)是如果網(wǎng)站存在 XSS 漏洞的,那么這種方式會(huì)失效。同時(shí)這種方式不能做到子域名的隔離。cookie 屬性的時(shí)候設(shè)置 Samesite ,限制 cookie 不能作為被第三方使用,從而可以避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴(yán)格模式,在嚴(yán)格模式下 cookie 在任何情況下都不可能作為第三方 Cookie 使用,在寬松模式下,cookie 可以被請(qǐng)求是 GET 請(qǐng)求,且會(huì)發(fā)生頁(yè)面跳轉(zhuǎn)的請(qǐng)求所使用。
詳細(xì)資料可以參考:?《前端安全系列之二:如何防止 CSRF 攻擊?》?《[ HTTP 趣談] origin, referer 和 host 區(qū)別》
105、?什么是 Samesite Cookie 屬性?
Samesite Cookie 表示同站 cookie,避免 cookie 被第三方所利用。將 Samesite 設(shè)為 strict ,這種稱為嚴(yán)格模式,表示這個(gè) cookie 在任何情況下都不可能作為第三方 cookie。將 Samesite 設(shè)為 Lax ,這種模式稱為寬松模式,如果這個(gè)請(qǐng)求是個(gè) GET 請(qǐng)求,并且這個(gè)請(qǐng)求改變了當(dāng)前頁(yè)面或者打開了新的頁(yè)面,那么這個(gè) cookie 可以作為第三方 cookie,其余情況下都不能作為第三方 cookie。使用這種方法的缺點(diǎn)是,因?yàn)樗恢С肿佑颍宰佑驔](méi)有辦法與主域共享登錄信息,每次轉(zhuǎn)入子域的網(wǎng)站,都回重新登錄。還有一個(gè)問(wèn)題就是它的兼容性不夠好。
106、什么是點(diǎn)擊劫持?如何防范點(diǎn)擊劫持?
點(diǎn)擊劫持是一種視覺(jué)欺騙的攻擊手段,攻擊者將需要攻擊的網(wǎng)站通過(guò) iframe 嵌套的方式嵌入自己的網(wǎng)頁(yè)中,并將 iframe 設(shè)置為透明,在頁(yè)面中透出一個(gè)按鈕誘導(dǎo)用戶點(diǎn)擊。我們可以在 http 相應(yīng)頭中設(shè)置 X-FRAME-OPTIONS 來(lái)防御用 iframe 嵌套的點(diǎn)擊劫持攻擊。通過(guò)不同的值,可以規(guī)定頁(yè)面在特定的一些情況才能作為 iframe 來(lái)使用。
詳細(xì)資料可以參考:?《web 安全之--點(diǎn)擊劫持攻擊與防御技術(shù)簡(jiǎn)介》
107、SQL 注入攻擊?
SQL 注入攻擊指的是攻擊者在 HTTP 請(qǐng)求中注入惡意的 SQL 代碼,服務(wù)器使用參數(shù)構(gòu)建數(shù)據(jù)庫(kù) SQL 命令時(shí),惡意 SQL 被一起構(gòu)造,破壞原有 SQL 結(jié)構(gòu),并在數(shù)據(jù)庫(kù)中執(zhí)行,達(dá)到編寫程序時(shí)意料之外結(jié)果的攻擊行為。
詳細(xì)資料可以參考:?《Web 安全漏洞之 SQL 注入》?《如何防范常見(jiàn)的 Web 攻擊》
108、什么是 MVVM?比之 MVC 有什么區(qū)別?什么又是 MVP ?
和 MVVM 是三種常見(jiàn)的軟件架構(gòu)設(shè)計(jì)模式,主要通過(guò)分離關(guān)注點(diǎn)的方式來(lái)組織代碼結(jié)構(gòu),優(yōu)化我們的開發(fā)效率。比如說(shuō)我們實(shí)驗(yàn)室在以前項(xiàng)目開發(fā)的時(shí)候,使用單頁(yè)應(yīng)用時(shí),往往一個(gè)路由頁(yè)面對(duì)應(yīng)了一個(gè)腳本文件,所有的頁(yè)面邏輯都在一個(gè)腳本文件里。頁(yè)面的渲染、數(shù)據(jù)的獲取,對(duì)用戶事件的響應(yīng)所有的應(yīng)用邏輯都混合在一起,這樣在開發(fā)簡(jiǎn)單項(xiàng)目時(shí),可能看不出什么問(wèn)題,當(dāng)時(shí)一旦項(xiàng)目變得復(fù)雜,那么整個(gè)文件就會(huì)變得冗長(zhǎng),混亂,這樣對(duì)我們的項(xiàng)目開發(fā)和后期的項(xiàng)目維護(hù)是非常不利的。MVC 通過(guò)分離 Model、View 和 Controller 的方式來(lái)組織代碼結(jié)構(gòu)。其中 View 負(fù)責(zé)頁(yè)面的顯示邏輯,Model 負(fù)責(zé)存儲(chǔ)頁(yè)面的業(yè)務(wù)數(shù)據(jù),以及對(duì)相應(yīng)數(shù)據(jù)的操作。并且 View 和 Model 應(yīng)用了觀察者模式,當(dāng) Model 層發(fā)生改變的時(shí)候它會(huì)通知有關(guān) View 層更新頁(yè)面。Controller 層是 View 層和 Model 層的紐帶,它主要負(fù)責(zé)用戶與應(yīng)用的響應(yīng)操作,當(dāng)用戶與頁(yè)面產(chǎn)生交互的時(shí)候,Controller 中的事件觸發(fā)器就開始工作了,通過(guò)調(diào)用 Model 層,來(lái)完成對(duì) Model 的修改,然后 Model 層再去通知 View 層更新。MVP 模式與 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中我們使用觀察者模式,來(lái)實(shí)現(xiàn)當(dāng) Model 層數(shù)據(jù)發(fā)生變化的時(shí)候,通知 View 層的更新。這樣 View 層和 Model 層耦合在一起,當(dāng)項(xiàng)目邏輯變得復(fù)雜的時(shí)候,可能會(huì)造成代碼的混亂,并且可能會(huì)對(duì)代碼的復(fù)用性造成一些問(wèn)題。MVP 的模式通過(guò)使用 Presenter 來(lái)實(shí)現(xiàn)對(duì) View 層和 Model 層的解耦。MVC 中的Controller 只知道 Model 的接口,因此它沒(méi)有辦法控制 View 層的更新,MVP 模式中,View 層的接口暴露給了 Presenter 因此我們可以在 Presenter 中將 Model 的變化和 View 的變化綁定在一起,以此來(lái)實(shí)現(xiàn) View 和 Model 的同步更新。這樣就實(shí)現(xiàn)了對(duì) View 和 Model 的解耦,Presenter 還包含了其他的響應(yīng)邏輯。MVVM 模式中的 VM,指的是 ViewModel,它和 MVP 的思想其實(shí)是相同的,不過(guò)它通過(guò)雙向的數(shù)據(jù)綁定,將 View 和 Model 的同步更新給自動(dòng)化了。當(dāng) Model 發(fā)生變化的時(shí)候,ViewModel 就會(huì)自動(dòng)更新;ViewModel 變化了,View 也會(huì)更新。這樣就將 Presenter 中的工作給自動(dòng)化了。我了解過(guò)一點(diǎn)雙向數(shù)據(jù)綁定的原理,比如 vue 是通過(guò)使用數(shù)據(jù)劫持和發(fā)布訂閱者模式來(lái)實(shí)現(xiàn)的這一功能。
詳細(xì)資料可以參考:?《淺析前端開發(fā)中的 MVC/MVP/MVVM 模式》?《MVC,MVP 和 MVVM 的圖示》?《MVVM》?《一篇文章了解架構(gòu)模式:MVC/MVP/MVVM》
109、 vue 雙向數(shù)據(jù)綁定原理?
vue 通過(guò)使用雙向數(shù)據(jù)綁定,來(lái)實(shí)現(xiàn)了 View 和 Model 的同步更新。vue 的雙向數(shù)據(jù)綁定主要是通過(guò)使用數(shù)據(jù)劫持和發(fā)布訂閱者模式來(lái)實(shí)現(xiàn)的。首先我們通過(guò) Object.defineProperty() 方法來(lái)對(duì) Model 數(shù)據(jù)各個(gè)屬性添加訪問(wèn)器屬性,以此來(lái)實(shí)現(xiàn)數(shù)據(jù)的劫持,因此當(dāng) Model 中的數(shù)據(jù)發(fā)生變化的時(shí)候,我們可以通過(guò)配置的 setter 和 getter 方法來(lái)實(shí)現(xiàn)對(duì) View 層數(shù)據(jù)更新的通知。數(shù)據(jù)在 html 模板中一共有兩種綁定情況,一種是使用 v-model 來(lái)對(duì) value 值進(jìn)行綁定,一種是作為文本綁定,在對(duì)模板引擎進(jìn)行解析的過(guò)程中。如果遇到元素節(jié)點(diǎn),并且屬性值包含 v-model 的話,我們就從 Model 中去獲取 v-model 所對(duì)應(yīng)的屬性的值,并賦值給元素的 value 值。然后給這個(gè)元素設(shè)置一個(gè)監(jiān)聽(tīng)事件,當(dāng) View 中元素的數(shù)據(jù)發(fā)生變化的時(shí)候觸發(fā)該事件,通知 Model 中的對(duì)應(yīng)的屬性的值進(jìn)行更新。如果遇到了綁定的文本節(jié)點(diǎn),我們使用 Model 中對(duì)應(yīng)的屬性的值來(lái)替換這個(gè)文本。對(duì)于文本節(jié)點(diǎn)的更新,我們使用了發(fā)布訂閱者模式,屬性作為一個(gè)主題,我們?yōu)檫@個(gè)節(jié)點(diǎn)設(shè)置一個(gè)訂閱者對(duì)象,將這個(gè)訂閱者對(duì)象加入這個(gè)屬性主題的訂閱者列表中。當(dāng) Model 層數(shù)據(jù)發(fā)生改變的時(shí)候,Model 作為發(fā)布者向主題發(fā)出通知,主題收到通知再向它的所有訂閱者推送,訂閱者收到通知后更改自己的數(shù)據(jù)。
詳細(xì)資料可以參考:?《Vue.js 雙向綁定的實(shí)現(xiàn)原理》
110、 Object.defineProperty 介紹?
Object.defineProperty 函數(shù)一共有三個(gè)參數(shù),第一個(gè)參數(shù)是需要定義屬性的對(duì)象,第二個(gè)參數(shù)是需要定義的屬性,第三個(gè)是該屬性描述符。一個(gè)屬性的描述符有四個(gè)屬性,分別是 value 屬性的值,writable 屬性是否可寫,enumerable 屬性是否可枚舉,configurable 屬性是否可配置修改。
詳細(xì)資料可以參考:?《Object.defineProperty()》
111、?使用 Object.defineProperty() 來(lái)進(jìn)行數(shù)據(jù)劫持有什么缺點(diǎn)?
有一些對(duì)屬性的操作,使用這種方法無(wú)法攔截,比如說(shuō)通過(guò)下標(biāo)方式修改數(shù)組數(shù)據(jù)或者給對(duì)象新增屬性,vue 內(nèi)部通過(guò)重寫函數(shù)解決了這個(gè)問(wèn)題。在 Vue3.0 中已經(jīng)不使用這種方式了,而是通過(guò)使用 Proxy 對(duì)對(duì)象進(jìn)行代理,從而實(shí)現(xiàn)數(shù)據(jù)劫持。使用 Proxy 的好處是它可以完美的監(jiān)聽(tīng)到任何方式的數(shù)據(jù)改變,唯一的缺點(diǎn)是兼容性的問(wèn)題,因?yàn)檫@是 ES6 的語(yǔ)法。我對(duì) Virtual DOM 的理解是,首先對(duì)我們將要插入到文檔中的 DOM 樹結(jié)構(gòu)進(jìn)行分析,使用 js 對(duì)象將其表示出來(lái),比如一個(gè)元素對(duì)象,包含 TagName、props 和 Children 這些屬性。然后我們將這個(gè) js 對(duì)象樹給保存下來(lái),最后再將 DOM 片段插入到文檔中。當(dāng)頁(yè)面的狀態(tài)發(fā)生改變,我們需要對(duì)頁(yè)面的 DOM 的結(jié)構(gòu)進(jìn)行調(diào)整的時(shí)候,我們首先根據(jù)變更的狀態(tài),重新構(gòu)建起一棵對(duì)象樹,然后將這棵新的對(duì)象樹和舊的對(duì)象樹進(jìn)行比較,記錄下兩棵樹的的差異。最后將記錄的有差異的地方應(yīng)用到真正的 DOM 樹中去,這樣視圖就更新了。我認(rèn)為 Virtual DOM 這種方法對(duì)于我們需要有大量的 DOM 操作的時(shí)候,能夠很好的提高我們的操作效率,通過(guò)在操作前確定需要做的最小修改,盡可能的減少 DOM 操作帶來(lái)的重流和重繪的影響。其實(shí) Virtual DOM 并不一定比我們真實(shí)的操作 DOM 要快,這種方法的目的是為了提高我們開發(fā)時(shí)的可維護(hù)性,在任意的情況下,都能保證一個(gè)盡量小的性能消耗去進(jìn)行操作。
詳細(xì)資料可以參考:?《Virtual DOM》?《理解 Virtual DOM》?《深度剖析:如何實(shí)現(xiàn)一個(gè) Virtual DOM 算法》?《網(wǎng)上都說(shuō)操作真實(shí) DOM 慢,但測(cè)試結(jié)果卻比 React 更快,為什么?》
113、如何比較兩個(gè) DOM 樹的差異?
兩個(gè)樹的完全 diff 算法的時(shí)間復(fù)雜度為 O(n^3) ,但是在前端中,我們很少會(huì)跨層級(jí)的移動(dòng)元素,所以我們只需要比較同一層級(jí)的元素進(jìn)行比較,這樣就可以將算法的時(shí)間復(fù)雜度降低為 O(n)。算法首先會(huì)對(duì)新舊兩棵樹進(jìn)行一個(gè)深度優(yōu)先的遍歷,這樣每個(gè)節(jié)點(diǎn)都會(huì)有一個(gè)序號(hào)。在深度遍歷的時(shí)候,每遍歷到一個(gè)節(jié)點(diǎn),我們就將這個(gè)節(jié)點(diǎn)和新的樹中的節(jié)點(diǎn)進(jìn)行比較,如果有差異,則將這個(gè)差異記錄到一個(gè)對(duì)象中。在對(duì)列表元素進(jìn)行對(duì)比的時(shí)候,由于 TagName 是重復(fù)的,所以我們不能使用這個(gè)來(lái)對(duì)比。我們需要給每一個(gè)子節(jié)點(diǎn)加上一個(gè) key,列表對(duì)比的時(shí)候使用 key 來(lái)進(jìn)行比較,這樣我們才能夠復(fù)用老的 DOM 樹上的節(jié)點(diǎn)。
詳細(xì)資料可以參考:?《你需要知道的 requestAnimationFrame》?《CSS3 動(dòng)畫那么強(qiáng),requestAnimationFrame 還有毛線用?》
115、談?wù)勀銓?duì) webpack 的看法
webpack 的一個(gè)最主要原因是為了簡(jiǎn)化頁(yè)面依賴的管理,并且通過(guò)將其打包為一個(gè)文件來(lái)降低頁(yè)面加載時(shí)請(qǐng)求的資源數(shù)。webpack 的主要原理是,它將所有的資源都看成是一個(gè)模塊,并且把頁(yè)面邏輯當(dāng)作一個(gè)整體,通過(guò)一個(gè)給定的入口文件,webpack 從這個(gè)文件開始,找到所有的依賴文件,將各個(gè)依賴文件模塊通過(guò) loader 和 plugins 處理后,然后打包在一起,最后輸出一個(gè)瀏覽器可識(shí)別的 JS 文件。Webpack 具有四個(gè)核心的概念,分別是 Entry(入口)、Output(輸出)、loader 和 Plugins(插件)。Entry 是 webpack 的入口起點(diǎn),它指示 webpack 應(yīng)該從哪個(gè)模塊開始著手,來(lái)作為其構(gòu)建內(nèi)部依賴圖的開始。Output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的打包文件,也可指定打包文件的名稱,默認(rèn)位置為 ./dist。loader 可以理解為 webpack 的編譯器,它使得 webpack 可以處理一些非 JavaScript 文件。在對(duì) loader 進(jìn)行配置的時(shí)候,test 屬性,標(biāo)志有哪些后綴的文件應(yīng)該被處理,是一個(gè)正則表達(dá)式。use 屬性,指定 test 類型的文件應(yīng)該使用哪個(gè) loader 進(jìn)行預(yù)處理。常用的 loader 有 css-loader、style-loader 等。npm 包管理器進(jìn)行安裝,然后在配置文件中引入,最后將其實(shí)例化后傳遞給 plugins 數(shù)組屬性。webpack 的確能夠提供我們對(duì)于項(xiàng)目的管理,但是它的缺點(diǎn)就是調(diào)試和配置起來(lái)太麻煩了。但現(xiàn)在 webpack4.0 的免配置一定程度上解決了這個(gè)問(wèn)題。但是我感覺(jué)就是對(duì)我來(lái)說(shuō),就是一個(gè)黑盒,很多時(shí)候出現(xiàn)了問(wèn)題,沒(méi)有辦法很好的定位。
詳細(xì)資料可以參考:?《不聊 webpack 配置,來(lái)說(shuō)說(shuō)它的原理》?《前端工程化——構(gòu)建工具選型:grunt、gulp、webpack》?《淺入淺出 webpack》?《前端構(gòu)建工具發(fā)展及其比較》
116、offsetWidth/offsetHeight,clientWidth/clientHeight 與 scrollWidth/scrollHeight 的區(qū)別?
返回的是元素的內(nèi)部寬度,它的值只包含 content + padding,如果有滾動(dòng)條,不包含滾動(dòng)條。clientTop 返回的是上邊框的寬度。clientLeft 返回的左邊框的寬度。返回的是元素的布局寬度,它的值包含 content + padding + border 包含了滾動(dòng)條。offsetTop 返回的是當(dāng)前元素相對(duì)于其 offsetParent 元素的頂部的距離。offsetLeft 返回的是當(dāng)前元素相對(duì)于其 offsetParent 元素的左部的距離。返回值包含 content + padding + 溢出內(nèi)容的尺寸。scrollTop 屬性返回的是一個(gè)元素的內(nèi)容垂直滾動(dòng)的像素?cái)?shù)。scrollLeft 屬性返回的是元素滾動(dòng)條到元素左邊的距離。
詳細(xì)資料可以參考:?《最全的獲取元素寬高及位置的方法》?《用 Javascript 獲取頁(yè)面元素的位置》
117、談一談你理解的函數(shù)式編程?
簡(jiǎn)單說(shuō),"函數(shù)式編程"是一種"編程范式"(programming paradigm),也就是如何編寫程序的方法論。它具有以下特性:閉包和高階函數(shù)、惰性計(jì)算、遞歸、函數(shù)是"第一等公民"、只用"表達(dá)式"。
詳細(xì)資料可以參考:?《函數(shù)式編程初探》
118、異步編程的實(shí)現(xiàn)方式?
相關(guān)資料:
回調(diào)函數(shù)優(yōu)點(diǎn):簡(jiǎn)單、容易理解缺點(diǎn):不利于維護(hù),代碼耦合高事件監(jiān)聽(tīng)(采用時(shí)間驅(qū)動(dòng)模式,取決于某個(gè)事件是否發(fā)生):優(yōu)點(diǎn):容易理解,可以綁定多個(gè)事件,每個(gè)事件可以指定多個(gè)回調(diào)函數(shù)缺點(diǎn):事件驅(qū)動(dòng)型,流程不夠清晰發(fā)布/訂閱(觀察者模式)類似于事件監(jiān)聽(tīng),但是可以通過(guò)‘消息中心’,了解現(xiàn)在有多少發(fā)布者,多少訂閱者Promise 對(duì)象優(yōu)點(diǎn):可以利用 then 方法,進(jìn)行鏈?zhǔn)綄懛ǎ豢梢詴鴮戝e(cuò)誤時(shí)的回調(diào)函數(shù);缺點(diǎn):編寫和理解,相對(duì)比較難Generator 函數(shù)優(yōu)點(diǎn):函數(shù)體內(nèi)外的數(shù)據(jù)交換、錯(cuò)誤處理機(jī)制缺點(diǎn):流程管理不方便async 函數(shù)優(yōu)點(diǎn):內(nèi)置執(zhí)行器、更好的語(yǔ)義、更廣的適用性、返回的是 Promise、結(jié)構(gòu)清晰。缺點(diǎn):錯(cuò)誤處理機(jī)制
回答:
js 中的異步機(jī)制可以分為以下幾種:第一種最常見(jiàn)的是使用回調(diào)函數(shù)的方式,使用回調(diào)函數(shù)的方式有一個(gè)缺點(diǎn)是,多個(gè)回調(diào)函數(shù)嵌套的時(shí)候會(huì)造成回調(diào)函數(shù)地獄,上下兩層的回調(diào)函數(shù)間的代碼耦合度太高,不利于代碼的可維護(hù)。第二種是 Promise 的方式,使用 Promise 的方式可以將嵌套的回調(diào)函數(shù)作為鏈?zhǔn)秸{(diào)用。但是使用這種方法,有時(shí)會(huì)造成多個(gè) then 的鏈?zhǔn)秸{(diào)用,可能會(huì)造成代碼的語(yǔ)義不夠明確。第三種是使用 generator 的方式,它可以在函數(shù)的執(zhí)行過(guò)程中,將函數(shù)的執(zhí)行權(quán)轉(zhuǎn)移出去,在函數(shù)外部我們還可以將執(zhí)行權(quán)轉(zhuǎn)移回來(lái)。當(dāng)我們遇到異步函數(shù)執(zhí)行的時(shí)候,將函數(shù)執(zhí)行權(quán)轉(zhuǎn)移出去,當(dāng)異步函數(shù)執(zhí)行完畢的時(shí)候我們?cè)賹?zhí)行權(quán)給轉(zhuǎn)移回來(lái)。因此我們?cè)?generator 內(nèi)部對(duì)于異步操作的方式,可以以同步的順序來(lái)書寫。使用這種方式我們需要考慮的問(wèn)題是何時(shí)將函數(shù)的控制權(quán)轉(zhuǎn)移回來(lái),因此我們需要有一個(gè)自動(dòng)執(zhí)行 generator 的機(jī)制,比如說(shuō) co 模塊等方式來(lái)實(shí)現(xiàn) generator 的自動(dòng)執(zhí)行。第四種是使用 async 函數(shù)的形式,async 函數(shù)是 generator 和 promise 實(shí)現(xiàn)的一個(gè)自動(dòng)執(zhí)行的語(yǔ)法糖,它內(nèi)部自帶執(zhí)行器,當(dāng)函數(shù)內(nèi)部執(zhí)行到一個(gè) await 語(yǔ)句的時(shí)候,如果語(yǔ)句返回一個(gè) promise 對(duì)象,那么函數(shù)將會(huì)等待 promise 對(duì)象的狀態(tài)變?yōu)?resolve 后再繼續(xù)向下執(zhí)行。因此我們可以將異步邏輯,轉(zhuǎn)化為同步的順序來(lái)書寫,并且這個(gè)函數(shù)可以自動(dòng)執(zhí)行。
119、Js?動(dòng)畫與?CSS?動(dòng)畫區(qū)別及相應(yīng)實(shí)現(xiàn)
CSS3 的動(dòng)畫的優(yōu)點(diǎn)CSS3 的動(dòng)畫做一些優(yōu)化代碼相對(duì)簡(jiǎn)單缺點(diǎn)在動(dòng)畫控制上不夠靈活兼容性不好JavaScript 的動(dòng)畫正好彌補(bǔ)了這兩個(gè)缺點(diǎn),控制能力很強(qiáng),可以單幀的控制、變換,同時(shí)寫得好完全可以兼容 IE6,并且功能強(qiáng)大。對(duì)于一些復(fù)雜控制的動(dòng)畫,使用 javascript 會(huì)比較靠譜。而在實(shí)現(xiàn)一些小的交互動(dòng)效的時(shí)候,就多考慮考慮 CSS 吧
120、get?請(qǐng)求傳參長(zhǎng)度的誤區(qū)
誤區(qū):我們經(jīng)常說(shuō) get 請(qǐng)求參數(shù)的大小存在限制,而 post 請(qǐng)求的參數(shù)大小是無(wú)限制的。實(shí)際上 HTTP 協(xié)議從未規(guī)定 GET/POST 的請(qǐng)求長(zhǎng)度限制是多少。對(duì) get 請(qǐng)求參數(shù)的限制是來(lái)源與瀏覽器或web 服務(wù)器,瀏覽器或 web 服務(wù)器限制了 url 的長(zhǎng)度。為了明確這個(gè)概念,我們必須再次強(qiáng)調(diào)下面幾點(diǎn):
1.HTTP 協(xié)議未規(guī)定 GET 和 POST 的長(zhǎng)度限制
2.GET 的最大長(zhǎng)度顯示是因?yàn)闉g覽器和 web 服務(wù)器限制了 URI 的長(zhǎng)度
3.不同的瀏覽器和 WEB 服務(wù)器,限制的最大長(zhǎng)度不一樣
4.要支持 IE,則最大長(zhǎng)度為 2083byte,若只支持 Chrome,則最大長(zhǎng)度 8182byte
121、URL 和 URI 的區(qū)別?
URI: Uniform Resource Identifier 指的是統(tǒng)一資源標(biāo)識(shí)符URL: Uniform Resource Location 指的是統(tǒng)一資源定位符URN: Universal Resource Name 指的是統(tǒng)一資源名稱URI 指的是統(tǒng)一資源標(biāo)識(shí)符,用唯一的標(biāo)識(shí)來(lái)確定一個(gè)資源,它是一種抽象的定義,也就是說(shuō),不管使用什么方法來(lái)定義,只要能唯一的標(biāo)識(shí)一個(gè)資源,就可以稱為 URI。URL 指的是統(tǒng)一資源定位符,URN 指的是統(tǒng)一資源名稱。URL 和 URN 是 URI 的子集,URL 可以理解為使用地址來(lái)標(biāo)識(shí)資源,URN 可以理解為使用名稱來(lái)標(biāo)識(shí)資源。
詳細(xì)資料可以參考:?《HTTP 協(xié)議中 URI 和 URL 有什么區(qū)別?》?《你知道 URL、URI 和 URN 三者之間的區(qū)別嗎?》?《URI、URL 和 URN 的區(qū)別》
122、?get 和 post 請(qǐng)求在緩存方面的區(qū)別
相關(guān)知識(shí)點(diǎn):
get 請(qǐng)求類似于查找的過(guò)程,用戶獲取數(shù)據(jù),可以不用每次都與數(shù)據(jù)庫(kù)連接,所以可以使用緩存。post 不同,post 做的一般是修改和刪除的工作,所以必須與數(shù)據(jù)庫(kù)交互,所以不能使用緩存。因此 get 請(qǐng)求適合于請(qǐng)求緩存。
回答:
緩存一般只適用于那些不會(huì)更新服務(wù)端數(shù)據(jù)的請(qǐng)求。一般 get 請(qǐng)求都是查找請(qǐng)求,不會(huì)對(duì)服務(wù)器資源數(shù)據(jù)造成修改,而 post 請(qǐng)求一般都會(huì)對(duì)服務(wù)器數(shù)據(jù)造成修改,所以,一般會(huì)對(duì) get 請(qǐng)求進(jìn)行緩存,很少會(huì)對(duì) post 請(qǐng)求進(jìn)行緩存。詳細(xì)資料可以參考:?《HTML 關(guān)于 post 和 get 的區(qū)別以及緩存問(wèn)題的理解》
123、圖片的懶加載和預(yù)加載
相關(guān)知識(shí)點(diǎn):
預(yù)加載:提前加載圖片,當(dāng)用戶需要查看時(shí)可直接從本地緩存中渲染。懶加載:懶加載的主要目的是作為服務(wù)器前端的優(yōu)化,減少請(qǐng)求數(shù)或延遲請(qǐng)求數(shù)。兩種技術(shù)的本質(zhì):兩者的行為是相反的,一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對(duì)服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。
回答:
懶加載也叫延遲加載,指的是在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖片的時(shí)機(jī),當(dāng)用戶需要訪問(wèn)時(shí),再去加載,這樣可以提高網(wǎng)站的首屏加載速度,提升用戶的體驗(yàn),并且可以減少服務(wù)器的壓力。它適用于圖片很多,頁(yè)面很長(zhǎng)的電商網(wǎng)站的場(chǎng)景。懶加載的實(shí)現(xiàn)原理是,將頁(yè)面上的圖片的 src 屬性設(shè)置為空字符串,將圖片的真實(shí)路徑保存在一個(gè)自定義屬性中,當(dāng)頁(yè)面滾動(dòng)的時(shí)候,進(jìn)行判斷,如果圖片進(jìn)入頁(yè)面可視區(qū)域內(nèi),則從自定義屬性中取出真實(shí)路徑賦值給圖片的 src 屬性,以此來(lái)實(shí)現(xiàn)圖片的延遲加載。預(yù)加載指的是將所需的資源提前請(qǐng)求加載到本地,這樣后面在需要用到時(shí)就直接從緩存取資源。通過(guò)預(yù)加載能夠減少用戶的等待時(shí)間,提高用戶的體驗(yàn)。我了解的預(yù)加載的最常用的方式是使用 js 中的 image 對(duì)象,通過(guò)為 image 對(duì)象來(lái)設(shè)置 scr 屬性,來(lái)實(shí)現(xiàn)圖片的預(yù)加載。這兩種方式都是提高網(wǎng)頁(yè)性能的方式,兩者主要區(qū)別是一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對(duì)服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。
詳細(xì)資料可以參考:?《懶加載和預(yù)加載》?《網(wǎng)頁(yè)圖片加載優(yōu)化方案》?《基于用戶行為的圖片等資源預(yù)加載》
124、?mouseover 和 mouseenter 的區(qū)別?
當(dāng)鼠標(biāo)移動(dòng)到元素上時(shí)就會(huì)觸發(fā) mouseenter 事件,類似 mouseover,它們兩者之間的差別是 mouseenter 不會(huì)冒泡。由于 mouseenter 不支持事件冒泡,導(dǎo)致在一個(gè)元素的子元素上進(jìn)入或離開的時(shí)候會(huì)觸發(fā)其 mouseover 和 mouseout 事件,但是卻不會(huì)觸發(fā) mouseenter 和 mouseleave 事件。
詳細(xì)資料可以參考:?《mouseenter 與 mouseover 為何這般糾纏不清?》
125、?js 拖拽功能的實(shí)現(xiàn)
相關(guān)知識(shí)點(diǎn):
首先是三個(gè)事件,分別是 mousedown,mousemove,mouseup當(dāng)鼠標(biāo)點(diǎn)擊按下的時(shí)候,需要一個(gè) tag 標(biāo)識(shí)此時(shí)已經(jīng)按下,可以執(zhí)行 mousemove 里面的具體方法。clientX,clientY 標(biāo)識(shí)的是鼠標(biāo)的坐標(biāo),分別標(biāo)識(shí)橫坐標(biāo)和縱坐標(biāo),并且我們用 offsetX 和 offsetY 來(lái)表示元素的元素的初始坐標(biāo),移動(dòng)的舉例應(yīng)該是:鼠標(biāo)移動(dòng)時(shí)候的坐標(biāo)-鼠標(biāo)按下去時(shí)候的坐標(biāo)。也就是說(shuō)定位信息為:鼠標(biāo)移動(dòng)時(shí)候的坐標(biāo)-鼠標(biāo)按下去時(shí)候的坐標(biāo)+元素初始情況下的 offetLeft.
回答:
一個(gè)元素的拖拽過(guò)程,我們可以分為三個(gè)步驟,第一步是鼠標(biāo)按下目標(biāo)元素,第二步是鼠標(biāo)保持按下的狀態(tài)移動(dòng)鼠標(biāo),第三步是鼠標(biāo)抬起,拖拽過(guò)程結(jié)束。這三步分別對(duì)應(yīng)了三個(gè)事件,mousedown 事件,mousemove 事件和 mouseup 事件。只有在鼠標(biāo)按下的狀態(tài)移動(dòng)鼠標(biāo)我們才會(huì)執(zhí)行拖拽事件,因此我們需要在 mousedown 事件中設(shè)置一個(gè)狀態(tài)來(lái)標(biāo)識(shí)鼠標(biāo)已經(jīng)按下,然后在 mouseup 事件中再取消這個(gè)狀態(tài)。在 mousedown 事件中我們首先應(yīng)該判斷,目標(biāo)元素是否為拖拽元素,如果是拖拽元素,我們就設(shè)置狀態(tài)并且保存這個(gè)時(shí)候鼠標(biāo)的位置。然后在 mousemove 事件中,我們通過(guò)判斷鼠標(biāo)現(xiàn)在的位置和以前位置的相對(duì)移動(dòng),來(lái)確定拖拽元素在移動(dòng)中的坐標(biāo)。最后 mouseup 事件觸發(fā)后,清除狀態(tài),結(jié)束拖拽事件。
詳細(xì)資料可以參考:?《原生 js 實(shí)現(xiàn)拖拽功能基本思路》
126、為什么使用 setTimeout 實(shí)現(xiàn) setInterval?怎么模擬?
相關(guān)知識(shí)點(diǎn):
// 思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達(dá)到 setInterval 的效果function mySetInterval(fn, timeout) {// 控制器,控制定時(shí)器是否繼續(xù)執(zhí)行var timer = {flag: true};// 設(shè)置遞歸函數(shù),模擬定時(shí)器執(zhí)行。function interval() {if (timer.flag) {fn();setTimeout(interval, timeout);}}// 啟動(dòng)定時(shí)器setTimeout(interval, timeout);// 返回控制器return timer;}
回答:
setInterval 的作用是每隔一段指定時(shí)間執(zhí)行一個(gè)函數(shù),但是這個(gè)執(zhí)行不是真的到了時(shí)間立即執(zhí)行,它真正的作用是每隔一段時(shí)間將事件加入事件隊(duì)列中去,只有當(dāng)當(dāng)前的執(zhí)行棧為空的時(shí)候,才能去從事件隊(duì)列中取出事件執(zhí)行。所以可能會(huì)出現(xiàn)這樣的情況,就是當(dāng)前執(zhí)行棧執(zhí)行的時(shí)間很長(zhǎng),導(dǎo)致事件隊(duì)列里邊積累多個(gè)定時(shí)器加入的事件,當(dāng)執(zhí)行棧結(jié)束的時(shí)候,這些事件會(huì)依次執(zhí)行,因此就不能到間隔一段時(shí)間執(zhí)行的效果。針對(duì) setInterval 的這個(gè)缺點(diǎn),我們可以使用 setTimeout 遞歸調(diào)用來(lái)模擬 setInterval,這樣我們就確保了只有一個(gè)事件結(jié)束了,我們才會(huì)觸發(fā)下一個(gè)定時(shí)器事件,這樣解決了 setInterval 的問(wèn)題。
詳細(xì)資料可以參考:?《用 setTimeout 實(shí)現(xiàn) setInterval》?《setInterval 有什么缺點(diǎn)?》
127、let 和 const 的注意點(diǎn)?
1.聲明的變量只在聲明時(shí)的代碼塊內(nèi)有效
2.不存在聲明提升
3.存在暫時(shí)性死區(qū),如果在變量聲明前使用,會(huì)報(bào)錯(cuò)
4.不允許重復(fù)聲明,重復(fù)聲明會(huì)報(bào)錯(cuò)
128、什么是 rest 參數(shù)?
rest 參數(shù)(形式為...變量名),用于獲取函數(shù)的多余參數(shù)。129、什么是尾調(diào)用,使用尾調(diào)用有什么好處?
尾調(diào)用指的是函數(shù)的最后一步調(diào)用另一個(gè)函數(shù)。我們代碼執(zhí)行是基于執(zhí)行棧的,所以當(dāng)我們?cè)谝粋€(gè)函數(shù)里調(diào)用另一個(gè)函數(shù)時(shí),我們會(huì)保留當(dāng)前的執(zhí)行上下文,然后再新建另外一個(gè)執(zhí)行上下文加入棧中。使用尾調(diào)用的話,因?yàn)橐呀?jīng)是函數(shù)的最后一步,所以這個(gè)時(shí)候我們可以不必再保留當(dāng)前的執(zhí)行上下文,從而節(jié)省了內(nèi)存,這就是尾調(diào)用優(yōu)化。但是 ES6 的尾調(diào)用優(yōu)化只在嚴(yán)格模式下開啟,正常模式是無(wú)效的。1.Symbol 函數(shù)前不能使用 new 命令,否則會(huì)報(bào)錯(cuò)。
2.Symbol 函數(shù)可以接受一個(gè)字符串作為參數(shù),表示對(duì) Symbol 實(shí)例的描述,主要是為了在控制臺(tái)顯示,或者轉(zhuǎn)為字符串時(shí),比較容易區(qū)分。
3.Symbol 作為屬性名,該屬性不會(huì)出現(xiàn)在 for...in、for...of 循環(huán)中,也不會(huì)被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。
4.Object.getOwnPropertySymbols 方法返回一個(gè)數(shù)組,成員是當(dāng)前對(duì)象的所有用作屬性名的 Symbol 值。
5.Symbol.for 接受一個(gè)字符串作為參數(shù),然后搜索有沒(méi)有以該參數(shù)作為名稱的 Symbol 值。如果有,就返回這個(gè) Symbol 值,否則就新建并返回一個(gè)以該字符串為名稱的 Symbol 值。
6.Symbol.keyFor 方法返回一個(gè)已登記的 Symbol 類型值的 key。
131、Set 和 WeakSet 結(jié)構(gòu)?
1.ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類似于數(shù)組,但是成員的值都是唯一的,沒(méi)有重復(fù)的值。
2.WeakSet 結(jié)構(gòu)與 Set 類似,也是不重復(fù)的值的集合。但是 WeakSet 的成員只能是對(duì)象,而不能是其他類型的值。WeakSet 中的對(duì)象都是弱引用,即垃圾回收機(jī)制不考慮 WeakSet 對(duì)該對(duì)象的引用,
1.Map 數(shù)據(jù)結(jié)構(gòu)。它類似于對(duì)象,也是鍵值對(duì)的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對(duì)象)都可以當(dāng)作鍵。
2.WeakMap 結(jié)構(gòu)與 Map 結(jié)構(gòu)類似,也是用于生成鍵值對(duì)的集合。但是 WeakMap 只接受對(duì)象作為鍵名( null 除外),不接受其他類型的值作為鍵名。而且 WeakMap 的鍵名所指向的對(duì)象,不計(jì)入垃圾回收機(jī)制。
133、什么是 Proxy ?
Proxy 用于修改某些操作的默認(rèn)行為,等同于在語(yǔ)言層面做出修改,所以屬于一種“元編程”,即對(duì)編程語(yǔ)言進(jìn)行編程。Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來(lái)“代理”某些操作,可以譯為“代理器”。
1.將 Object 對(duì)象的一些明顯屬于語(yǔ)言內(nèi)部的方法(比如 Object.defineProperty,放到 Reflect 對(duì)象上。
2.修改某些 Object 方法的返回結(jié)果,讓其變得更合理。
3.讓 Object 操作都變成函數(shù)行為。
4.Reflect 對(duì)象的方法與 Proxy 對(duì)象的方法一一對(duì)應(yīng),只要是 Proxy 對(duì)象的方法,就能在 Reflect 對(duì)象上找到對(duì)應(yīng)的方法。這就讓 Proxy 對(duì)象可以方便地調(diào)用對(duì)應(yīng)的 Reflect 方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說(shuō),不管 Proxy 怎么修改默認(rèn)行為,你總可以在 Reflect 上獲取默認(rèn)行為。
135、?require 模塊引入的查找方式?
當(dāng) Node 遇到 require(X) 時(shí),按下面的順序處理。(1)如果 X 是內(nèi)置模塊(比如 require('http'))a. 返回該模塊。b. 不再繼續(xù)執(zhí)行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 開頭a. 根據(jù) X 所在的父模塊,確定 X 的絕對(duì)路徑。b. 將 X 當(dāng)成文件,依次查找下面文件,只要其中有一個(gè)存在,就返回該文件,不再繼續(xù)執(zhí)行。XX.jsX.jsonX.nodec. 將 X 當(dāng)成目錄,依次查找下面文件,只要其中有一個(gè)存在,就返回該文件,不再繼續(xù)執(zhí)行。X/package.json(main字段)X/index.jsX/index.jsonX/index.node(3)如果 X 不帶路徑a. 根據(jù) X 所在的父模塊,確定 X 可能的安裝目錄。b. 依次在每個(gè)目錄中,將 X 當(dāng)成文件名或目錄名加載。(4)拋出 "not found"
詳細(xì)資料可以參考:?《require() 源碼解讀》
136、?什么是 Promise 對(duì)象,什么是 Promises/A+ 規(guī)范?
Promise 對(duì)象是異步編程的一種解決方案,最早由社區(qū)提出。Promises/A+ 規(guī)范是 JavaScript Promise 的標(biāo)準(zhǔn),規(guī)定了一個(gè) Promise 所必須具有的特性。Promise 是一個(gè)構(gòu)造函數(shù),接收一個(gè)函數(shù)作為參數(shù),返回一個(gè) Promise 實(shí)例。一個(gè) Promise 實(shí)例有三種狀態(tài),分別是 pending、resolved 和 rejected,分別代表了進(jìn)行中、已成功和已失敗。實(shí)例的狀態(tài)只能由 pending 轉(zhuǎn)變 resolved 或者 rejected 狀態(tài),并且狀態(tài)一經(jīng)改變,就凝固了,無(wú)法再被改變了。狀態(tài)的改變是通過(guò) resolve() 和 reject() 函數(shù)來(lái)實(shí)現(xiàn)的,我們可以在異步操作結(jié)束后調(diào)用這兩個(gè)函數(shù)改變 Promise 實(shí)例的狀態(tài),它的原型上定義了一個(gè) then 方法,使用這個(gè) then 方法可以為兩個(gè)狀態(tài)的改變注冊(cè)回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)屬于微任務(wù),會(huì)在本輪事件循環(huán)的末尾執(zhí)行。
詳細(xì)資料可以參考:?《Promises/A+ 規(guī)范》?《Promise》
137、?手寫一個(gè) Promise
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";function MyPromise(fn) {// 保存初始化狀態(tài)var self = this;// 初始化狀態(tài)this.state = PENDING;// 用于保存 resolve 或者 rejected 傳入的值this.value = null;// 用于保存 resolve 的回調(diào)函數(shù)this.resolvedCallbacks = [];// 用于保存 reject 的回調(diào)函數(shù)this.rejectedCallbacks = [];// 狀態(tài)轉(zhuǎn)變?yōu)?resolved 方法function resolve(value) {// 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個(gè)狀態(tài)改變后再進(jìn)行改變if (value instanceof MyPromise) {return value.then(resolve, reject);}// 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾setTimeout(() => {// 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變,if (self.state === PENDING) {// 修改狀態(tài)self.state = RESOLVED;// 設(shè)置傳入的值self.value = value;// 執(zhí)行回調(diào)函數(shù)self.resolvedCallbacks.forEach(callback => {callback(value);});}}, 0);}// 狀態(tài)轉(zhuǎn)變?yōu)?rejected 方法function reject(value) {// 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾setTimeout(() => {// 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變if (self.state === PENDING) {// 修改狀態(tài)self.state = REJECTED;// 設(shè)置傳入的值self.value = value;// 執(zhí)行回調(diào)函數(shù)self.rejectedCallbacks.forEach(callback => {callback(value);});}}, 0);}// 將兩個(gè)方法傳入函數(shù)執(zhí)行try {fn(resolve, reject);} catch (e) {// 遇到錯(cuò)誤時(shí),捕獲錯(cuò)誤,執(zhí)行 reject 函數(shù)reject(e);}}MyPromise.prototype.then = function(onResolved, onRejected) {// 首先判斷兩個(gè)參數(shù)是否為函數(shù)類型,因?yàn)檫@兩個(gè)參數(shù)是可選參數(shù)onResolved =typeof onResolved === "function"? onResolved: function(value) {return value;};onRejected =typeof onRejected === "function"? onRejected: function(error) {throw error;};// 如果是等待狀態(tài),則將函數(shù)加入對(duì)應(yīng)列表中if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);this.rejectedCallbacks.push(onRejected);}// 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對(duì)應(yīng)狀態(tài)的函數(shù)if (this.state === RESOLVED) {onResolved(this.value);}if (this.state === REJECTED) {onRejected(this.value);}};
138、?如何檢測(cè)瀏覽器所支持的最小字體大小?
用 JS 設(shè)置 DOM 的字體為某一個(gè)值,然后再取出來(lái),如果值設(shè)置成功,就說(shuō)明支持。error 統(tǒng)計(jì)使用瀏覽器的 window.error 事件。單例模式保證了全局只有一個(gè)實(shí)例來(lái)被訪問(wèn)。比如說(shuō)常用的如彈框組件的實(shí)現(xiàn)和全局狀態(tài)的實(shí)現(xiàn)。141、策略模式是什么?
策略模式主要是用來(lái)將方法的實(shí)現(xiàn)和方法的調(diào)用分離開,外部通過(guò)不同的參數(shù)可以調(diào)用不同的策略。我主要在 MVP 模式解耦的時(shí)候用來(lái)將視圖層的方法定義和方法調(diào)用分離。
142、代理模式是什么?
代理模式是為一個(gè)對(duì)象提供一個(gè)代用品或占位符,以便控制對(duì)它的訪問(wèn)。比如說(shuō)常見(jiàn)的事件代理。中介者模式指的是,多個(gè)對(duì)象通過(guò)一個(gè)中介者進(jìn)行交流,而不是直接進(jìn)行交流,這樣能夠?qū)⑼ㄐ诺母鱾€(gè)對(duì)象解耦。適配器用來(lái)解決兩個(gè)接口不兼容的情況,不需要改變已有的接口,通過(guò)包裝一層的方式實(shí)現(xiàn)兩個(gè)接口的正常協(xié)作。假如我們需要一種新的接口返回方式,但是老的接口由于在太多地方已經(jīng)使用了,不能隨意更改,這個(gè)時(shí)候就可以使用適配器模式。比如我們需要一種自定義的時(shí)間返回格式,但是我們又不能對(duì) js 時(shí)間格式化的接口進(jìn)行修改,這個(gè)時(shí)候就可以使用適配器模式。
更多關(guān)于設(shè)計(jì)模式的資料可以參考:?《前端面試之道》?《JavaScript 設(shè)計(jì)模式》?《JavaScript 中常見(jiàn)設(shè)計(jì)模式整理》
145、觀察者模式和發(fā)布訂閱模式有什么不同?
發(fā)布訂閱模式其實(shí)屬于廣義上的觀察者模式在觀察者模式中,觀察者需要直接訂閱目標(biāo)事件。在目標(biāo)發(fā)出內(nèi)容改變的事件后,直接接收事件并作出響應(yīng)。而在發(fā)布訂閱模式中,發(fā)布者和訂閱者之間多了一個(gè)調(diào)度中心。調(diào)度中心一方面從發(fā)布者接收事件,另一方面向訂閱者發(fā)布事件,訂閱者需要在調(diào)度中心中訂閱事件。通過(guò)調(diào)度中心實(shí)現(xiàn)了發(fā)布者和訂閱者關(guān)系的解耦。使用發(fā)布訂閱者模式更利于我們代碼的可維護(hù)性。
詳細(xì)資料可以參考:?《觀察者模式和發(fā)布訂閱模式有什么不同?》
146、?Vue 的生命周期是什么?
Vue 的生命周期指的是組件從創(chuàng)建到銷毀的一系列的過(guò)程,被稱為 Vue 的生命周期。通過(guò)提供的 Vue 在生命周期各個(gè)階段的鉤子函數(shù),我們可以很好的在 Vue 的各個(gè)生命階段實(shí)現(xiàn)一些操作。147、?Vue?的各個(gè)生命階段是什么?
Vue 一共有8個(gè)生命階段,分別是創(chuàng)建前、創(chuàng)建后、加載前、加載后、更新前、更新后、銷毀前和銷毀后,每個(gè)階段對(duì)應(yīng)了一個(gè)生命周期的鉤子函數(shù)。(1)beforeCreate 鉤子函數(shù),在實(shí)例初始化之后,在數(shù)據(jù)監(jiān)聽(tīng)和事件配置之前觸發(fā)。因此在這個(gè)事件中我們是獲取不到 data 數(shù)據(jù)的。(2)created 鉤子函數(shù),在實(shí)例創(chuàng)建完成后觸發(fā),此時(shí)可以訪問(wèn) data、methods 等屬性。但這個(gè)時(shí)候組件還沒(méi)有被掛載到頁(yè)面中去,所以這個(gè)時(shí)候訪問(wèn)不到 $el 屬性。一般我們可以在這個(gè)函數(shù)中進(jìn)行一些頁(yè)面初始化的工作,比如通過(guò) ajax 請(qǐng)求數(shù)據(jù)來(lái)對(duì)頁(yè)面進(jìn)行初始化。(3)beforeMount 鉤子函數(shù),在組件被掛載到頁(yè)面之前觸發(fā)。在 beforeMount 之前,會(huì)找到對(duì)應(yīng)的 template,并編譯成 render 函數(shù)。(4)mounted 鉤子函數(shù),在組件掛載到頁(yè)面之后觸發(fā)。此時(shí)可以通過(guò) DOM API 獲取到頁(yè)面中的 DOM 元素。(5)beforeUpdate 鉤子函數(shù),在響應(yīng)式數(shù)據(jù)更新時(shí)觸發(fā),發(fā)生在虛擬 DOM 重新渲染和打補(bǔ)丁之前,這個(gè)時(shí)候我們可以對(duì)可能會(huì)被移除的元素做一些操作,比如移除事件監(jiān)聽(tīng)器。(6)updated 鉤子函數(shù),虛擬 DOM 重新渲染和打補(bǔ)丁之后調(diào)用。(7)beforeDestroy 鉤子函數(shù),在實(shí)例銷毀之前調(diào)用。一般在這一步我們可以銷毀定時(shí)器、解綁全局事件等。(8)destroyed 鉤子函數(shù),在實(shí)例銷毀之后調(diào)用,調(diào)用后,Vue 實(shí)例中的所有東西都會(huì)解除綁定,所有的事件監(jiān)聽(tīng)器會(huì)被移除,所有的子實(shí)例也會(huì)被銷毀。當(dāng)我們使用 keep-alive 的時(shí)候,還有兩個(gè)鉤子函數(shù),分別是 activated 和 deactivated 。用 keep-alive 包裹的組件在切換時(shí)不會(huì)進(jìn)行銷毀,而是緩存到內(nèi)存中并執(zhí)行 deactivated 鉤子函數(shù),命中緩存渲染后會(huì)執(zhí)行 actived 鉤子函數(shù)。
詳細(xì)資料可以參考:?《vue 生命周期深入》?《Vue 實(shí)例》
148、Vue 組件間的參數(shù)傳遞方式?
(1)父子組件間通信第一種方法是子組件通過(guò) props 屬性來(lái)接受父組件的數(shù)據(jù),然后父組件在子組件上注冊(cè)監(jiān)聽(tīng)事件,子組件通過(guò) emit 觸發(fā)事件來(lái)向父組件發(fā)送數(shù)據(jù)。第二種是通過(guò) ref 屬性給子組件設(shè)置一個(gè)名字。父組件通過(guò) $refs 組件名來(lái)獲得子組件,子組件通過(guò) $parent 獲得父組件,這樣也可以實(shí)現(xiàn)通信。第三種是使用 provider/inject,在父組件中通過(guò) provider 提供變量,在子組件中通過(guò) inject 來(lái)將變量注入到組件中。不論子組件有多深,只要調(diào)用了 inject 那么就可以注入 provider 中的數(shù)據(jù)。(2)兄弟組件間通信第一種是使用 eventBus 的方法,它的本質(zhì)是通過(guò)創(chuàng)建一個(gè)空的 Vue 實(shí)例來(lái)作為消息傳遞的對(duì)象,通信的組件引入這個(gè)實(shí)例,通信的組件通過(guò)在這個(gè)實(shí)例上監(jiān)聽(tīng)和觸發(fā)事件,來(lái)實(shí)現(xiàn)消息的傳遞。第二種是通過(guò) $parent.$refs 來(lái)獲取到兄弟組件,也可以進(jìn)行通信。(3)任意組件之間使用 eventBus ,其實(shí)就是創(chuàng)建一個(gè)事件中心,相當(dāng)于中轉(zhuǎn)站,可以用它來(lái)傳遞事件和接收事件。如果業(yè)務(wù)邏輯復(fù)雜,很多組件之間需要同時(shí)處理一些公共的數(shù)據(jù),這個(gè)時(shí)候采用上面這一些方法可能不利于項(xiàng)目的維護(hù)。這個(gè)時(shí)候可以使用 vuex ,vuex 的思想就是將這一些公共的數(shù)據(jù)抽離出來(lái),將它作為一個(gè)全局的變量來(lái)管理,然后其他組件就可以對(duì)這個(gè)公共數(shù)據(jù)進(jìn)行讀寫操作,這樣達(dá)到了解耦的目的。
詳細(xì)資料可以參考:?《VUE 組件之間數(shù)據(jù)傳遞全集》
149、computed 和 watch 的差異?
(1)computed 是計(jì)算一個(gè)新的屬性,并將該屬性掛載到 Vue 實(shí)例上,而 watch 是監(jiān)聽(tīng)已經(jīng)存在且已掛載到 Vue 實(shí)例上的數(shù)據(jù),所以用 watch 同樣可以監(jiān)聽(tīng) computed 計(jì)算屬性的變化。(2)computed 本質(zhì)是一個(gè)惰性求值的觀察者,具有緩存性,只有當(dāng)依賴變化后,第一次訪問(wèn) computed 屬性,才會(huì)計(jì)算新的值。而 watch 則是當(dāng)數(shù)據(jù)發(fā)生變化便會(huì)調(diào)用執(zhí)行函數(shù)。(3)從使用場(chǎng)景上說(shuō),computed 適用一個(gè)數(shù)據(jù)被多個(gè)數(shù)據(jù)影響,而 watch 適用一個(gè)數(shù)據(jù)影響多個(gè)數(shù)據(jù)。
詳細(xì)資料可以參考:?《做面試的不倒翁:淺談 Vue 中 computed 實(shí)現(xiàn)原理》?《深入理解 Vue 的 watch 實(shí)現(xiàn)原理及其實(shí)現(xiàn)方式》
150、vue-router 中的導(dǎo)航鉤子函數(shù)
(1)全局的鉤子函數(shù) beforeEach 和 afterEachbeforeEach 有三個(gè)參數(shù),to 代表要進(jìn)入的路由對(duì)象,from 代表離開的路由對(duì)象。next 是一個(gè)必須要執(zhí)行的函數(shù),如果不傳參數(shù),那就執(zhí)行下一個(gè)鉤子函數(shù),如果傳入 false,則終止跳轉(zhuǎn),如果傳入一個(gè)路徑,則導(dǎo)航到對(duì)應(yīng)的路由,如果傳入 error ,則導(dǎo)航終止,error 傳入錯(cuò)誤的監(jiān)聽(tīng)函數(shù)。(2)單個(gè)路由獨(dú)享的鉤子函數(shù) beforeEnter,它是在路由配置上直接進(jìn)行定義的。(3)組件內(nèi)的導(dǎo)航鉤子主要有這三種:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它們是直接在路由組件內(nèi)部直接進(jìn)行定義的。
詳細(xì)資料可以參考:?《導(dǎo)航守衛(wèi)》
151、$route 和 $router 的區(qū)別?
$route 是“路由信息對(duì)象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息參數(shù)。而 $router 是“路由實(shí)例”對(duì)象包括了路由的跳轉(zhuǎn)方法,鉤子函數(shù)等。.prevent: 提交事件不再重載頁(yè)面;.stop: 阻止單擊事件冒泡;.self: 當(dāng)事件發(fā)生在該元素本身而不是子元素的時(shí)候會(huì)觸發(fā);vue 中 key 值的作用可以分為兩種情況來(lái)考慮。第一種情況是 v-if 中使用 key。由于 Vue 會(huì)盡可能高效地渲染元素,通常會(huì)復(fù)用已有元素而不是從頭開始渲染。因此當(dāng)我們使用 v-if 來(lái)實(shí)現(xiàn)元素切換的時(shí)候,如果切換前后含有相同類型的元素,那么這個(gè)元素就會(huì)被復(fù)用。如果是相同的 input 元素,那么切換前后用戶的輸入不會(huì)被清除掉,這樣是不符合需求的。因此我們可以通過(guò)使用 key 來(lái)唯一的標(biāo)識(shí)一個(gè)元素,這個(gè)情況下,使用 key 的元素不會(huì)被復(fù)用。這個(gè)時(shí)候 key 的作用是用來(lái)標(biāo)識(shí)一個(gè)獨(dú)立的元素。第二種情況是 v-for 中使用 key。用 v-for 更新已渲染過(guò)的元素列表時(shí),它默認(rèn)使用“就地復(fù)用”的策略。如果數(shù)據(jù)項(xiàng)的順序發(fā)生了改變,Vue 不會(huì)移動(dòng) DOM 元素來(lái)匹配數(shù)據(jù)項(xiàng)的順序,而是簡(jiǎn)單復(fù)用此處的每個(gè)元素。因此通過(guò)為每個(gè)列表項(xiàng)提供一個(gè) key 值,來(lái)以便 Vue 跟蹤元素的身份,從而高效的實(shí)現(xiàn)復(fù)用。這個(gè)時(shí)候 key 的作用是為了高效的更新渲染虛擬 DOM。
詳細(xì)資料可以參考:?《Vue 面試中,經(jīng)常會(huì)被問(wèn)到的面試題 Vue 知識(shí)點(diǎn)整理》?《Vue2.0 v-for 中 :key 到底有什么用?》?《vue 中 key 的作用》
154、computed 和 watch 區(qū)別?
computed 是計(jì)算屬性,依賴其他屬性計(jì)算值,并且 computed 的值有緩存,只有當(dāng)計(jì)算值變化才會(huì)返回內(nèi)容。watch 監(jiān)聽(tīng)到值的變化就會(huì)執(zhí)行回調(diào),在回調(diào)中可以進(jìn)行一些邏輯操作。
155、keep-alive?組件有什么作用?
如果你需要在組件切換的時(shí)候,保存一些組件的狀態(tài)防止多次渲染,就可以使用 keep-alive 組件包裹需要保存的組件。mixin?用于全局混入,會(huì)影響到每個(gè)組件實(shí)例。mixins 應(yīng)該是我們最常使用的擴(kuò)展組件的方式了。如果多個(gè)組件中有相同的業(yè)務(wù)邏輯,就可以將這些邏輯剝離出來(lái),通過(guò) mixins 混入代碼,比如上拉下拉加載數(shù)據(jù)這種邏輯等等。另外需要注意的是 mixins 混入的鉤子函數(shù)會(huì)先于組件內(nèi)的鉤子函數(shù)執(zhí)行,并且在遇到同名選項(xiàng)的時(shí)候也會(huì)有選擇性的進(jìn)行合并
詳細(xì)資料可以參考:?《前端面試之道》?《混入》
157、開發(fā)中常用的幾種 Content-Type ?
(1)application/x-www-form-urlencoded瀏覽器的原生 form 表單,如果不設(shè)置 enctype 屬性,那么最終就會(huì)以 application/x-www-form-urlencoded 方式提交數(shù)據(jù)。該種方式提交的數(shù)據(jù)放在 body 里面,數(shù)據(jù)按照 key1=val1&key2=val2 的方式進(jìn)行編碼,key 和 val 都進(jìn)行了 URL轉(zhuǎn)碼。(2)multipart/form-data該種方式也是一個(gè)常見(jiàn)的 POST 提交方式,通常表單上傳文件時(shí)使用該種方式。(3)application/json告訴服務(wù)器消息主體是序列化后的 JSON 字符串。(4)text/xml該種方式主要用來(lái)提交 XML 格式的數(shù)據(jù)。
詳細(xì)資料可以參考:?《常用的幾種 Content-Type》
158、如何封裝一個(gè) javascript 的類型判斷函數(shù)?
function getType(value) {// 判斷數(shù)據(jù)是 null 的情況if (value === null) {return value + "";}// 判斷數(shù)據(jù)是引用類型的情況if (typeof value === "object") {let valueClass = Object.prototype.toString.call(value),type = valueClass.split(" ")[1].split("");type.pop();return type.join("").toLowerCase();} else {// 判斷數(shù)據(jù)是基本數(shù)據(jù)類型的情況和函數(shù)的情況return typeof value;}}
詳細(xì)資料可以參考:?《JavaScript 專題之類型判斷(上)》
159、如何判斷一個(gè)對(duì)象是否為空對(duì)象?
function checkNullObj(obj) {return Object.keys(obj).length === 0;}
詳細(xì)資料可以參考:?《js 判斷一個(gè) object 對(duì)象是否為空》
160、使用閉包實(shí)現(xiàn)每隔一秒打印 1,2,3,4
// 使用閉包實(shí)現(xiàn)for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);}, i * 1000);})(i);}// 使用 let 塊級(jí)作用域for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, i * 1000);}
function jsonp(url, params, callback) {// 判斷是否含有參數(shù)let queryString = url.indexOf("?") === "-1" ? "?" : "&";// 添加參數(shù)for (var k in params) {if (params.hasOwnProperty(k)) {queryString += k + "=" + params[k] + "&";}}// 處理回調(diào)函數(shù)名let random = Math.random().toString().replace(".", ""),callbackName = "myJsonp" + random;// 添加回調(diào)函數(shù)queryString += "callback=" + callbackName;// 構(gòu)建請(qǐng)求let scriptNode = document.createElement("script");scriptNode.src = url + queryString;window[callbackName] = function() {// 調(diào)用回調(diào)函數(shù)callback(...arguments);// 刪除這個(gè)引入的腳本document.getElementsByTagName("head")[0].removeChild(scriptNode);};// 發(fā)起請(qǐng)求document.getElementsByTagName("head")[0].appendChild(scriptNode);}
詳細(xì)資料可以參考:?《原生 jsonp 具體實(shí)現(xiàn)》?《jsonp 的原理與實(shí)現(xiàn)》
162、?手寫一個(gè)觀察者模式?
var events = (function() {var topics = {};return {// 注冊(cè)監(jiān)聽(tīng)函數(shù)subscribe: function(topic, handler) {if (!topics.hasOwnProperty(topic)) {topics[topic] = [];}topics[topic].push(handler);},// 發(fā)布事件,觸發(fā)觀察者回調(diào)事件publish: function(topic, info) {if (topics.hasOwnProperty(topic)) {topics[topic].forEach(function(handler) {handler(info);});}},// 移除主題的一個(gè)觀察者的回調(diào)事件remove: function(topic, handler) {if (!topics.hasOwnProperty(topic)) return;var handlerIndex = -1;topics[topic].forEach(function(item, index) {if (item === handler) {handlerIndex = index;}});if (handlerIndex >= 0) {topics[topic].splice(handlerIndex, 1);}},// 移除主題的所有觀察者的回調(diào)事件removeAll: function(topic) {if (topics.hasOwnProperty(topic)) {topics[topic] = [];}}};})();
詳細(xì)資料可以參考:?《JS 事件模型》
163、?EventEmitter 實(shí)現(xiàn)
class EventEmitter {constructor() {this.events = {};}on(event, callback) {let callbacks = this.events[event] || [];callbacks.push(callback);this.events[event] = callbacks;return this;}off(event, callback) {let callbacks = this.events[event];this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);return this;}emit(event, ...args) {let callbacks = this.events[event];callbacks.forEach(fn => {fn(...args);});return this;}once(event, callback) {let wrapFun = function(...args) {callback(...args);this.off(event, wrapFun);};this.on(event, wrapFun);return this;}}
function Foo() {getName = function() {alert(1);};return this;}Foo.getName = function() {alert(2);};Foo.prototype.getName = function() {alert(3);};var getName = function() {alert(4);};function getName() {alert(5);}//請(qǐng)寫出以下輸出結(jié)果:Foo.getName(); // 2getName(); // 4Foo().getName(); // 1getName(); // 1new Foo.getName(); // 2new Foo().getName(); // 3new new Foo().getName(); // 3
詳細(xì)資料可以參考:?《前端程序員經(jīng)常忽視的一個(gè) JavaScript 面試題》?《一道考察運(yùn)算符優(yōu)先級(jí)的 JavaScript 面試題》?《一道常被人輕視的前端 JS 面試題》
165、?如何確定頁(yè)面的可用性時(shí)間,什么是 Performance API?
Performance API 用于精確度量、控制、增強(qiáng)瀏覽器的性能表現(xiàn)。這個(gè) API 為測(cè)量網(wǎng)站性能,提供以前沒(méi)有辦法做到的精度。使用 getTime 來(lái)計(jì)算腳本耗時(shí)的缺點(diǎn),首先,getTime方法(以及 Date 對(duì)象的其他方法)都只能精確到毫秒級(jí)別(一秒的千分之一),想要得到更小的時(shí)間差別就無(wú)能為力了。其次,這種寫法只能獲取代碼運(yùn)行過(guò)程中的時(shí)間進(jìn)度,無(wú)法知道一些后臺(tái)事件的時(shí)間進(jìn)度,比如瀏覽器用了多少時(shí)間從服務(wù)器加載網(wǎng)頁(yè)。為了解決這兩個(gè)不足之處,ECMAScript 5引入“高精度時(shí)間戳”這個(gè) API,部署在 performance 對(duì)象上。它的精度可以達(dá)到1毫秒的千分之一(1秒的百萬(wàn)分之一)。navigationStart:當(dāng)前瀏覽器窗口的前一個(gè)網(wǎng)頁(yè)關(guān)閉,發(fā)生 unload 事件時(shí)的 Unix 毫秒時(shí)間戳。如果沒(méi)有前一個(gè)網(wǎng)頁(yè),則等于 fetchStart 屬性。loadEventEnd:返回當(dāng)前網(wǎng)頁(yè) load 事件的回調(diào)函數(shù)運(yùn)行結(jié)束時(shí)的 Unix 毫秒時(shí)間戳。如果該事件還沒(méi)有發(fā)生,返回 0。
根據(jù)上面這些屬性,可以計(jì)算出網(wǎng)頁(yè)加載各個(gè)階段的耗時(shí)。比如,網(wǎng)頁(yè)加載整個(gè)過(guò)程的耗時(shí)的計(jì)算方法如下:
var t = performance.timing;var pageLoadTime = t.loadEventEnd - t.navigationStart;
詳細(xì)資料可以參考:?《Performance API》
166、js 中的命名規(guī)則
(1)第一個(gè)字符必須是字母、下劃線(_)或美元符號(hào)($)(2)余下的字符可以是下劃線、美元符號(hào)或任何字母或數(shù)字字符一般我們推薦使用駝峰法來(lái)對(duì)變量名進(jìn)行命名,因?yàn)檫@樣可以與 ECMAScript 內(nèi)置的函數(shù)和對(duì)象命名格式保持一致。
詳細(xì)資料可以參考:?《ECMAScript 變量》
167、js 語(yǔ)句末尾分號(hào)是否可以省略?
在 ECMAScript 規(guī)范中,語(yǔ)句結(jié)尾的分號(hào)并不是必需的。但是我們一般最好不要省略分號(hào),因?yàn)榧由戏痔?hào)一方面有利于我們代碼的可維護(hù)性,另一方面也可以避免我們?cè)趯?duì)代碼進(jìn)行壓縮時(shí)出現(xiàn)錯(cuò)誤。
Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。Math.ceil() === 向上取整,函數(shù)返回一個(gè)大于或等于給定數(shù)字的最小整數(shù)。Math.floor() === 向下取整,函數(shù)返回一個(gè)小于或等于給定數(shù)字的最大整數(shù)。
for (var i = 0, j = 0; i < 5, j < 9; i++, j++) {console.log(i, j);}// 當(dāng)判斷語(yǔ)句含有多個(gè)語(yǔ)句時(shí),以最后一個(gè)判斷語(yǔ)句的值為準(zhǔn),因此上面的代碼會(huì)執(zhí)行 10 次。// 當(dāng)判斷語(yǔ)句為空時(shí),循環(huán)會(huì)一直進(jìn)行。
我們需要思考的問(wèn)題:該處理是否必須同步完成?數(shù)據(jù)是否必須按順序完成?解決辦法:(1)將數(shù)據(jù)分頁(yè),利用分頁(yè)的原理,每次服務(wù)器端只返回一定數(shù)目的數(shù)據(jù),瀏覽器每次只對(duì)一部分進(jìn)行加載。(2)使用懶加載的方法,每次加載一部分?jǐn)?shù)據(jù),其余數(shù)據(jù)當(dāng)需要使用時(shí)再去加載。(3)使用數(shù)組分塊技術(shù),基本思路是為要處理的項(xiàng)目創(chuàng)建一個(gè)隊(duì)列,然后設(shè)置定時(shí)器每過(guò)一段時(shí)間取出一部分?jǐn)?shù)據(jù),然后再使用定時(shí)器取出下一個(gè)要處理的項(xiàng)目進(jìn)行處理,接著再設(shè)置另一個(gè)定時(shí)器。
在前端實(shí)現(xiàn)中我們一般通過(guò) setTimeout 和 setInterval 方法來(lái)實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)效果。但是使用這些方法會(huì)存在時(shí)間偏差的問(wèn)題,這是由于 js 的程序執(zhí)行機(jī)制造成的,setTimeout 和 setInterval 的作用是隔一段時(shí)間將回調(diào)事件加入到事件隊(duì)列中,因此事件并不是立即執(zhí)行的,它會(huì)等到當(dāng)前執(zhí)行棧為空的時(shí)候再取出事件執(zhí)行,因此事件等待執(zhí)行的時(shí)間就是造成誤差的原因。一般解決倒計(jì)時(shí)中的誤差的有這樣兩種辦法:(1)第一種是通過(guò)前端定時(shí)向服務(wù)器發(fā)送請(qǐng)求獲取最新的時(shí)間差,以此來(lái)校準(zhǔn)倒計(jì)時(shí)時(shí)間。(2)第二種方法是前端根據(jù)偏差時(shí)間來(lái)自動(dòng)調(diào)整間隔時(shí)間的方式來(lái)實(shí)現(xiàn)的。這一種方式首先是以 setTimeout 遞歸的方式來(lái)實(shí)現(xiàn)倒計(jì)時(shí),然后通過(guò)一個(gè)變量來(lái)記錄已經(jīng)倒計(jì)時(shí)的秒數(shù)。每一次函數(shù)調(diào)用的時(shí)候,首先將變量加一,然后根據(jù)這個(gè)變量和每次的間隔時(shí)間,我們就可以計(jì)算出此時(shí)無(wú)偏差時(shí)應(yīng)該顯示的時(shí)間。然后將當(dāng)前的真實(shí)時(shí)間與這個(gè)時(shí)間相減,這樣我們就可以得到時(shí)間的偏差大小,因此我們?cè)谠O(shè)置下一個(gè)定時(shí)器的間隔大小的時(shí)候,我們就從間隔時(shí)間中減去這個(gè)偏差大小,以此來(lái)實(shí)現(xiàn)由于程序執(zhí)行所造成的時(shí)間誤差的糾正。
詳細(xì)資料可以參考:?《JavaScript 前端倒計(jì)時(shí)糾偏實(shí)現(xiàn)》
173、進(jìn)程間通信的方式?
1.管道通信
2.消息隊(duì)列通信
3.信號(hào)量通信
4.信號(hào)通信
5.共享內(nèi)存通信
6.套接字通信
詳細(xì)資料可以參考:?《進(jìn)程間 8 種通信方式詳解》?《進(jìn)程與線程的一個(gè)簡(jiǎn)單解釋》
174、?如何查找一篇英文文章中出現(xiàn)頻率最高的單詞?
function findMostWord(article) {// 合法性判斷if (!article) return;// 參數(shù)處理article = article.trim().toLowerCase();let wordList = article.match(/[a-z]+/g),visited = [],maxNum = 0,maxWord = "";article = " " + wordList.join(" ") + " ";// 遍歷判斷單詞出現(xiàn)次數(shù)wordList.forEach(function(item) {if (visited.indexOf(item) < 0) {// 加入 visitedvisited.push(item);let word = new RegExp(" " + item + " ", "g"),num = article.match(word).length;if (num > maxNum) {maxNum = num;maxWord = item;}}});return maxWord + " " + maxNum;}

