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