<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          174道JavaScript 面試知識點總結(jié)(下)

          共 29800字,需瀏覽 60分鐘

           ·

          2021-03-17 12:24

          關(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ù)起。Math.round 是將一個數(shù)字四舍五入到一個整數(shù)。

          102、什么是 XSS 攻擊?如何防范 XSS 攻擊?

          XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者通過在網(wǎng)站注入惡意腳本,使之在用戶的瀏覽器上運行,從而盜取用戶的信息如 cookie 等。
          XSS 的本質(zhì)是因為網(wǎng)站沒有對惡意代碼進行過濾,與正常的代碼混合在一起了,瀏覽器沒有辦法分辨哪些腳本是可信的,從而導(dǎo)致了惡意代碼的執(zhí)行。
          XSS 一般分為存儲型、反射型和 DOM 型。
          存儲型指的是惡意代碼提交到了網(wǎng)站的數(shù)據(jù)庫中,當(dāng)用戶請求數(shù)據(jù)的時候,服務(wù)器將其拼接為 HTML 后返回給了用戶,從而導(dǎo)致了惡意代碼的執(zhí)行。
          反射型指的是攻擊者構(gòu)建了特殊的 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ù)最后的使用場景,所以直接在輸入端進行惡意代碼的處理,其實是不太可靠的。
          因此我們可以從瀏覽器的執(zhí)行來進行預(yù)防,一種是使用純前端的方式,不用服務(wù)器端拼接后返回。另一種是對需要插入到 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 可以用下面幾種方法來防護:
          第一種是同源檢測的方法,服務(wù)器根據(jù) 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 漏洞的,那么這種方式會失效。同時這種方式不能做到子域名的隔離。
          第四種方式是使用在設(shè)置 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 ?

          MVC、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 的語法。
          112、 什么是 Virtual DOM?為什么 Virtual DOM 比原生 DOM 快?
          我對 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é)點。
          114、什么是 requestAnimationFrame ?

          詳細(xì)資料可以參考: 《你需要知道的 requestAnimationFrame》 《CSS3 動畫那么強,requestAnimationFrame 還有毛線用?》

          115、談?wù)勀銓?webpack 的看法

          我當(dāng)時使用 webpack 的一個最主要原因是為了簡化頁面依賴的管理,并且通過將其打包為一個文件來降低頁面加載時請求的資源數(shù)。
          我認(rèn)為 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 等。
          插件可以用于執(zhí)行范圍更廣的任務(wù),包括打包、優(yōu)化、壓縮、搭建服務(wù)器等等,要使用一個插件,一般是先使用 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ū)別?

          clientWidth/clientHeight 返回的是元素的內(nèi)部寬度,它的值只包含 content + padding,如果有滾動條,不包含滾動條。clientTop 返回的是上邊框的寬度。clientLeft 返回的左邊框的寬度。
          offsetWidth/offsetHeight 返回的是元素的布局寬度,它的值包含 content + padding + border 包含了滾動條。offsetTop 返回的是當(dāng)前元素相對于其 offsetParent 元素的頂部的距離。offsetLeft 返回的是當(dāng)前元素相對于其 offsetParent 元素的左部的距離。
          scrollWidth/scrollHeight 返回值包含 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)格模式下開啟,正常模式是無效的。
          130、Symbol 類型的注意點?
          • 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 對該對象的引用,

          132、 Map 和 WeakMap 結(jié)構(gòu)?
          • 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 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
          134、Reflect 對象創(chuàng)建目的?
          • 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í)行。 X X.js X.json X.node
            c. 將 X 當(dāng)成目錄,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續(xù)執(zhí)行。 X/package.json(main字段) X/index.js X/index.json X/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è)置成功,就說明支持。
          139、怎么做 JS 代碼 Error 統(tǒng)計?
          error 統(tǒng)計使用瀏覽器的 window.error 事件。
          140、單例模式模式是什么?
          單例模式保證了全局只有一個實例來被訪問。比如說常用的如彈框組件的實現(xiàn)和全局狀態(tài)的實現(xiàn)。

          141、策略模式是什么?

          策略模式主要是用來將方法的實現(xiàn)和方法的調(diào)用分離開,外部通過不同的參數(shù)可以調(diào)用不同的策略。我主要在 MVP 模式解耦的時候用來將視圖層的方法定義和方法調(diào)用分離。

          142、代理模式是什么?

           代理模式是為一個對象提供一個代用品或占位符,以便控制對它的訪問。比如說常見的事件代理。
           143、 中介者模式是什么?
          中介者模式指的是,多個對象通過一個中介者進行交流,而不是直接進行交流,這樣能夠?qū)⑼ㄐ诺母鱾€對象解耦。
          144、適配器模式是什么?
          適配器用來解決兩個接口不兼容的情況,不需要改變已有的接口,通過包裝一層的方式實現(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 和 afterEach
          beforeEach 有三個參數(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ù)等。
          152、vue 常用的修飾符?
          .prevent: 提交事件不再重載頁面;.stop: 阻止單擊事件冒泡;.self: 當(dāng)事件發(fā)生在該元素本身而不是子元素的時候會觸發(fā);
          153、 vue 中 key 值的作用?
          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 組件包裹需要保存的組件。
          156、vue 中 mixin 和 mixins 區(qū)別?
          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);}
          161、手寫一個 jsonp
          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; }}
          164、一道常被人輕視的前端 JS 面試題
          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)錯誤。
          168、Object.assign()
          Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標(biāo)對象。它將返回目標(biāo)對象。
          169、 Math.ceil 和 Math.floor
          Math.ceil() === 向上取整,函數(shù)返回一個大于或等于給定數(shù)字的最小整數(shù)。
          Math.floor() === 向下取整,函數(shù)返回一個小于或等于給定數(shù)字的最大整數(shù)。
          170、js for 循環(huán)注意點
          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)會一直進行。
          171、一個列表,假設(shè)有 100000 個數(shù)據(jù),這個該怎么辦?
          我們需要思考的問題:該處理是否必須同步完成?數(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è)置另一個定時器。
          172、js 中倒計時的糾偏實現(xiàn)?
          在前端實現(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) {
          // 加入 visited visited.push(item);
          let word = new RegExp(" " + item + " ", "g"), num = article.match(word).length;
          if (num > maxNum) { maxNum = num; maxWord = item; } } });
          return maxWord + " " + maxNum;}


          本文完?

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「編程」,加入前端算法源碼群,每日一刷(工作日),每題瓶子君都會很認(rèn)真的解答喲
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美色俺去了 | 欧美一区操逼视频 | 男人天堂五月天 | 三级精品在线观看 | 午夜看片无码 |