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

          共 32471字,需瀏覽 65分鐘

           ·

          2021-03-22 00:14

          來源 | https://github.com/CavsZhouyou/


          全篇幅較長,保障閱讀體驗(yàn),故拆分為上中下3部分發(fā)布

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

          以下為第二部分

          62. 簡單談一下 cookie ?

          我的理解是 cookie 是服務(wù)器提供的一種用于維護(hù)會話狀態(tài)信息的數(shù)據(jù),通過服務(wù)器發(fā)送到瀏覽器,瀏覽器保存在本地,當(dāng)下一次有同源的請求時,將保存的 cookie 值添加到請求頭部,發(fā)送給服務(wù)端。這可以用來實(shí)現(xiàn)記錄用戶登錄狀態(tài)等功能。cookie 一般可以存儲 4k 大小的數(shù)據(jù),并且只能夠被同源的網(wǎng)頁所共享訪問。

          服務(wù)器端可以使用 Set-Cookie 的響應(yīng)頭部來配置 cookie 信息。一條cookie 包括了5個屬性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的時間,domain 是域名、path是路徑,domain 和 path 一起限制了 cookie 能夠被哪些 url 訪問。secure 規(guī)定了 cookie 只能在確保安全的情況下傳輸,HttpOnly 規(guī)定了這個 cookie 只能被服務(wù)器訪問,不能使用 js 腳本訪問。

          在發(fā)生 xhr 的跨域請求的時候,即使是同源下的 cookie,也不會被自動添加到請求頭部,除非顯示地規(guī)定。

          詳細(xì)資料可以參考:《HTTP cookies》 《聊一聊 cookie》

          63. 模塊化開發(fā)怎么做?

          我對模塊的理解是,一個模塊是實(shí)現(xiàn)一個特定功能的一組方法。在最開始的時候,js 只實(shí)現(xiàn)一些簡單的功能,所以并沒有模塊的概念
          ,但隨著程序越來越復(fù)雜,代碼的模塊化開發(fā)變得越來越重要。

          由于函數(shù)具有獨(dú)立作用域的特點(diǎn),最原始的寫法是使用函數(shù)來作為模塊,幾個函數(shù)作為一個模塊,但是這種方式容易造成全局變量的污
          染,并且模塊間沒有聯(lián)系。

          后面提出了對象寫法,通過將函數(shù)作為一個對象的方法來實(shí)現(xiàn),這樣解決了直接使用函數(shù)作為模塊的一些缺點(diǎn),但是這種辦法會暴露所
          有的所有的模塊成員,外部代碼可以修改內(nèi)部屬性的值。

          現(xiàn)在最常用的是立即執(zhí)行函數(shù)的寫法,通過利用閉包來實(shí)現(xiàn)模塊私有作用域的建立,同時不會對全局作用域造成污染。

          詳細(xì)資料可以參考:《淺談模塊化開發(fā)》《Javascript 模塊化編程(一):模塊的寫法》《前端模塊化:CommonJS,AMD,CMD,ES6》《Module 的語法》

          64. js 的幾種模塊規(guī)范?

          js 中現(xiàn)在比較成熟的有四種模塊加載方案。

          第一種是 CommonJS 方案,它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。這種模塊加載方案是
          服務(wù)器端的解決方案,它是以同步的方式來引入模塊的,因?yàn)樵诜?wù)端文件都存儲在本地磁盤,所以讀取非???,所以以同步的方式
          加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網(wǎng)絡(luò)請求,因此使用異步加載的方式更加合適。

          第二種是 AMD 方案,這種方案采用異步加載的方式來加載模塊,模塊的加載不影響后面語句的執(zhí)行,所有依賴這個模塊的語句都定
          義在一個回調(diào)函數(shù)里,等到加載完成后再執(zhí)行回調(diào)函數(shù)。require.js 實(shí)現(xiàn)了 AMD 規(guī)范。

          第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問題,sea.js 實(shí)現(xiàn)了 CMD 規(guī)范。它和 require.js
          的區(qū)別在于模塊定義時對依賴的處理不同和對依賴模塊的執(zhí)行時機(jī)的處理不同。參考60

          第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導(dǎo)入導(dǎo)出模塊。這種方案和上面三種方案都不同。參考 61。

          65. AMD 和 CMD 規(guī)范的區(qū)別?

          它們之間的主要區(qū)別有兩個方面。

          (1)第一個方面是在模塊定義時對依賴的處理不同。AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。而 CMD 推崇 就近依賴,只有在用到某個模塊的時候再去 require。

          (2)第二個方面是對依賴模塊的執(zhí)行時機(jī)處理不同。首先 AMD 和 CMD 對于模塊的加載方式都是異步加載,不過它們的區(qū)別在于 模塊的執(zhí)行時機(jī),AMD 在依賴模塊加載完成后就直接執(zhí)行依賴模塊,依賴模塊的執(zhí)行順序和我們書寫的順序不一定一致。而 CMD 在依賴模塊加載完成后并不執(zhí)行,只是下載而已,等到所有的依賴模塊都加載好后,進(jìn)入回調(diào)函數(shù)邏輯,遇到 require 語句 的時候才執(zhí)行對應(yīng)的模塊,這樣模塊的執(zhí)行順序就和我們書寫的順序保持一致了。

          // CMD
          define(function(require, exports, module{
            var a = require("./a");
            a.doSomething();
            // 此處略去 100 行
            var b = require("./b"); // 依賴可以就近書寫
            b.doSomething();
            // ...
          });

          // AMD 默認(rèn)推薦
          define(["./a""./b"], function(a, b{
            // 依賴必須一開始就寫好
            a.doSomething();
            // 此處略去 100 行
            b.doSomething();
            // ...
          });

          詳細(xì)資料可以參考:《前端模塊化,AMD 與 CMD 的區(qū)別》

          66. ES6 模塊與 CommonJS 模塊、AMD、CMD 的差異。

          • 1.CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值。ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令 import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。

          • 2.CommonJS 模塊是運(yùn)行時加載,ES6 模塊是編譯時輸出接口。CommonJS 模塊就是對象,即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運(yùn)行時加載”。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。

          67. requireJS 的核心原理是什么?(如何動態(tài)加載的?如何避免多次加載的?如何 緩存的?)

          require.js 的核心原理是通過動態(tài)創(chuàng)建 script 腳本來異步引入模塊,然后對每個腳本的 load 事件進(jìn)行監(jiān)聽,如果每個腳本都加載完成了,再調(diào)用回調(diào)函數(shù)。

          詳細(xì)資料可以參考:《requireJS 的用法和原理分析》《requireJS 的核心原理是什么?》《從 RequireJs 源碼剖析腳本加載原理》《requireJS 原理分析》

          68. JS 模塊加載器的輪子怎么造,也就是如何實(shí)現(xiàn)一個模塊加載器?

          詳細(xì)資料可以參考:《JS 模塊加載器加載原理是怎么樣的?》

          69. ECMAScript6 怎么寫 class,為什么會出現(xiàn) class 這種東西?

          在我看來 ES6 新添加的 class 只是為了補(bǔ)充 js 中缺少的一些面向?qū)ο笳Z言的特性,但本質(zhì)上來說它只是一種語法糖,不是一個新的東西,其背后還是原型繼承的思想。通過加入 class 可以有利于我們更好的組織代碼。

          在 class 中添加的方法,其實(shí)是添加在類的原型上的。

          詳細(xì)資料可以參考:《ECMAScript 6 實(shí)現(xiàn)了 class,對 JavaScript 前端開發(fā)有什么意義?》《Class 的基本語法》

          70. documen.write 和 innerHTML 的區(qū)別?

          document.write 的內(nèi)容會代替整個文檔內(nèi)容,會重寫整個頁面。

          innerHTML 的內(nèi)容只是替代指定元素的內(nèi)容,只會重寫頁面中的部分內(nèi)容。

          詳細(xì)資料可以參考:《簡述 document.write 和 innerHTML 的區(qū)別?!?/p>

          71. DOM 操作——怎樣添加、移除、移動、復(fù)制、創(chuàng)建和查找節(jié)點(diǎn)?

          (1)創(chuàng)建新節(jié)點(diǎn)

          createDocumentFragment(node);
          createElement(node);
          createTextNode(text);

          (2)添加、移除、替換、插入

          appendChild(node)
          removeChild(node)
          replaceChild(new,old)
          insertBefore(new,old)

          (3)查找

          getElementById();
          getElementsByName();
          getElementsByTagName();
          getElementsByClassName();
          querySelector();
          querySelectorAll();

          (4)屬性操作

          getAttribute(key);
          setAttribute(key, value);
          hasAttribute(key);
          removeAttribute(key);

          詳細(xì)資料可以參考:《DOM 概述》《原生 JavaScript 的 DOM 操作匯總》《原生 JS 中 DOM 節(jié)點(diǎn)相關(guān) API 合集》

          72. innerHTML 與 outerHTML 的區(qū)別?

          對于這樣一個 HTML 元素:<div>content<br/></div>。

          innerHTML:內(nèi)部 HTML,content<br/>;
          outerHTML:外部 HTML,<div>content<br/></div>;
          innerText:內(nèi)部文本,content ;
          outerText:內(nèi)部文本,content ;

          73. .call() 和 .apply() 的區(qū)別?

          它們的作用一模一樣,區(qū)別僅在于傳入?yún)?shù)的形式的不同。

          apply 接受兩個參數(shù),第一個參數(shù)指定了函數(shù)體內(nèi) this 對象的指向,第二個參數(shù)為一個帶下標(biāo)的集合,這個集合可以為數(shù)組,也可以為類數(shù)組,apply 方法把這個集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)。

          call 傳入的參數(shù)數(shù)量不固定,跟 apply 相同的是,第一個參數(shù)也是代表函數(shù)體內(nèi)的 this 指向,從第二個參數(shù)開始往后,每個參數(shù)被依次傳入函數(shù)。

          詳細(xì)資料可以參考:《apply、call 的區(qū)別和用途》

          74. JavaScript 類數(shù)組對象的定義?

          一個擁有 length 屬性和若干索引屬性的對象就可以被稱為類數(shù)組對象,類數(shù)組對象和數(shù)組類似,但是不能調(diào)用數(shù)組的方法。

          常見的類數(shù)組對象有 arguments 和 DOM 方法的返回結(jié)果,還有一個函數(shù)也可以被看作是類數(shù)組對象,因?yàn)樗?nbsp;length
          屬性值,代表可接收的參數(shù)個數(shù)。

          常見的類數(shù)組轉(zhuǎn)換為數(shù)組的方法有這樣幾種:

          (1)通過 call 調(diào)用數(shù)組的 slice 方法來實(shí)現(xiàn)轉(zhuǎn)換

          Array.prototype.slice.call(arrayLike);

          (2)通過 call 調(diào)用數(shù)組的 splice 方法來實(shí)現(xiàn)轉(zhuǎn)換

          Array.prototype.splice.call(arrayLike, 0);

          (3)通過 apply 調(diào)用數(shù)組的 concat 方法來實(shí)現(xiàn)轉(zhuǎn)換

          Array.prototype.concat.apply([], arrayLike);

          (4)通過 Array.from 方法來實(shí)現(xiàn)轉(zhuǎn)換

          Array.from(arrayLike);

          詳細(xì)的資料可以參考:《JavaScript 深入之類數(shù)組對象與 arguments》《javascript 類數(shù)組》《深入理解 JavaScript 類數(shù)組》

          75. 數(shù)組和對象有哪些原生方法,列舉一下?

          數(shù)組和字符串的轉(zhuǎn)換方法:toString()、toLocalString()、join() 其中 join() 方法可以指定轉(zhuǎn)換為字符串時的分隔符。

          數(shù)組尾部操作的方法 pop() 和 push(),push 方法可以傳入多個參數(shù)。

          數(shù)組首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以傳入一個函數(shù)來進(jìn)行比較,傳入前后兩個值,如果返回值為正數(shù),則交換兩個參數(shù)的位置。

          數(shù)組連接的方法 concat() ,返回的是拼接好的數(shù)組,不影響原數(shù)組。

          數(shù)組截取辦法 slice(),用于截取數(shù)組中的一部分返回,不影響原數(shù)組。

          數(shù)組插入方法 splice(),影響原數(shù)組查找特定項(xiàng)的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法

          數(shù)組歸并方法 reduce() 和 reduceRight() 方法

          詳細(xì)資料可以參考:《JavaScript 深入理解之 Array 類型詳解》

          76. 數(shù)組的 fill 方法?

          fill() 方法用一個固定值填充一個數(shù)組中從起始索引到終止索引內(nèi)的全部元素。不包括終止索引。
          fill 方法接受三個參數(shù) value,start 以及 end,start 和 end 參數(shù)是可選的,其默認(rèn)值分別為 0 和 this 對象的 length 屬性值。

          詳細(xì)資料可以參考:《Array.prototype.fill()》

          77. [,,,] 的長度?

          尾后逗號 (有時叫做“終止逗號”)在向 JavaScript 代碼添加元素、參數(shù)、屬性時十分有用。如果你想要添加新的屬性,并且上一行已經(jīng)使用了尾后逗號,你可以僅僅添加新的一行,而不需要修改上一行。這使得版本控制更加清晰,以及代碼維護(hù)麻煩更少。

          JavaScript 一開始就支持?jǐn)?shù)組字面值中的尾后逗號,隨后向?qū)ο笞置嬷担‥CMAScript 5)中添加了尾后逗號。最近(ECMAS
          cript 2017),又將其添加到函數(shù)參數(shù)中。但是 JSON 不支持尾后逗號。

          如果使用了多于一個尾后逗號,會產(chǎn)生間隙。 帶有間隙的數(shù)組叫做稀疏數(shù)組(密致數(shù)組沒有間隙)。稀疏數(shù)組的長度為逗號的數(shù)
          量。

          詳細(xì)資料可以參考:《尾后逗號》

          78. JavaScript 中的作用域與變量聲明提升?

          變量提升的表現(xiàn)是,無論我們在函數(shù)中何處位置聲明的變量,好像都被提升到了函數(shù)的首部,我們可以在變量聲明前訪問到而不會報(bào)錯。

          造成變量聲明提升的本質(zhì)原因是 js 引擎在代碼執(zhí)行前有一個解析的過程,創(chuàng)建了執(zhí)行上下文,初始化了一些代碼執(zhí)行時需要用到的對象。當(dāng)我們訪問一個變量時,我們會到當(dāng)前執(zhí)行上下文中的作用域鏈中去查找,而作用域鏈的首端指向的是當(dāng)前執(zhí)行上下文的變量對象,這個變量對象是執(zhí)行上下文的一個屬性,它包含了函數(shù)的形參、所有的函數(shù)和變量聲明,這個對象的是在代碼解析的時候創(chuàng)建的。這就是會出現(xiàn)變量聲明提升的根本原因。

          詳細(xì)資料可以參考:《JavaScript 深入理解之變量對象》

          79. 如何編寫高性能的 Javascript ?

          • 1.使用位運(yùn)算代替一些簡單的四則運(yùn)算。
          • 2.避免使用過深的嵌套循環(huán)。
          • 3.不要使用未定義的變量。
          • 4.當(dāng)需要多次訪問數(shù)組長度時,可以用變量保存起來,避免每次都會去進(jìn)行屬性查找。

          詳細(xì)資料可以參考:《如何編寫高性能的 Javascript?》

          80. 簡單介紹一下 V8 引擎的垃圾回收機(jī)制

          v8 的垃圾回收機(jī)制基于分代回收機(jī)制,這個機(jī)制又基于世代假說,這個假說有兩個特點(diǎn),一是新生的對象容易早死,另一個是不死的對象會活得更久?;谶@個假說,v8 引擎將內(nèi)存分為了新生代和老生代。

          新創(chuàng)建的對象或者只經(jīng)歷過一次的垃圾回收的對象被稱為新生代。經(jīng)歷過多次垃圾回收的對象被稱為老生代。

          新生代被分為 From 和 To 兩個空間,To 一般是閑置的。當(dāng) From 空間滿了的時候會執(zhí)行 Scavenge 算法進(jìn)行垃圾回收。當(dāng)我們執(zhí)行垃圾回收算法的時候應(yīng)用邏輯將會停止,等垃圾回收結(jié)束后再繼續(xù)執(zhí)行。這個算法分為三步:

          (1)首先檢查 From 空間的存活對象,如果對象存活則判斷對象是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動 To 空間。

          (2)如果對象不存活,則釋放對象的空間。

          (3)最后將 From 空間和 To 空間角色進(jìn)行交換。

          新生代對象晉升到老生代有兩個條件:

          (1)第一個是判斷是對象否已經(jīng)經(jīng)過一次 Scavenge 回收。若經(jīng)歷過,則將對象從 From 空間復(fù)制到老生代中;若沒有經(jīng)歷,則復(fù)制到 To 空間。

          (2)第二個是 To 空間的內(nèi)存使用占比是否超過限制。當(dāng)對象從 From 空間復(fù)制到 To 空間時,若 To 空間使用超過 25%,則對象直接晉升到老生代中。設(shè)置 25% 的原因主要是因?yàn)樗惴ńY(jié)束后,兩個空間結(jié)束后會交換位置,如果 To 空間的內(nèi)存太小,會影響后續(xù)的內(nèi)存分配。

          老生代采用了標(biāo)記清除法和標(biāo)記壓縮法。標(biāo)記清除法首先會對內(nèi)存中存活的對象進(jìn)行標(biāo)記,標(biāo)記結(jié)束后清除掉那些沒有標(biāo)記的對象。由于標(biāo)記清除后會造成很多的內(nèi)存碎片,不便于后面的內(nèi)存分配。所以了解決內(nèi)存碎片的問題引入了標(biāo)記壓縮法。

          由于在進(jìn)行垃圾回收的時候會暫停應(yīng)用的邏輯,對于新生代方法由于內(nèi)存小,每次停頓的時間不會太長,但對于老生代來說每次垃圾回收的時間長,停頓會造成很大的影響。 為了解決這個問題 V8 引入了增量標(biāo)記的方法,將一次停頓進(jìn)行的過程分為了多步,每次執(zhí)行完一小步就讓運(yùn)行邏輯執(zhí)行一會,就這樣交替運(yùn)行。

          詳細(xì)資料可以參考:《深入理解 V8 的垃圾回收原理》《JavaScript 中的垃圾回收》

          81. 哪些操作會造成內(nèi)存泄漏?

          相關(guān)知識點(diǎn):

          • 1.意外的全局變量
          • 2.被遺忘的計(jì)時器或回調(diào)函數(shù)
          • 3.脫離 DOM 的引用
          • 4.閉包

          回答:

          第一種情況是我們由于使用未聲明的變量,而意外的創(chuàng)建了一個全局變量,而使這個變量一直留在內(nèi)存中無法被回收。

          第二種情況是我們設(shè)置了 setInterval 定時器,而忘記取消它,如果循環(huán)函數(shù)有對外部變量的引用的話,那么這個變量會被一直留
          在內(nèi)存中,而無法被回收。

          第三種情況是我們獲取一個 DOM 元素的引用,而后面這個元素被刪除,由于我們一直保留了對這個元素的引用,所以它也無法被回
          收。

          第四種情況是不合理的使用閉包,從而導(dǎo)致某些變量一直被留在內(nèi)存當(dāng)中。

          詳細(xì)資料可以參考:《JavaScript 內(nèi)存泄漏教程》《4 類 JavaScript 內(nèi)存泄漏及如何避免》《杜絕 js 中四種內(nèi)存泄漏類型的發(fā)生》《javascript 典型內(nèi)存泄漏及 chrome 的排查方法》

          82. 需求:實(shí)現(xiàn)一個頁面操作不會整頁刷新的網(wǎng)站,并且能在瀏覽器前進(jìn)、后退時正確響應(yīng)。給出你的技術(shù)實(shí)現(xiàn)方案?

          通過使用 pushState + ajax 實(shí)現(xiàn)瀏覽器無刷新前進(jìn)后退,當(dāng)一次 ajax 調(diào)用成功后我們將一條 state 記錄加入到 history
          對象中。一條 state 記錄包含了 url、title 和 content 屬性,在 popstate 事件中可以獲取到這個 state 對象,我們可
          以使用 content 來傳遞數(shù)據(jù)。最后我們通過對 window.onpopstate 事件監(jiān)聽來響應(yīng)瀏覽器的前進(jìn)后退操作。

          使用 pushState 來實(shí)現(xiàn)有兩個問題,一個是打開首頁時沒有記錄,我們可以使用 replaceState 來將首頁的記錄替換,另一個問
          題是當(dāng)一個頁面刷新的時候,仍然會向服務(wù)器端請求數(shù)據(jù),因此如果請求的 url 需要后端的配合將其重定向到一個頁面。

          詳細(xì)資料可以參考:《pushState + ajax 實(shí)現(xiàn)瀏覽器無刷新前進(jìn)后退》《Manipulating the browser history》

          83. 如何判斷當(dāng)前腳本運(yùn)行在瀏覽器還是 node 環(huán)境中?(阿里)

          this === window ? 'browser' : 'node';

          通過判斷 Global 對象是否為 window,如果不為 window,當(dāng)前腳本沒有運(yùn)行在瀏覽器中。

          84. 把 script 標(biāo)簽放在頁面的最底部的 body 封閉之前和封閉之后有什么區(qū)別?瀏覽器會如何解析它們?

          詳細(xì)資料可以參考:《為什么把 script 標(biāo)簽放在 body 結(jié)束標(biāo)簽之后 html 結(jié)束標(biāo)簽之前?》《從 Chrome 源碼看瀏覽器如何加載資源》

          85. 移動端的點(diǎn)擊事件的有延遲,時間是多久,為什么會有?怎么解決這個延時?

          移動端點(diǎn)擊有 300ms 的延遲是因?yàn)橐苿佣藭须p擊縮放的這個操作,因此瀏覽器在 click 之后要等待 300ms,看用戶有沒有下一次點(diǎn)擊,來判斷這次操作是不是雙擊。

          有三種辦法來解決這個問題:

          • 1.通過 meta 標(biāo)簽禁用網(wǎng)頁的縮放。
          • 2.通過 meta 標(biāo)簽將網(wǎng)頁的 viewport 設(shè)置為 ideal viewport。
          • 3.調(diào)用一些 js 庫,比如 FastClick
          click 延時問題還可能引起點(diǎn)擊穿透的問題,就是如果我們在一個元素上注冊了 touchStart 的監(jiān)聽事件,這個事件會將這個元素隱藏掉,我們發(fā)現(xiàn)當(dāng)這個元素隱藏后,觸發(fā)了這個元素下的一個元素的點(diǎn)擊事件,這就是點(diǎn)擊穿透。

          詳細(xì)資料可以參考:《移動端 300ms 點(diǎn)擊延遲和點(diǎn)擊穿透》

          86. 什么是“前端路由”?什么時候適合使用“前端路由”?“前端路由”有哪些優(yōu)點(diǎn)和缺點(diǎn)?

          (1)什么是前端路由?

          前端路由就是把不同路由對應(yīng)不同的內(nèi)容或頁面的任務(wù)交給前端來做,之前是通過服務(wù)端根據(jù) url 的不同返回不同的頁面實(shí)現(xiàn)的。

          (2)什么時候使用前端路由?

          在單頁面應(yīng)用,大部分頁面結(jié)構(gòu)不變,只改變部分內(nèi)容的使用

          (3)前端路由有什么優(yōu)點(diǎn)和缺點(diǎn)?

          優(yōu)點(diǎn):用戶體驗(yàn)好,不需要每次都從服務(wù)器全部獲取,快速展現(xiàn)給用戶

          缺點(diǎn):單頁面無法記住之前滾動的位置,無法在前進(jìn),后退的時候記住滾動的位置

          前端路由一共有兩種實(shí)現(xiàn)方式,一種是通過 hash 的方式,一種是通過使用 pushState 的方式。

          詳細(xì)資料可以參考:《什么是“前端路由”》《淺談前端路由》 《前端路由是什么東西?》

          87. 如何測試前端代碼么?知道 BDD, TDD, Unit Test 么?知道怎么測試你的前端工程么(mocha, sinon, jasmin, qUnit..)?

          詳細(xì)資料可以參考:《淺談前端單元測試》

          88. 檢測瀏覽器版本版本有哪些方式?

          檢測瀏覽器版本一共有兩種方式:

          一種是檢測 window.navigator.userAgent 的值,但這種方式很不可靠,因?yàn)?userAgent 可以被改寫,并且早期的瀏覽器如 ie,會通過偽裝自己的 userAgent 的值為 Mozilla 來躲過服務(wù)器的檢測。

          第二種方式是功能檢測,根據(jù)每個瀏覽器獨(dú)有的特性來進(jìn)行判斷,如 ie 下獨(dú)有的 ActiveXObject。

          詳細(xì)資料可以參考:《JavaScript 判斷瀏覽器類型》

          89. 什么是 Polyfill ?

          Polyfill 指的是用于實(shí)現(xiàn)瀏覽器并不支持的原生 API 的代碼。

          比如說 querySelectorAll 是很多現(xiàn)代瀏覽器都支持的原生 Web API,但是有些古老的瀏覽器并不支持,那么假設(shè)有人寫了一段代碼來實(shí)現(xiàn)這個功能使這些瀏覽器也支持了這個功能,那么這就可以成為一個 Polyfill。

          一個 shim 是一個庫,有自己的 API,而不是單純實(shí)現(xiàn)原生不支持的 API。

          詳細(xì)資料可以參考:《Web 開發(fā)中的“黑話”》《Polyfill 為何物》

          90. 使用 JS 實(shí)現(xiàn)獲取文件擴(kuò)展名?

          // String.lastIndexOf() 方法返回指定值(本例中的'.')在調(diào)用該方法的字符串中最后出現(xiàn)的位置,如果沒找到則返回 -1。

          // 對于 'filename' 和 '.hiddenfile' ,lastIndexOf 的返回值分別為 0 和 -1 無符號右移操作符(>>>) 將 -1 轉(zhuǎn)換為 4294967295 ,將 -2 轉(zhuǎn)換為 4294967294 ,這個方法可以保證邊緣情況時文件名不變。

          // String.prototype.slice() 從上面計(jì)算的索引處提取文件的擴(kuò)展名。如果索引比文件名的長度大,結(jié)果為""。
          function getFileExtension(filename{
            return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
          }

          詳細(xì)資料可以參考:《如何更有效的獲取文件擴(kuò)展名》

          91. 介紹一下 js 的節(jié)流與防抖?

          相關(guān)知識點(diǎn):

          // 函數(shù)防抖: 在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時。

          // 函數(shù)節(jié)流: 規(guī)定一個單位時間,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次,只有一次能生效。

          // 函數(shù)防抖的實(shí)現(xiàn)
          function debounce(fn, wait{
            var timer = null;

            return function({
              var context = this,
                args = arguments;

              // 如果此時存在定時器的話,則取消之前的定時器重新記時
              if (timer) {
                clearTimeout(timer);
                timer = null;
              }

              // 設(shè)置定時器,使事件間隔指定事件后執(zhí)行
              timer = setTimeout(() => {
                fn.apply(context, args);
              }, wait);
            };
          }

          // 函數(shù)節(jié)流的實(shí)現(xiàn);
          function throttle(fn, delay{
            var preTime = Date.now();

            return function({
              var context = this,
                args = arguments,
                nowTime = Date.now();

              // 如果兩次時間間隔超過了指定時間,則執(zhí)行函數(shù)。
              if (nowTime - preTime >= delay) {
                preTime = Date.now();
                return fn.apply(context, args);
              }
            };
          }

          回答:

          函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時。這可以使用在一些點(diǎn)擊請求的事件上,避免因?yàn)橛脩舻亩啻吸c(diǎn)擊向后端發(fā)送多次請求。

          函數(shù)節(jié)流是指規(guī)定一個單位時間,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過事件節(jié)流來降低事件調(diào)用的頻率。

          詳細(xì)資料可以參考:《輕松理解 JS 函數(shù)節(jié)流和函數(shù)防抖》《JavaScript 事件節(jié)流和事件防抖》《JS 的防抖與節(jié)流》

          92. Object.is() 與原來的比較操作符 “===”、“==” 的區(qū)別?

          相關(guān)知識點(diǎn):

          兩等號判等,會在比較時進(jìn)行類型轉(zhuǎn)換。
          三等號判等(判斷嚴(yán)格),比較時不進(jìn)行隱式類型轉(zhuǎn)換,(類型不同則會返回false)。

          Object.is 在三等號判等的基礎(chǔ)上特別處理了 NaN 、-0 和 +0 ,保證 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 會返回 true.

          Object.is 應(yīng)被認(rèn)為有其特殊的用途,而不能用它認(rèn)為它比其它的相等對比更寬松或嚴(yán)格。

          回答:

          使用雙等號進(jìn)行相等判斷時,如果兩邊的類型不一致,則會進(jìn)行強(qiáng)制類型轉(zhuǎn)化后再進(jìn)行比較。

          使用三等號進(jìn)行相等判斷時,如果兩邊的類型不一致時,不會做強(qiáng)制類型準(zhǔn)換,直接返回 false

          使用 Object.is 來進(jìn)行相等判斷時,一般情況下和三等號的判斷相同,它處理了一些特殊的情況,比如 -0 和 +0 不再相等,兩個 NaN 認(rèn)定為是相等的。

          93. escape,encodeURI,encodeURIComponent 有什么區(qū)別?

          相關(guān)知識點(diǎn):

          escape 和 encodeURI 都屬于 Percent-encoding,基本功能都是把 URI 非法字符轉(zhuǎn)化成合法字符,轉(zhuǎn)化后形式類似「%*」。
          它們的根本區(qū)別在于,escape 在處理 0xff 之外字符的時候,是直接使用字符的 unicode 在前面加上一個「%u」,而 encode URI 則是先進(jìn)行 UTF-8,再在 UTF-8 的每個字節(jié)碼前加上一個「%」;在處理 0xff 以內(nèi)字符時,編碼方式是一樣的(都是「%XX」,XX 為字符的 16 進(jìn)制 unicode,同時也是字符的 UTF-8),只是范圍(即哪些字符編碼哪些字符不編碼)不一樣。

          回答:

          encodeURI 是對整個 URI 進(jìn)行轉(zhuǎn)義,將 URI 中的非法字符轉(zhuǎn)換為合法字符,所以對于一些在 URI 中有特殊意義的字符不會進(jìn)行轉(zhuǎn)義。

          encodeURIComponent 是對 URI 的組成部分進(jìn)行轉(zhuǎn)義,所以一些特殊字符也會得到轉(zhuǎn)義。

          escape 和 encodeURI 的作用相同,不過它們對于 unicode 編碼為 0xff 之外字符的時候會有區(qū)別,escape 是直接在字符的 unicode 編碼前加上 %u,而 encodeURI 首先會將字符轉(zhuǎn)換為 UTF-8 的格式,再在每個字節(jié)前加上 %。

          詳細(xì)資料可以參考:《escape,encodeURI,encodeURIComponent 有什么區(qū)別?》

          94. Unicode 和 UTF-8 之間的關(guān)系?

          Unicode 是一種字符集合,現(xiàn)在可容納 100 多萬個字符。每個字符對應(yīng)一個不同的 Unicode 編碼,它只規(guī)定了符號的二進(jìn)制代碼,卻沒有規(guī)定這個二進(jìn)制代碼在計(jì)算機(jī)中如何編碼傳輸。

          UTF-8 是一種對 Unicode 的編碼方式,它是一種變長的編碼方式,可以用 1~4 個字節(jié)來表示一個字符。

          詳細(xì)資料可以參考:《字符編碼詳解》《字符編碼筆記:ASCII,Unicode 和 UTF-8》

          95. js 的事件循環(huán)是什么?

          相關(guān)知識點(diǎn):

          事件隊(duì)列是一個存儲著待執(zhí)行任務(wù)的隊(duì)列,其中的任務(wù)嚴(yán)格按照時間先后順序執(zhí)行,排在隊(duì)頭的任務(wù)將會率先執(zhí)行,而排在隊(duì)尾的任務(wù)會最后執(zhí)行。事件隊(duì)列每次僅執(zhí)行一個任務(wù),在該任務(wù)執(zhí)行完畢之后,再執(zhí)行下一個任務(wù)。執(zhí)行棧則是一個類似于函數(shù)調(diào)用棧的運(yùn)行容器,當(dāng)執(zhí)行棧為空時,JS 引擎便檢查事件隊(duì)列,如果不為空的話,事件隊(duì)列便將第一個任務(wù)壓入執(zhí)行棧中運(yùn)行。

          回答:

          因?yàn)?js 是單線程運(yùn)行的,在代碼執(zhí)行的時候,通過將不同函數(shù)的執(zhí)行上下文壓入執(zhí)行棧中來保證代碼的有序執(zhí)行。在執(zhí)行同步代碼的時候,如果遇到了異步事件,js 引擎并不會一直等待其返回結(jié)果,而是會將這個事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。當(dāng)異步事件執(zhí)行完畢后,再將異步事件對應(yīng)的回調(diào)加入到與當(dāng)前執(zhí)行棧中不同的另一個任務(wù)隊(duì)列中等待執(zhí)行。任務(wù)隊(duì)列可以分為宏任務(wù)對列和微任務(wù)對列,當(dāng)當(dāng)前執(zhí)行棧中的事件執(zhí)行完畢后,js 引擎首先會判斷微任務(wù)對列中是否有任務(wù)可以執(zhí)行,如果有就將微任務(wù)隊(duì)首的事件壓入棧中執(zhí)行。當(dāng)微任務(wù)對列中的任務(wù)都執(zhí)行完成后再去判斷宏任務(wù)對列中的任務(wù)。

          微任務(wù)包括了 promise 的回調(diào)、node 中的 process.nextTick 、對 Dom 變化監(jiān)聽的 MutationObserver。

          宏任務(wù)包括了 script 腳本的執(zhí)行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操作、UI 渲
          染等。

          詳細(xì)資料可以參考:《瀏覽器事件循環(huán)機(jī)制(event loop)》《詳解 JavaScript 中的 Event Loop(事件循環(huán))機(jī)制》《什么是 Event Loop?》《這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制》

          96. js 中的深淺拷貝實(shí)現(xiàn)?

          相關(guān)資料:

          // 淺拷貝的實(shí)現(xiàn);

          function shallowCopy(object{
            // 只拷貝對象
            if (!object || typeof object !== "object"return;

            // 根據(jù) object 的類型判斷是新建一個數(shù)組還是對象
            let newObject = Array.isArray(object) ? [] : {};

            // 遍歷 object,并且判斷是 object 的屬性才拷貝
            for (let key in object) {
              if (object.hasOwnProperty(key)) {
                newObject[key] = object[key];
              }
            }

            return newObject;
          }

          // 深拷貝的實(shí)現(xiàn);

          function deepCopy(object{
            if (!object || typeof object !== "object"return;

            let newObject = Array.isArray(object) ? [] : {};

            for (let key in object) {
              if (object.hasOwnProperty(key)) {
                newObject[key] =
                  typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
              }
            }

            return newObject;
          }

          回答:

          淺拷貝指的是將一個對象的屬性值復(fù)制到另一個對象,如果有的屬性的值為引用類型的話,那么會將這個引用的地址復(fù)制給對象,因此兩個對象會有同一個引用類型的引用。淺拷貝可以使用  Object.assign 和展開運(yùn)算符來實(shí)現(xiàn)。

          深拷貝相對淺拷貝而言,如果遇到屬性值為引用類型的時候,它新建一個引用類型并將對應(yīng)的值復(fù)制給它,因此對象獲得的一個新的引用類型而不是一個原有類型的引用。深拷貝對于一些對象可以使用 JSON 的兩個函數(shù)來實(shí)現(xiàn),但是由于 JSON 的對象格式比 js 的對象格式更加嚴(yán)格,所以如果屬性值里邊出現(xiàn)函數(shù)或者 Symbol 類型的值時,會轉(zhuǎn)換失敗。

          詳細(xì)資料可以參考:《JavaScript 專題之深淺拷貝》《前端面試之道》

          97. 手寫 call、apply 及 bind 函數(shù)

          相關(guān)資料:

          // call函數(shù)實(shí)現(xiàn)
          Function.prototype.myCall = function(context{
            // 判斷調(diào)用對象
            if (typeof this !== "function") {
              console.error("type error");
            }

            // 獲取參數(shù)
            let args = [...arguments].slice(1),
              result = null;

            // 判斷 context 是否傳入,如果未傳入則設(shè)置為 window
            context = context || window;

            // 將調(diào)用函數(shù)設(shè)為對象的方法
            context.fn = this;

            // 調(diào)用函數(shù)
            result = context.fn(...args);

            // 將屬性刪除
            delete context.fn;

            return result;
          };

          // apply 函數(shù)實(shí)現(xiàn)

          Function.prototype.myApply = function(context{
            // 判斷調(diào)用對象是否為函數(shù)
            if (typeof this !== "function") {
              throw new TypeError("Error");
            }

            let result = null;

            // 判斷 context 是否存在,如果未傳入則為 window
            context = context || window;

            // 將函數(shù)設(shè)為對象的方法
            context.fn = this;

            // 調(diào)用方法
            if (arguments[1]) {
              result = context.fn(...arguments[1]);
            } else {
              result = context.fn();
            }

            // 將屬性刪除
            delete context.fn;

            return result;
          };

          // bind 函數(shù)實(shí)現(xiàn)
          Function.prototype.myBind = function(context{
            // 判斷調(diào)用對象是否為函數(shù)
            if (typeof this !== "function") {
              throw new TypeError("Error");
            }

            // 獲取參數(shù)
            var args = [...arguments].slice(1),
              fn = this;

            return function Fn({
              // 根據(jù)調(diào)用方式,傳入不同綁定值
              return fn.apply(
                this instanceof Fn ? this : context,
                args.concat(...arguments)
              );
            };
          };

          回答:

          call 函數(shù)的實(shí)現(xiàn)步驟:

          • 1.判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。
          • 2.判斷傳入上下文對象是否存在,如果不存在,則設(shè)置為 window 。
          • 3.處理傳入的參數(shù),截取第一個參數(shù)后的所有參數(shù)。
          • 4.將函數(shù)作為上下文對象的一個屬性。
          • 5.使用上下文對象來調(diào)用這個方法,并保存返回結(jié)果。
          • 6.刪除剛才新增的屬性。
          • 7.返回結(jié)果。

          apply 函數(shù)的實(shí)現(xiàn)步驟:

          • 1.判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。
          • 2.判斷傳入上下文對象是否存在,如果不存在,則設(shè)置為 window 。
          • 3.將函數(shù)作為上下文對象的一個屬性。
          • 4.判斷參數(shù)值是否傳入
          • 4.使用上下文對象來調(diào)用這個方法,并保存返回結(jié)果。
          • 5.刪除剛才新增的屬性
          • 6.返回結(jié)果

          bind 函數(shù)的實(shí)現(xiàn)步驟:

          • 1.判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。
          • 2.保存當(dāng)前函數(shù)的引用,獲取其余傳入?yún)?shù)值。
          • 3.創(chuàng)建一個函數(shù)返回
          • 4.函數(shù)內(nèi)部使用 apply 來綁定函數(shù)調(diào)用,需要判斷函數(shù)作為構(gòu)造函數(shù)的情況,這個時候需要傳入當(dāng)前函數(shù)的 this 給 apply 調(diào)用,其余情況都傳入指定的上下文對象。

          詳細(xì)資料可以參考:《手寫 call、apply 及 bind 函數(shù)》《JavaScript 深入之 call 和 apply 的模擬實(shí)現(xiàn)》

          98. 函數(shù)柯里化的實(shí)現(xiàn)

          // 函數(shù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。

          function curry(fn, args{
            // 獲取函數(shù)需要的參數(shù)長度
            let length = fn.length;

            args = args || [];

            return function({
              let subArgs = args.slice(0);

              // 拼接得到現(xiàn)有的所有參數(shù)
              for (let i = 0; i < arguments.length; i++) {
                subArgs.push(arguments[i]);
              }

              // 判斷參數(shù)的長度是否已經(jīng)滿足函數(shù)所需參數(shù)的長度
              if (subArgs.length >= length) {
                // 如果滿足,執(zhí)行函數(shù)
                return fn.apply(this, subArgs);
              } else {
                // 如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入
                return curry.call(this, fn, subArgs);
              }
            };
          }

          // es6 實(shí)現(xiàn)
          function curry(fn, ...args{
            return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
          }

          詳細(xì)資料可以參考:《JavaScript 專題之函數(shù)柯里化》

          99. 為什么 0.1 + 0.2 != 0.3?如何解決這個問題?

          當(dāng)計(jì)算機(jī)計(jì)算 0.1+0.2 的時候,實(shí)際上計(jì)算的是這兩個數(shù)字在計(jì)算機(jī)里所存儲的二進(jìn)制,0.1 和 0.2 在轉(zhuǎn)換為二進(jìn)制表示的時候會出現(xiàn)位數(shù)無限循環(huán)的情況。js 中是以 64 位雙精度格式來存儲數(shù)字的,只有 53 位的有效數(shù)字,超過這個長度的位數(shù)會被截取掉這樣就造成了精度丟失的問題。這是第一個會造成精度丟失的地方。在對兩個以 64 位雙精度格式的數(shù)據(jù)進(jìn)行計(jì)算的時候,首先會進(jìn)行對階的處理,對階指的是將階碼對齊,也就是將小數(shù)點(diǎn)的位置對齊后,再進(jìn)行計(jì)算,一般是小階向大階對齊,因此小階的數(shù)在對齊的過程中,有效數(shù)字會向右移動,移動后超過有效位數(shù)的位會被截取掉,這是第二個可能會出現(xiàn)精度丟失的地方。當(dāng)兩個數(shù)據(jù)階碼對齊后,進(jìn)行相加運(yùn)算后,得到的結(jié)果可能會超過 53 位有效數(shù)字,因此超過的位數(shù)也會被截取掉,這是可能發(fā)生精度丟失的第三個地方。

          對于這樣的情況,我們可以將其轉(zhuǎn)換為整數(shù)后再進(jìn)行運(yùn)算,運(yùn)算后再轉(zhuǎn)換為對應(yīng)的小數(shù),以這種方式來解決這個問題。

          我們還可以將兩個數(shù)相加的結(jié)果和右邊相減,如果相減的結(jié)果小于一個極小數(shù),那么我們就可以認(rèn)定結(jié)果是相等的,這個極小數(shù)可以
          使用 es6 的 Number.EPSILON

          詳細(xì)資料可以參考:《十進(jìn)制的 0.1 為什么不能用二進(jìn)制很好的表示?》《十進(jìn)制浮點(diǎn)數(shù)轉(zhuǎn)成二進(jìn)制》《浮點(diǎn)數(shù)的二進(jìn)制表示》《js 浮點(diǎn)數(shù)存儲精度丟失原理》《浮點(diǎn)數(shù)精度之謎》《JavaScript 浮點(diǎn)數(shù)陷阱及解法》《0.1+0.2 !== 0.3?》《JavaScript 中奇特的~運(yùn)算符》

          100. 原碼、反碼和補(bǔ)碼的介紹

          原碼是計(jì)算機(jī)中對數(shù)字的二進(jìn)制的定點(diǎn)表示方法,最高位表示符號位,其余位表示數(shù)值位。優(yōu)點(diǎn)是易于分辨,缺點(diǎn)是不能夠直接參與運(yùn)算。

          正數(shù)的反碼和其原碼一樣;負(fù)數(shù)的反碼,符號位為1,數(shù)值部分按原碼取反。
          如 [+7]原 = 00000111,[+7]反 = 00000111; [-7]原 = 10000111,[-7]反 = 11111000。

          正數(shù)的補(bǔ)碼和其原碼一樣;負(fù)數(shù)的補(bǔ)碼為其反碼加1。

          例如 [+7]原 = 00000111,[+7]反 = 00000111,[+7]補(bǔ) = 00000111;
          [-7]原 = 10000111,[-7]反 = 11111000,[-7]補(bǔ) = 11111001

          之所以在計(jì)算機(jī)中使用補(bǔ)碼來表示負(fù)數(shù)的原因是,這樣可以將加法運(yùn)算擴(kuò)展到所有的數(shù)值計(jì)算上,因此在數(shù)字電路中我們只需要考慮加法器的設(shè)計(jì)就行了,而不用再為減法設(shè)置新的數(shù)字電路。

          詳細(xì)資料可以參考:《關(guān)于 2 的補(bǔ)碼》

          101. toPrecision 和 toFixed 和 Math.round 的區(qū)別?

          toPrecision 用于處理精度,精度是從左至右第一個不為 0 的數(shù)開始數(shù)起。
          toFixed 是對小數(shù)點(diǎn)后指定位數(shù)取整,從小數(shù)點(diǎn)開始數(shù)起。
          Math.round 是將一個數(shù)字四舍五入到一個整數(shù)。

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

          XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者通過在網(wǎng)站注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行,從而盜取用戶的信息如 cookie 等。

          XSS 的本質(zhì)是因?yàn)榫W(wǎng)站沒有對惡意代碼進(jìn)行過濾,與正常的代碼混合在一起了,瀏覽器沒有辦法分辨哪些腳本是可信的,從而導(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ù)都進(jìn)行的轉(zhuǎn)義處理,但是一個數(shù)據(jù)可能在多個地方使用,有的地方可能不需要轉(zhuǎn)義,由于我們沒有辦法判斷數(shù)據(jù)最后的使用場景,所以直接在輸入端進(jìn)行惡意代碼的處理,其實(shí)是不太可靠的。

          因此我們可以從瀏覽器的執(zhí)行來進(jìn)行預(yù)防,一種是使用純前端的方式,不用服務(wù)器端拼接后返回。另一種是對需要插入到 HTML 中的代碼做好充分的轉(zhuǎn)義。對于 DOM 型的攻擊,主要是前端腳本的不可靠而造成的,我們對于數(shù)據(jù)獲取渲染和字符串拼接的時候應(yīng)該對可能出現(xiàn)的惡意代碼情況進(jìn)行判斷。

          還有一些方式,比如使用 CSP ,CSP 的本質(zhì)是建立一個白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行,從而防止惡意代碼的注入攻擊。

          還可以對一些敏感信息進(jìn)行保護(hù),比如 cookie 使用 http-only ,使得腳本無法獲取。也可以使用驗(yàn)證碼,避免腳本偽裝成用戶執(zhí)行一些操作。

          詳細(xì)資料可以參考:《前端安全系列(一):如何防止 XSS 攻擊?》

          103. 什么是 CSP?

          CSP 指的是內(nèi)容安全策略,它的本質(zhì)是建立一個白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行。我們只需要配置規(guī)則,如何攔截由瀏覽器自己來實(shí)現(xiàn)。

          通常有兩種方式來開啟 CSP,一種是設(shè)置 HTTP 首部中的 Content-Security-Policy,一種是設(shè)置 meta 標(biāo)簽的方式 <meta
          http-equiv="Content-Security-Policy">

          詳細(xì)資料可以參考:《內(nèi)容安全策略(CSP)》《前端面試之道》

          104. 什么是 CSRF 攻擊?如何防范 CSRF 攻擊?

          CSRF 攻擊指的是跨站請求偽造攻擊,攻擊者誘導(dǎo)用戶進(jìn)入一個第三方網(wǎng)站,然后該網(wǎng)站向被攻擊網(wǎng)站發(fā)送跨站請求。如果用戶在被
          攻擊網(wǎng)站中保存了登錄狀態(tài),那么攻擊者就可以利用這個登錄狀態(tài),繞過后臺的用戶驗(yàn)證,冒充用戶向服務(wù)器執(zhí)行一些操作。

          CSRF 攻擊的本質(zhì)是利用了 cookie 會在同源請求中攜帶發(fā)送給服務(wù)器的特點(diǎn),以此來實(shí)現(xiàn)用戶的冒充。

          一般的 CSRF 攻擊類型有三種:

          第一種是 GET 類型的 CSRF 攻擊,比如在網(wǎng)站中的一個 img 標(biāo)簽里構(gòu)建一個請求,當(dāng)用戶打開這個網(wǎng)站的時候就會自動發(fā)起提
          交。

          第二種是 POST 類型的 CSRF 攻擊,比如說構(gòu)建一個表單,然后隱藏它,當(dāng)用戶進(jìn)入頁面時,自動提交這個表單。

          第三種是鏈接類型的 CSRF 攻擊,比如說在 a 標(biāo)簽的 href 屬性里構(gòu)建一個請求,然后誘導(dǎo)用戶去點(diǎn)擊。

          CSRF 可以用下面幾種方法來防護(hù):

          第一種是同源檢測的方法,服務(wù)器根據(jù) http 請求頭中 origin 或者 referer 信息來判斷請求是否為允許訪問的站點(diǎn),從而對請求進(jìn)行過濾。當(dāng) origin 或者 referer 信息都不存在的時候,直接阻止。這種方式的缺點(diǎn)是有些情況下 referer 可以被偽造。還有就是我們這種方法同時把搜索引擎的鏈接也給屏蔽了,所以一般網(wǎng)站會允許搜索引擎的頁面請求,但是相應(yīng)的頁面請求這種請求方式也可能被攻擊者給利用。

          第二種方法是使用 CSRF Token 來進(jìn)行驗(yàn)證,服務(wù)器向用戶返回一個隨機(jī)數(shù) Token ,當(dāng)網(wǎng)站再次發(fā)起請求時,在請求參數(shù)中加入服務(wù)器端返回的 token ,然后服務(wù)器對這個 token 進(jìn)行驗(yàn)證。這種方法解決了使用 cookie 單一驗(yàn)證方式時,可能會被冒用的問題,但是這種方法存在一個缺點(diǎn)就是,我們需要給網(wǎng)站中的所有請求都添加上這個 token,操作比較繁瑣。還有一個問題是一般不會只有一臺網(wǎng)站服務(wù)器,如果我們的請求經(jīng)過負(fù)載平衡轉(zhuǎn)移到了其他的服務(wù)器,但是這個服務(wù)器的 session 中沒有保留這個 token 的話,就沒有辦法驗(yàn)證了。這種情況我們可以通過改變 token 的構(gòu)建方式來解決。

          第三種方式使用雙重 Cookie 驗(yàn)證的辦法,服務(wù)器在用戶訪問網(wǎng)站頁面時,向請求域名注入一個Cookie,內(nèi)容為隨機(jī)字符串,然后當(dāng)用戶再次向服務(wù)器發(fā)送請求的時候,從 cookie 中取出這個字符串,添加到 URL 參數(shù)中,然后服務(wù)器通過對 cookie 中的數(shù)據(jù)和參數(shù)中的數(shù)據(jù)進(jìn)行比較,來進(jìn)行驗(yàn)證。使用這種方式是利用了攻擊者只能利用 cookie,但是不能訪問獲取 cookie 的特點(diǎn)。并且這種方法比 CSRF Token 的方法更加方便,并且不涉及到分布式訪問的問題。這種方法的缺點(diǎn)是如果網(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。

          使用這種方法的缺點(diǎn)是,因?yàn)樗恢С肿佑颍宰佑驔]有辦法與主域共享登錄信息,每次轉(zhuǎn)入子域的網(wǎng)站,都回重新登錄。還有一個問題就是它的兼容性不夠好。

          106. 什么是點(diǎn)擊劫持?如何防范點(diǎn)擊劫持?

          點(diǎn)擊劫持是一種視覺欺騙的攻擊手段,攻擊者將需要攻擊的網(wǎng)站通過 iframe 嵌套的方式嵌入自己的網(wǎng)頁中,并將 iframe 設(shè)置為透明,在頁面中透出一個按鈕誘導(dǎo)用戶點(diǎn)擊。

          我們可以在 http 相應(yīng)頭中設(shè)置 X-FRAME-OPTIONS 來防御用 iframe 嵌套的點(diǎn)擊劫持攻擊。通過不同的值,可以規(guī)定頁面在特
          定的一些情況才能作為 iframe 來使用。

          詳細(xì)資料可以參考:《web 安全之--點(diǎn)擊劫持攻擊與防御技術(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è)計(jì)模式,主要通過分離關(guān)注點(diǎn)的方式來組織代碼結(jié)構(gòu),優(yōu)化我們的開發(fā)效率。

          比如說我們實(shí)驗(yàn)室在以前項(xiàng)目開發(fā)的時候,使用單頁應(yīng)用時,往往一個路由頁面對應(yīng)了一個腳本文件,所有的頁面邏輯都在一個腳本文件里。頁面的渲染、數(shù)據(jù)的獲取,對用戶事件的響應(yīng)所有的應(yīng)用邏輯都混合在一起,這樣在開發(fā)簡單項(xiàng)目時,可能看不出什么問題,當(dāng)時一旦項(xiàng)目變得復(fù)雜,那么整個文件就會變得冗長,混亂,這樣對我們的項(xiàng)目開發(fā)和后期的項(xiàng)目維護(hù)是非常不利的。

          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)生交互的時候,Co
          ntroller 中的事件觸發(fā)器就開始工作了,通過調(diào)用 Model 層,來完成對 Model 的修改,然后 Model 層再去通知 View 層更新。

          MVP 模式與 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中我們使用觀察者模式,來實(shí)現(xiàn)當(dāng) Model 層數(shù)據(jù)發(fā)生變化的時候,通知 View 層的更新。這樣 View 層和 Model 層耦合在一起,當(dāng)項(xiàng)目邏輯變得復(fù)雜的時候,可能會造成代碼的混亂,并且可能會對代碼的復(fù)用性造成一些問題。MVP 的模式通過使用 Presenter 來實(shí)現(xiàn)對 View 層和 Model 層的解耦。MVC 中的
          Controller 只知道 Model 的接口,因此它沒有辦法控制 View 層的更新,MVP 模式中,View 層的接口暴露給了 Presenter 因此我們可以在 Presenter 中將 Model 的變化和 View 的變化綁定在一起,以此來實(shí)現(xiàn) View 和 Model 的同步更新。這樣就實(shí)現(xiàn)了對 View 和 Model 的解耦,Presenter 還包含了其他的響應(yīng)邏輯。

          MVVM 模式中的 VM,指的是 ViewModel,它和 MVP 的思想其實(shí)是相同的,不過它通過雙向的數(shù)據(jù)綁定,將 View 和 Model 的同步更新給自動化了。當(dāng) Model 發(fā)生變化的時候,ViewModel 就會自動更新;ViewModel 變化了,View 也會更新。這樣就將 Presenter 中的工作給自動化了。我了解過一點(diǎn)雙向數(shù)據(jù)綁定的原理,比如 vue 是通過使用數(shù)據(jù)劫持和發(fā)布訂閱者模式來實(shí)現(xiàn)的這一功
          能。

          詳細(xì)資料可以參考:《淺析前端開發(fā)中的 MVC/MVP/MVVM 模式》《MVC,MVP 和 MVVM 的圖示》《MVVM》《一篇文章了解架構(gòu)模式:MVC/MVP/MVVM》

          109. vue 雙向數(shù)據(jù)綁定原理?

          vue 通過使用雙向數(shù)據(jù)綁定,來實(shí)現(xiàn)了 View 和 Model 的同步更新。vue 的雙向數(shù)據(jù)綁定主要是通過使用數(shù)據(jù)劫持和發(fā)布訂閱者模式來實(shí)現(xiàn)的。

          首先我們通過 Object.defineProperty() 方法來對 Model 數(shù)據(jù)各個屬性添加訪問器屬性,以此來實(shí)現(xiàn)數(shù)據(jù)的劫持,因此當(dāng) Model 中的數(shù)據(jù)發(fā)生變化的時候,我們可以通過配置的 setter 和 getter 方法來實(shí)現(xiàn)對 View 層數(shù)據(jù)更新的通知。

          數(shù)據(jù)在 html 模板中一共有兩種綁定情況,一種是使用 v-model 來對 value 值進(jìn)行綁定,一種是作為文本綁定,在對模板引擎進(jìn)行解析的過程中。

          如果遇到元素節(jié)點(diǎn),并且屬性值包含 v-model 的話,我們就從 Model 中去獲取 v-model 所對應(yīng)的屬性的值,并賦值給元素的 value 值。然后給這個元素設(shè)置一個監(jiān)聽事件,當(dāng) View 中元素的數(shù)據(jù)發(fā)生變化的時候觸發(fā)該事件,通知 Model 中的對應(yīng)的屬性的值進(jìn)行更新。

          如果遇到了綁定的文本節(jié)點(diǎn),我們使用 Model 中對應(yīng)的屬性的值來替換這個文本。對于文本節(jié)點(diǎn)的更新,我們使用了發(fā)布訂閱者模式,屬性作為一個主題,我們?yōu)檫@個節(jié)點(diǎn)設(shè)置一個訂閱者對象,將這個訂閱者對象加入這個屬性主題的訂閱者列表中。當(dāng) Model 層數(shù)據(jù)發(fā)生改變的時候,Model 作為發(fā)布者向主題發(fā)出通知,主題收到通知再向它的所有訂閱者推送,訂閱者收到通知后更改自己的數(shù)
          據(jù)。

          詳細(xì)資料可以參考:《Vue.js 雙向綁定的實(shí)現(xiàn)原理》

          110. Object.defineProperty 介紹?

          Object.defineProperty 函數(shù)一共有三個參數(shù),第一個參數(shù)是需要定義屬性的對象,第二個參數(shù)是需要定義的屬性,第三個是該屬性描述符。

          一個屬性的描述符有四個屬性,分別是 value 屬性的值,writable 屬性是否可寫,enumerable 屬性是否可枚舉,configurable 屬性是否可配置修改。

          詳細(xì)資料可以參考:《Object.defineProperty()》

          111. 使用 Object.defineProperty() 來進(jìn)行數(shù)據(jù)劫持有什么缺點(diǎn)?

          有一些對屬性的操作,使用這種方法無法攔截,比如說通過下標(biāo)方式修改數(shù)組數(shù)據(jù)或者給對象新增屬性,vue 內(nèi)部通過重寫函數(shù)解決了這個問題。在 Vue3.0 中已經(jīng)不使用這種方式了,而是通過使用 Proxy 對對象進(jìn)行代理,從而實(shí)現(xiàn)數(shù)據(jù)劫持。使用 Proxy 的好處是它可以完美的監(jiān)聽到任何方式的數(shù)據(jù)改變,唯一的缺點(diǎn)是兼容性的問題,因?yàn)檫@是 ES6 的語法。

          112. 什么是 Virtual DOM?為什么 Virtual DOM 比原生 DOM 快?

          我對 Virtual DOM 的理解是,

          首先對我們將要插入到文檔中的 DOM 樹結(jié)構(gòu)進(jìn)行分析,使用 js 對象將其表示出來,比如一個元素對象,包含 TagName、props 和 Children 這些屬性。然后我們將這個 js 對象樹給保存下來,最后再將 DOM 片段插入到文檔中。

          當(dāng)頁面的狀態(tài)發(fā)生改變,我們需要對頁面的 DOM 的結(jié)構(gòu)進(jìn)行調(diào)整的時候,我們首先根據(jù)變更的狀態(tài),重新構(gòu)建起一棵對象樹,然后將這棵新的對象樹和舊的對象樹進(jìn)行比較,記錄下兩棵樹的的差異。

          最后將記錄的有差異的地方應(yīng)用到真正的 DOM 樹中去,這樣視圖就更新了。

          我認(rèn)為 Virtual DOM 這種方法對于我們需要有大量的 DOM 操作的時候,能夠很好的提高我們的操作效率,通過在操作前確定需要做的最小修改,盡可能的減少 DOM 操作帶來的重流和重繪的影響。其實(shí) Virtual DOM 并不一定比我們真實(shí)的操作 DOM 要快,這種方法的目的是為了提高我們開發(fā)時的可維護(hù)性,在任意的情況下,都能保證一個盡量小的性能消耗去進(jìn)行操作。

          詳細(xì)資料可以參考:《Virtual DOM》《理解 Virtual DOM》《深度剖析:如何實(shí)現(xiàn)一個 Virtual DOM 算法》《網(wǎng)上都說操作真實(shí) DOM 慢,但測試結(jié)果卻比 React 更快,為什么?》

          113. 如何比較兩個 DOM 樹的差異?

          兩個樹的完全 diff 算法的時間復(fù)雜度為 O(n^3) ,但是在前端中,我們很少會跨層級的移動元素,所以我們只需要比較同一層級的元素進(jìn)行比較,這樣就可以將算法的時間復(fù)雜度降低為 O(n)。

          算法首先會對新舊兩棵樹進(jìn)行一個深度優(yōu)先的遍歷,這樣每個節(jié)點(diǎn)都會有一個序號。在深度遍歷的時候,每遍歷到一個節(jié)點(diǎn),我們就將這個節(jié)點(diǎn)和新的樹中的節(jié)點(diǎn)進(jìn)行比較,如果有差異,則將這個差異記錄到一個對象中。

          在對列表元素進(jìn)行對比的時候,由于 TagName 是重復(fù)的,所以我們不能使用這個來對比。我們需要給每一個子節(jié)點(diǎn)加上一個 key,列表對比的時候使用 key 來進(jìn)行比較,這樣我們才能夠復(fù)用老的 DOM 樹上的節(jié)點(diǎn)。

          114. 什么是 requestAnimationFrame ?

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

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

          我當(dāng)時使用 webpack 的一個最主要原因是為了簡化頁面依賴的管理,并且通過將其打包為一個文件來降低頁面加載時請求的資源
          數(shù)。

          我認(rèn)為 webpack 的主要原理是,它將所有的資源都看成是一個模塊,并且把頁面邏輯當(dāng)作一個整體,通過一個給定的入口文件,webpack 從這個文件開始,找到所有的依賴文件,將各個依賴文件模塊通過 loader 和 plugins 處理后,然后打包在一起,最后輸出一個瀏覽器可識別的 JS 文件。

          Webpack 具有四個核心的概念,分別是 Entry(入口)、Output(輸出)、loader 和 Plugins(插件)。

          Entry 是 webpack 的入口起點(diǎn),它指示 webpack 應(yīng)該從哪個模塊開始著手,來作為其構(gòu)建內(nèi)部依賴圖的開始。

          Output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的打包文件,也可指定打包文件的名稱,默認(rèn)位置為 ./dist。

          loader 可以理解為 webpack 的編譯器,它使得 webpack 可以處理一些非 JavaScript 文件。在對 loader 進(jìn)行配置的時候,test 屬性,標(biāo)志有哪些后綴的文件應(yīng)該被處理,是一個正則表達(dá)式。use 屬性,指定 test 類型的文件應(yīng)該使用哪個 loader 進(jìn)行預(yù)處理。常用的 loader 有 css-loader、style-loader 等。

          插件可以用于執(zhí)行范圍更廣的任務(wù),包括打包、優(yōu)化、壓縮、搭建服務(wù)器等等,要使用一個插件,一般是先使用 npm 包管理器進(jìn)行安裝,然后在配置文件中引入,最后將其實(shí)例化后傳遞給 plugins 數(shù)組屬性。

          使用 webpack 的確能夠提供我們對于項(xiàng)目的管理,但是它的缺點(diǎn)就是調(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)容垂直滾動的像素?cái)?shù)。
          scrollLeft 屬性返回的是元素滾動條到元素左邊的距離。

          詳細(xì)資料可以參考:《最全的獲取元素寬高及位置的方法》《用 Javascript 獲取頁面元素的位置》

          117. 談一談你理解的函數(shù)式編程?

          簡單說,"函數(shù)式編程"是一種"編程范式"(programming paradigm),也就是如何編寫程序的方法論。

          它具有以下特性:閉包和高階函數(shù)、惰性計(jì)算、遞歸、函數(shù)是"第一等公民"、只用"表達(dá)式"。

          詳細(xì)資料可以參考:《函數(shù)式編程初探》

          118. 異步編程的實(shí)現(xiàn)方式?

          相關(guān)資料:

          回調(diào)函數(shù)
          優(yōu)點(diǎn):簡單、容易理解
          缺點(diǎn):不利于維護(hù),代碼耦合高

          事件監(jiān)聽(采用時間驅(qū)動模式,取決于某個事件是否發(fā)生):
          優(yōu)點(diǎn):容易理解,可以綁定多個事件,每個事件可以指定多個回調(diào)函數(shù)
          缺點(diǎn):事件驅(qū)動型,流程不夠清晰

          發(fā)布/訂閱(觀察者模式)
          類似于事件監(jiān)聽,但是可以通過‘消息中心’,了解現(xiàn)在有多少發(fā)布者,多少訂閱者

          Promise 對象
          優(yōu)點(diǎn):可以利用 then 方法,進(jìn)行鏈?zhǔn)綄懛?;可以書寫錯誤時的回調(diào)函數(shù);
          缺點(diǎn):編寫和理解,相對比較難

          Generator 函數(shù)
          優(yōu)點(diǎn):函數(shù)體內(nèi)外的數(shù)據(jù)交換、錯誤處理機(jī)制
          缺點(diǎn):流程管理不方便

          async 函數(shù)
          優(yōu)點(diǎn):內(nèi)置執(zhí)行器、更好的語義、更廣的適用性、返回的是 Promise、結(jié)構(gòu)清晰。
          缺點(diǎn):錯誤處理機(jī)制

          回答:

          js 中的異步機(jī)制可以分為以下幾種:

          第一種最常見的是使用回調(diào)函數(shù)的方式,使用回調(diào)函數(shù)的方式有一個缺點(diǎn)是,多個回調(diào)函數(shù)嵌套的時候會造成回調(diào)函數(shù)地獄,上下兩層的回調(diào)函數(shù)間的代碼耦合度太高,不利于代碼的可維護(hù)。

          第二種是 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 的機(jī)制,比如說 co 模塊等方式來實(shí)現(xiàn) generator 的自動執(zhí)行。

          第四種是使用 async 函數(shù)的形式,async 函數(shù)是 generator 和 promise 實(shí)現(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)實(shí)現(xiàn)

          CSS3 的動畫的優(yōu)點(diǎn)

          在性能上會稍微好一些,瀏覽器會對 CSS3 的動畫做一些優(yōu)化
          代碼相對簡單

          缺點(diǎn)

          在動畫控制上不夠靈活
          兼容性不好

          JavaScript 的動畫正好彌補(bǔ)了這兩個缺點(diǎn),控制能力很強(qiáng),可以單幀的控制、變換,同時寫得好完全可以兼容 IE6,并且功能強(qiáng)大。對于一些復(fù)雜控制的動畫,使用 javascript 會比較靠譜。而在實(shí)現(xiàn)一些小的交互動效的時候,就多考慮考慮 CSS 吧

          120. get 請求傳參長度的誤區(qū)

          誤區(qū):我們經(jīng)常說 get 請求參數(shù)的大小存在限制,而 post 請求的參數(shù)大小是無限制的。

          實(shí)際上 HTTP 協(xié)議從未規(guī)定 GET/POST 的請求長度限制是多少。對 get 請求參數(shù)的限制是來源與瀏覽器或web 服務(wù)器,瀏覽器或 web 服務(wù)器限制了 url 的長度。為了明確這個概念,我們必須再次強(qiáng)調(diào)下面幾點(diǎn):
          • 1.HTTP 協(xié)議未規(guī)定 GET 和 POST 的長度限制
          • 2.GET 的最大長度顯示是因?yàn)闉g覽器和 web 服務(wù)器限制了 URI 的長度
          • 3.不同的瀏覽器和 WEB 服務(wù)器,限制的最大長度不一樣
          • 4.要支持 IE,則最大長度為 2083byte,若只支持 Chrome,則最大長度 8182byte

          面試題倉庫推薦:

          • https://github.com/febobo/web-interview


          推薦閱讀:
          • 174道JavaScript 面試知識點(diǎn)總結(jié)(上)

          • JS語法 ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性

          • 19個JavaScript數(shù)組常用方法總結(jié)

          • JavaScript 代碼整潔之道

          • 面試官問 Vue 性能優(yōu)化,我該怎么回答

          • 深入淺出前端本地儲存

          關(guān)注數(shù):10億+ 文章數(shù):10億+
          粉絲量:10億+ 點(diǎn)擊量:10億+

           


          微信群管理員請掃描這里

          微信群管理員請掃描這里

          喜歡本文的朋友,歡迎關(guān)注公眾號 程序員哆啦A夢,收看更多精彩內(nèi)容

          點(diǎn)個[在看],是對小達(dá)最大的支持!


          如果覺得這篇文章還不錯,來個【分享、點(diǎn)贊、在看】三連吧,讓更多的人也看


          瀏覽 77
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  久久国产免费视频 | 免费在线成人网 | 高潮13p | 国产AV激情 | 国产高清视频色 |