<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 面試知識(shí)點(diǎn)總結(jié)(下)

          共 29015字,需瀏覽 59分鐘

           ·

          2021-02-15 23:08

          來(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ù)起。Math.round 是將一個(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 型。
          存儲(chǔ)型指的是惡意代碼提交到了網(wǎng)站的數(shù)據(jù)庫(kù)中,當(dāng)用戶請(qǐng)求數(shù)據(jù)的時(shí)候,服務(wù)器將其拼接為 HTML 后返回給了用戶,從而導(dǎo)致了惡意代碼的執(zhí)行。
          反射型指的是攻擊者構(gòu)建了特殊的 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í)是不太可靠的。
          因此我們可以從瀏覽器的執(zhí)行來(lái)進(jìn)行預(yù)防,一種是使用純前端的方式,不用服務(wù)器端拼接后返回。另一種是對(duì)需要插入到 HTML 中的代碼做好充分的轉(zhuǎn)義。對(duì)于 DOM 型的攻擊,主要是前端腳本的不可靠而造成的,我們對(duì)于數(shù)據(jù)獲取渲染和字符串拼接的時(shí)候應(yīng)該對(duì)可能出現(xiàn)的惡意代碼情況進(jìn)行判斷。
          還有一些方式,比如使用 CSP ,CSP 的本質(zhì)是建立一個(gè)白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行,從而防止惡意代碼的注入攻擊。
          還可以對(duì)一些敏感信息進(jìn)行保護(hù),比如 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ù):
          第一種是同源檢測(cè)的方法,服務(wù)器根據(jù) 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í)這種方式不能做到子域名的隔離。
          第四種方式是使用在設(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 ?

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

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

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

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

          clientWidth/clientHeight 返回的是元素的內(nèi)部寬度,它的值只包含 content + padding,如果有滾動(dòng)條,不包含滾動(dòng)條。clientTop 返回的是上邊框的寬度。clientLeft 返回的左邊框的寬度。
          offsetWidth/offsetHeight 返回的是元素的布局寬度,它的值包含 content + padding + border 包含了滾動(dòng)條。offsetTop 返回的是當(dāng)前元素相對(duì)于其 offsetParent 元素的頂部的距離。offsetLeft 返回的是當(dāng)前元素相對(duì)于其 offsetParent 元素的左部的距離。
          scrollWidth/scrollHeight 返回值包含 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)
          在性能上會(huì)稍微好一些,瀏覽器會(huì)對(duì) 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ú)效的。
          130、Symbol?類型的注意點(diǎn)?
          • 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ì)象的引用,

          132、?Map 和 WeakMap 結(jié)構(gòu)?
          • 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)“代理”某些操作,可以譯為“代理器”。
          134、Reflect?對(duì)象創(chuàng)建目的?
          • 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í)行。 X X.js X.json X.node
            c. 將 X 當(dāng)成目錄,依次查找下面文件,只要其中有一個(gè)存在,就返回該文件,不再繼續(xù)執(zhí)行。 X/package.json(main字段) X/index.js X/index.json X/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ō)明支持。
          139、怎么做?JS?代碼?Error?統(tǒng)計(jì)?
          error 統(tǒng)計(jì)使用瀏覽器的 window.error 事件。
          140、單例模式模式是什么?
          單例模式保證了全局只有一個(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)的事件代理。
          ?143、?中介者模式是什么?
          中介者模式指的是,多個(gè)對(duì)象通過(guò)一個(gè)中介者進(jìn)行交流,而不是直接進(jìn)行交流,這樣能夠?qū)⑼ㄐ诺母鱾€(gè)對(duì)象解耦。
          144、適配器模式是什么?
          適配器用來(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 和 afterEach
          beforeEach 有三個(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ù)等。
          152、vue?常用的修飾符?
          .prevent: 提交事件不再重載頁(yè)面;.stop: 阻止單擊事件冒泡;.self: 當(dāng)事件發(fā)生在該元素本身而不是子元素的時(shí)候會(huì)觸發(fā);
          153、?vue?中?key?值的作用?
          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 組件包裹需要保存的組件。
          156、vue?中?mixin?和?mixins?區(qū)別?
          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);}
          161、手寫一個(gè)?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)建請(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; }}
          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);}
          //請(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ò)誤。
          168、Object.assign()
          Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。
          169、?Math.ceil?和?Math.floor
          Math.ceil() === 向上取整,函數(shù)返回一個(gè)大于或等于給定數(shù)字的最小整數(shù)。
          Math.floor() === 向下取整,函數(shù)返回一個(gè)小于或等于給定數(shù)字的最大整數(shù)。
          170、js?for?循環(huán)注意點(diǎn)
          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)行。
          171、一個(gè)列表,假設(shè)有?100000?個(gè)數(shù)據(jù),這個(gè)該怎么辦?
          我們需要思考的問(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í)器。
          172、js?中倒計(jì)時(shí)的糾偏實(shí)現(xiàn)?
          在前端實(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) {
          // 加入 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;}


          本文完?

          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  天堂中文字幕在线 | 一本色道久草在线 | 国精品无码一区二区三区四区五区 | 国产一区二区天堂 | 亚洲视频在线视频观看视频在线 |