<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>

          核心前端體系知識點

          共 47150字,需瀏覽 95分鐘

           ·

          2021-12-24 18:29

          本文適合想對前端體系知識進行梳理的小伙伴閱讀。

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~

          一、前言

          ? ? 最近有小伙伴私聊廣東靚仔,快到年尾了,想充實下自己的前端知識體系,但是沒有個方向。我們都知道前端知識縱橫交錯,知識體系龐大,相信有不少小伙伴無從下手、囫圇吞棗。

          ? ? 廣東靚仔收集了一些比較重要的知識點,下面我們展開一起來看看。

          315ab8524af4b358678f566e80d23488.webp

          二、HTML

          1.語義化

          ? ? 所謂,語義化的標(biāo)簽,說明讓標(biāo)簽有自己的含義。也是近十年。最典型的栗子就是header,footer等,它可以讓你在沒有樣式的情況下,就大概能想到,他就是個頭部或者底部。他存在的意義,就是讓前端開發(fā)人員,在開發(fā)過程中,更容易去閱讀代碼,以及明白這些代碼的意義。

          好處

          • 能夠更好的展示內(nèi)容結(jié)構(gòu)
          • 便于團隊的維護與開發(fā)
          • 有利于SEO
          • 爬蟲可以分析每個關(guān)鍵詞的權(quán)重
          • 方便其他設(shè)備解析?(如屏幕閱讀器)

          2.SEO

          ? ? 身為前端,我們不得不知道的SEO,這涉及到公司的網(wǎng)站推廣。SEO,中文稱搜索引擎優(yōu)化,一種利用搜索引擎的搜索規(guī)則來提高目前網(wǎng)站在有關(guān)搜索引擎內(nèi)的自然排名的方式。他的實現(xiàn)原來分別為,頁面抓取,分析入庫,檢索排序。

          如何優(yōu)化SEO

          • title、description、keywords
          • 利用好html語義化
          • 重要的東西放前面
          • 少用iframe


          3.doctype

          ? ? 前端經(jīng)常在html頭部看到DOCTYPE的聲明,一般常位于文檔的第一行。那么他的作用是什么,可能對新的瀏覽器或者新的網(wǎng)站暫無什么影響,但是相對古老的瀏覽器或者是網(wǎng)站,可能會出現(xiàn)不同。因為瀏覽器有標(biāo)準(zhǔn)模式與兼容模式,差異相對比較大。標(biāo)準(zhǔn)模式的渲染方式和 JS 引擎的解析方式都是以該瀏覽器支持的最高標(biāo)準(zhǔn)運行。兼容模式中,頁面以寬松的向后兼容的方式顯示 ,模擬老式瀏覽器的行為以防止站點無法工作。而DOCTYPE的存在,就是為了聲明,該頁面使用標(biāo)準(zhǔn)模式。不聲明,可能一些舊的網(wǎng)站會出現(xiàn)兼容模式。


          4.link與@import

          link與import , 本質(zhì)使用上,我們都是用他來引入css。

          區(qū)別:

          • link是一種引入資源的標(biāo)簽,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的資源,包括圖片,RSS等。
          • 加載順序上也有一些差異。link引用的CSS會同時被加載。import引用的CSS會等到頁面全部被下載完再加載。
          • 兼容性的差別。link無任何兼容問題,import兼容IE5以上。(當(dāng)然,IE5估計也找不到了)
          • 動態(tài)引入樣式 link可以后期引入樣式,而import是不可以后期引入的,只能初始化頁面之前引入。
          • 復(fù)用率的問題 import可以復(fù)用之前的css文件,而link只能一次引用一個文件。當(dāng)然,import復(fù)用文件時,在瀏覽器實際上是加載了多個文件,會有多個請求。而每一個link只是一個http請求。

          5.async與defer

          ? ? 首先這兩個東西為什么而存在的問題。在日漸復(fù)雜的前端,異常已經(jīng)是程序的一部分。如果出現(xiàn)一些小問題,或者服務(wù)器加載上出現(xiàn)延遲。而我們默認(rèn)的引入的script腳本,會阻塞后續(xù)的DOM渲染。一旦沒有部分異常無法及時加載完成,那么我們的頁面因為阻塞問題,將整個白屏。也許我們可以保證自己服務(wù)器的正常,但是你決定保證不了第三方服務(wù)器的正常,于是引入了asyncdefer來優(yōu)化這個問題。再來談?wù)剆cript的默認(rèn),asyncdefer的之前的差異。默認(rèn)情況下:瀏覽器會立即加載并執(zhí)行指定的腳本。指定的腳本,指在script標(biāo)簽之上的腳本。所以,如果script放在header中,而對應(yīng)的文件還未加載完成,會形成阻塞。所以這就是現(xiàn)在很多頁面,都會使用默認(rèn)且把scipt放在頁面結(jié)尾的原因。async情況下:async ,加載和渲染后續(xù)文檔元素的過程將和 script.js 的加載與執(zhí)行并行進行(異步)。async是亂序的。defer情況下:defer,加載后續(xù)文檔元素的過程將和 script.js 的加載并行進行(異步),但是 script.js 的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。defer是順序執(zhí)行。此外,async跟defer,不支持或者不兼容IE9一下瀏覽器,總體來說,script放最下方靠譜一些。


          6.捕捉,冒泡與委托

          ? ? 適合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。執(zhí)行順序:捕捉--》目標(biāo)--》冒泡event.stopPropagation()阻止事件的傳遞行為. event.preventDefault();阻止默認(rèn)行為,比如阻止a的href

          優(yōu)點:

          • 減少事件注冊,節(jié)省內(nèi)存。例如上面代碼,只指定 父元素的處理程序,即可管理所有所有子元素的“click”事件;
          • 簡化了dom節(jié)點更新時,相應(yīng)事件的更新

          缺點:

          • 利用事件冒泡的原理,不支持不冒泡的事件;
          • 層級過多,冒泡過程中,可能會被某層阻止掉;
          • 理論上委托會導(dǎo)致瀏覽器頻繁調(diào)用處理函數(shù),雖然很可能不需要處理。所以建議就近委托,比如在ol上代理li,而不是在document上代理li。
          • 把所有事件都用代理就可能會出現(xiàn)事件誤判。比如,在document中代理了所有button的click事件,另外的人在引用改js時,可能不知道,造成單擊button觸發(fā)了兩個click事件。


          7.漸進增強與優(yōu)雅降級

          ? ? 漸進增強:針對低版本瀏覽器進行構(gòu)建頁面,保證最基本的功能,然后再針對高級瀏覽器進行效果、交互等改進,達到更好的用戶體驗。優(yōu)雅降級:一開始就構(gòu)建完整的功能,然后再針對低版本瀏覽器進行兼容。

          三、js函數(shù)根基

          1.函數(shù)的獨特之處? ? 函數(shù)是第一型對象,因為函數(shù)可以像對象通過字面量進行創(chuàng)建,賦值變量、數(shù)組,作為函數(shù)的返回值,擁有動態(tài)創(chuàng)建并返回賦值的屬性。? ? 函數(shù)最重要的特性是它可以被調(diào)用,而通常都是以異步的方式進行調(diào)用,其原理是瀏覽器事件輪詢是單線程的,即每個事件都是按照在隊列放放置的順序類執(zhí)行的,就是FIFO(先進先出)列表,在任何情況下單線程不可能同時執(zhí)行兩個程序,所以必須等待當(dāng)前事件結(jié)束之后才能執(zhí)行另外一個事件,就是說事件執(zhí)行的時間不可知所以事件處理函數(shù)調(diào)用異步。
          2.函數(shù)聲明

          函數(shù)是使用字面量進行聲明從而創(chuàng)建函數(shù)值的,函數(shù)字面量由四個部分組成

          1.function關(guān)鍵詞?
          2.可選名稱
          3.括號內(nèi)部,一個以逗號分隔開的參數(shù)列表
          4.函數(shù)體

          函數(shù)名是可選的,它只是函數(shù)的引用,匿名函數(shù)就是一個栗子。可以通過訪問函數(shù)的name屬性獲取函數(shù)名

          function?ninja(){}
          ninja.name?//ninja

          另外值得注意的是書上說創(chuàng)建一個匿名函數(shù)并賦值給一個變量,這個變量并不是該函數(shù)的name,但是本人發(fā)現(xiàn)在支持ES6語法的chrome下獲取函數(shù)name不是空而是匿名函數(shù)賦值給該變量的變量名。

          var?fn?=?function?()?{};
          //?ES5
          fn.name?//?""
          //?ES6
          fn.name?//?"fn"

          也可以通過屬性訪問的方式獲取形參長度,注意是形參不是實參,形參和實參的區(qū)別是:形參是函數(shù)聲明時定義的參數(shù),而實參是函數(shù)調(diào)用時傳給函數(shù)的參數(shù)。下面會講怎么獲取實參。
          var?fn?=?function?(a,b)?{};
          fn.length?//?2

          3.函數(shù)調(diào)用? ??函數(shù)被調(diào)用的時候到底發(fā)生了什么?事實上函數(shù)被調(diào)用的方式對其函數(shù)內(nèi)部代碼執(zhí)行有著巨大的影響。有四種不同的方式進行函數(shù)調(diào)用,每個方式都有細(xì)微的差別。
          • 作為一個函數(shù)調(diào)用,是簡單的形式
          • 作為一個方法調(diào)用,在對象上進行調(diào)用
          • 作為構(gòu)造器調(diào)用,創(chuàng)建一個對象
          • 通過apply()和call()方法進行調(diào)用

          有趣的是在函數(shù)調(diào)用的時候都會傳遞兩個隱式參數(shù):arguments和this,所謂隱式就是這些參數(shù)不會顯示在函數(shù)簽名里,但是它們會傳遞給函數(shù)并在函數(shù)作用域內(nèi),在函數(shù)內(nèi)可以進行訪問和使用。

          arguments

          arguments是函數(shù)調(diào)用時傳給函數(shù)的實際參數(shù)集合,可以用length屬性獲取集合的長度,但是arguments并不是真正的數(shù)組。

          function?fn?(a,b,c){console.log(arguments.length)?}?
          fn.length?//?3
          fn(6,6,)?//?2?

          this參數(shù)

          this就是函數(shù)上下文,而this指向依賴于函數(shù)的調(diào)用方式,所以this稱作調(diào)用上下文更合適。當(dāng)函數(shù)作為“函數(shù)調(diào)用“,是指區(qū)別于方法、構(gòu)造器以及apply/call,this指向與widow對象

          function?ninja(){console.log(this)}
          ninja()?//window

          當(dāng)函數(shù)作為“方法調(diào)用”,是指當(dāng)函數(shù)被賦值給對象的一個屬性,并使用引用該函數(shù)的這個屬性進行調(diào)用,this指向該對象,實例如下:

          var?o={};
          o.ninja=function(){?console.log(this)};
          o.ninja();?//?o


          當(dāng)函數(shù)作為“構(gòu)造器”進行調(diào)用時:

          • 創(chuàng)建一個新的對象
          • 將構(gòu)造器函數(shù)作用域賦值給新對象(因此this指向了這個新對象)
          • 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
          • 如果沒有顯式的返回值,返回新對象
          function?Ninja(){
          ????this.shulk=function(){console.log(this)}
          }
          var?Ninja1=new?Ninja();
          var?Ninja1=new?Ninja();
          Ninja1.shulk()?//Ninja1
          Ninja2.shulk()?//Ninja2

          使用apply()和call()方法進行調(diào)用 在函數(shù)調(diào)用的時候JavaScript為我們提供一種可以顯式指定任何一個對象作為函數(shù)的上下文,使用apply()和call()方法可以實現(xiàn)這種功能
          function?juggle(){
          ????var?result=0;
          ????for(?var?i=0;i<arguments.length;i++){
          ????????result=+arguments[i];
          ????}
          ????this.result=result;
          }
          var?ninja1={},ninja2={};

          juggle.apply(ninja1,[1,2,3,4])?
          console.log(ninja1.result)?//?10
          juggle.call(ninja2,5,6,7,8)?
          console.log(ninja2.result)?//?26

          在本例中可以看出apply()方法把函數(shù)juggle上下文指定給了ninja1對象,call()方法把函數(shù)juggle上下文指定給了ninja2。常見使用apply()、call()方法是在實現(xiàn)函數(shù)回調(diào)的時候,比如下面實現(xiàn)一個簡單的forEach函數(shù):
          function?forEach(list,callback)(){
          ????for(var?i=0;i>list.length;i++){
          ????????callback.call(list[i],list[i],i)
          ????}
          }
          var?arr=['Tom','alice','jack'];
          forEach(arr,function(e,i){
          ????console.log(e)
          })??//Tom,alice,jack

          apply()和call()的功能基本相同,我們該選擇哪個比較好呢?如果在變量里有很多無關(guān)的值或者是指定為字面量,使用call()則可以直接將其作為參數(shù)列表傳進去,但是如果這些參數(shù)已經(jīng)在一個數(shù)組里了,或者很容易將其收集到數(shù)組里,apply()是更好選擇。

          4.async/await 原理

          ? ? async/await語法糖就是使用Generator函數(shù)+自動執(zhí)行器來運作的


          5.判斷數(shù)組的方法以及優(yōu)缺點

          • Array.isArray(arr) :兼容性不好
          • arr?instanceof Array
          • arr.proto?=== Array.prototype
          • arr.constructor === Array
          • Object.prototype.toString.call(arr) === '[object Array]'

          6.js腳本加載問題,async、defer問題

          • 如果依賴其他腳本和 DOM 結(jié)果,使用 defer
          • 如果與 DOM 和其他腳本依賴不強時,使用 async


          7.什么是作用域鏈?

          JavaScript 引擎會沿著“當(dāng)前執(zhí)行上下文–>內(nèi)嵌函數(shù)閉包–> 全局執(zhí)行上下文”的冒泡的方式順序查找變量,就形成了一條作用域鏈


          8.this 的指向

          • 當(dāng)函數(shù)作為對象的方法調(diào)用時,函數(shù)中的 this 就是該對象;
          • 當(dāng)函數(shù)被正常調(diào)用時,在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對象 window;


          9.改變 this 指向的方式:

          • 用 call、apply、bind設(shè)置
          • 通過對象調(diào)用方法設(shè)置(指向該對象)
          • 通過構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對象)


          10. 用過哪些設(shè)計模式

          11. Javascript垃圾回收

          當(dāng)對象不再被?引用?時被垃圾回收機制回收(“對象有沒有其他對象引用到它”)。

          內(nèi)存泄露就是不再被需要的內(nèi)存, 由于某種原因, 無法被釋放

          • 引用計數(shù)法:對象是否不再需要(限制:循環(huán)引入不能被回收)
          • 標(biāo)記清除法:從根開始,找所有從根開始引用的對象標(biāo)記,然后找這些對象引用的對象標(biāo)記,把不能達到的回收(這個算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”。)
          • 通過構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對象)

          避免內(nèi)存泄漏:

          • 盡少創(chuàng)建全局變量
          • 手動清除計時器
          • 少用閉包
          • 使用弱引用WeakMap和WeakSet


          12. 0.1 + 0.2 === 0.3 嘛?為什么?

          計算機存儲以二進制的方式,而0.1 在二進制中是無限循環(huán)的一些數(shù)字,所以會出現(xiàn)裁剪,精度丟失會出現(xiàn),0.100000000000000002 === 0.1,0.200000000000000002 === 0.2 // true 這兩加起來肯定不等于0.3解決:parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true

          四、es6

          es6新特性

          1. Proxy 有什么特點

          用于修改某些操作的默認(rèn)行為,(攔截對象對其內(nèi)置方法進行重載)

          可以直接攔截對象和數(shù)組

          let?target?=?{
          ????x:?10,
          ????y:?20
          };
          let?hanler?=?{
          ????//?obj=>target,prop?=>x
          ????get:?(obj,?prop)?=>?42
          };

          target?=?new?Proxy(target,?hanler);
          target.x;?//42
          target.y;?//42
          target.x;?//?42

          可以攔截的對象:

          • get、set、has、apply、construct、ownKeys
          • deleteProperty、defineProperty、isExtensible、preventExtensions
          • getPrototypeOf、setPrototypeOf、getOwnPropertyDescriptor

          實際用途可以攔截的對象:

          • 設(shè)置對象的初始值覆蓋undefined
          • 緩存封裝是否過期攔截


          2.CommonJs 和 ES6 module 區(qū)別

          • 前者支持動態(tài)引入即require(${path}/xx.js),后者不支持
          • ES6 module 靜態(tài)引入,即編譯時引入,CommonJs 動態(tài)引入,即執(zhí)行時引入
          • 前者是對模塊的淺拷貝,后者是對模塊的引用
          • 前者可以對導(dǎo)出對值賦值(改變指針),而后者不能相當(dāng)于 const
          • 后者支持 tree shaking,前者不支持


          3. 什么是UMD

          ((root,?factory)?=>?{
          ????if?(typeof?define?===?'function'?&&?define.amd)?{
          ????????//AMD
          ????????define(['jquery'],?factory);
          ????}?else?if?(typeof?exports?===?'object')?{
          ????????//CommonJS
          ????????var?$?=?requie('jquery');
          ????????module.exports?=?factory($);
          ????}?else?{
          ????????root.testModule?=?factory(root.jQuery);
          ????}
          })(this,?($)?=>?{
          ????//todo
          });

          4. weak-Set、weak-Map 和 Set、Map 區(qū)別

          WeakMap 是類似于 Map 的集合,它僅允許對象作為鍵,并且一旦通過其他方式無法訪問它們,便會將它們與其關(guān)聯(lián)值一同刪除。WeakSet 是類似于 Set 的集合,它僅存儲對象,并且一旦通過其他方式無法訪問它們,便會將其刪除。

          5. WebAssembly 是什么

          WebAssembly 是一種可以使用非 JavaScript 編程語言編寫代碼并且能在瀏覽器上運行的技術(shù)方案


          6.柯里化有什么作用

          主要有3個作用:參數(shù)復(fù)用提前返回和?延遲執(zhí)行

          es6解構(gòu)用法

          1.獲取用戶對象數(shù)組中的id集合

          普通寫法:

          let?users?=?[{id:'abc',name:'tom'},{id:'dfg',name:'jake'}]
          ?function?getUserIds(){
          ?????let?userIds?=?[];
          ????users.forEach(user=>{
          ????????userIds.push({id:user.id})
          ????})?
          ????return?userIds
          ?}?
          ?getUserIds()?//?[{id:'abc'},{id:'dfg'}]

          bigger 寫法:

          letlet?users?=?[{id:'abc',name:'tom'},{id:'dfg',name:'jake'}]
          function?getUserIds(){
          ??let?userIds?=?users.map(user=>{
          ?????let?{id}?=?user
          ?????return?{id}
          ???})?
          ???return?userIds
          }?
          ?getUserIds()?//?[{id:'abc'},{id:'dfg'}]

          2.刪除對象

          let?regionTree=[
          ????{
          ??????disabled:false,
          ??????value:'廣州',
          ??????children:[]
          ????},
          ????{
          ??????disabled:true,
          ??????value:'深圳',
          ??????children:[]
          ????}
          ??]
          let?result?=?regionTree.map(item?=>?{
          ?????????let?{?disabled,?...values?}?=?item;
          ?????????return?values;
          ???????});
          //result=[{value:'廣州',children:[]},{value:'深圳',children:[]}]

          3.優(yōu)雅地獲取年月日

          const?[all,?year,?month,?day]?=?/^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec('2020-01-20');
          //?2020-01-20?2020?01?20

          4.為Ajax請求設(shè)置預(yù)警

          假設(shè) this.$http(url) 返回的對象正確格式為{code:0,data:{id:'123',name:'tony'} },如data返回為空則拋出異常

          ?function?toastErr?(){
          ????alert('俄歐,沒有獲取到任何數(shù)據(jù)~')
          ?}
          ?let?{code,data:y=toastErr()}?=?this.$http(url)

          5.獲取函數(shù)參數(shù)

          function?getArguments(...args)?{?//這里args?是函數(shù)內(nèi)置的Arguments類數(shù)組
          ???console.log(args)?//?[1,3,2]
          }
          let?a=1,b=2,c=3
          getArguments(a,b,c)

          五、前端設(shè)計模式

          工廠模式

          簡單工廠- 處理變與不變的

          工廠模式:將創(chuàng)建對象的過程單獨封裝,實現(xiàn)無腦傳參,核心:處理變與不變的
          改進前:

          function?Coder(name?,?age)?{
          ????this.name?=?name
          ????this.age?=?age
          ????this.career?=?'coder'?
          ????this.work?=?['寫代碼','寫系分',?'修Bug']
          }
          function?ProductManager(name,?age)?{
          ????this.name?=?name?
          ????this.age?=?age
          ????this.career?=?'product?manager'
          ????this.work?=?['訂會議室',?'寫PRD',?'催更']
          }
          function?Factory(name,?age,?career)?{
          ????switch(career)?{
          ????????case?'coder':
          ????????????return?new?Coder(name,?age)?
          ????????????break
          ????????case?'product?manager':
          ????????????return?new?ProductManager(name,?age)
          ????????????break
          ????????...
          }

          改進后

          function?User(name,age,career,word){
          ????this.name?=?name?
          ????this.age?=?age
          ????this.career?=?career
          ????this.word?=?word?
          }

          function?Factory(name,age,career){
          ???let?word=[]
          ???switch(career){
          ?????case?'coder':
          ???????word=['寫代碼','寫系分',?'修Bug']
          ???????break;
          ?????case?'product?manager':
          ???????word=['寫原型','催進度']
          ???????break;?
          ?????case?'boss':
          ???????word=['喝茶','見客戶','談合作']
          ???????break;?
          ??}
          ?return?new?User(name,age,career,word)
          }


          抽象工廠- 開放封閉原則

          簡單工廠因為沒有遵守開放封閉原則, 暴露一個很大的缺陷。例如若我們添加管理層一些考評權(quán)限,難道我們要重新去修改Factory函數(shù)嗎?這樣做會導(dǎo)致Factory會變得異常龐大,而且很容易出bug,最后非常難維護
          class?MobilePhoneFactory?{
          ????//?提供操作系統(tǒng)的接口
          ????createOS(){
          ????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
          ????}
          ????//?提供硬件的接口
          ????createHardWare(){
          ????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
          ????}
          }
          //?具體工廠繼承自抽象工廠
          class?FakeStarFactory?extends?MobilePhoneFactory?{
          ????createOS()?{
          ????????//?提供安卓系統(tǒng)實例
          ????????return?new?AndroidOS()
          ????}
          ????createHardWare()?{
          ????????//?提供高通硬件實例
          ????????return?new?QualcommHardWare()
          ????}
          }

          //?定義操作系統(tǒng)這類產(chǎn)品的抽象產(chǎn)品類
          class?OS?{
          ????controlHardWare()?{
          ????????throw?new?Error('抽象產(chǎn)品方法不允許直接調(diào)用,你需要將我重寫!');
          ????}
          }

          //?定義具體操作系統(tǒng)的具體產(chǎn)品類
          class?AndroidOS?extends?OS?{
          ????controlHardWare()?{
          ????????console.log('我會用安卓的方式去操作硬件')
          ????}
          }

          class?AppleOS?extends?OS?{
          ????controlHardWare()?{
          ????????console.log('我會用??的方式去操作硬件')
          ????}
          }
          //?定義手機硬件這類產(chǎn)品的抽象產(chǎn)品類
          class?HardWare?{
          ????//?手機硬件的共性方法,這里提取了“根據(jù)命令運轉(zhuǎn)”這個共性
          ????operateByOrder()?{
          ????????throw?new?Error('抽象產(chǎn)品方法不允許直接調(diào)用,你需要將我重寫!');
          ????}
          }

          //?定義具體硬件的具體產(chǎn)品類
          class?QualcommHardWare?extends?HardWare?{
          ????operateByOrder()?{
          ????????console.log('我會用高通的方式去運轉(zhuǎn)')
          ????}
          }

          class?MiWare?extends?HardWare?{
          ????operateByOrder()?{
          ????????console.log('我會用小米的方式去運轉(zhuǎn)')
          ????}
          }

          //?這是我的手機
          const?myPhone?=?new?FakeStarFactory()
          //?讓它擁有操作系統(tǒng)
          const?myOS?=?myPhone.createOS()
          //?讓它擁有硬件
          const?myHardWare?=?myPhone.createHardWare()
          //?啟動操作系統(tǒng)(輸出‘我會用安卓的方式去操作硬件’)
          myOS.controlHardWare()
          //?喚醒硬件(輸出‘我會用高通的方式去運轉(zhuǎn)’)
          myHardWare.operateByOrder()

          對原有的系統(tǒng)不會造成任何潛在影響所謂的“對拓展開放,對修改封閉”

          單例模式

          單例模式 - 保證一個類只有一個實例

          保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
          class?SingleDog?{
          ?show(){
          ??console.log('單例方法')
          ?}
          }
          let?single1=new?SingleDog()
          let?single2=new?SingleDog()

          single1===single2?//?false

          單例模式要求不管我們嘗試去創(chuàng)建多少次,它都只給你返回第一次所創(chuàng)建的那唯一的一個實例

          class?SingleDog?{
          ?show(){
          ??console.log('單例方法')
          ?}
          ?static?getInstance(){
          ???if(!SingleDog.instance){
          ????SingleDog.instance?=?new?SingleDog()????
          ????}
          ????return?SingleDog.instance
          ??}
          }
          let?single1?=?SingleDog.getInstance()
          let?single2?=?SingleDog.getInstance()
          single1===single2?//?true

          單例實際應(yīng)用 vuex

          Store 存放共享數(shù)據(jù)的唯一數(shù)據(jù)源,要求一個 Vue 實例只能對應(yīng)一個 Store,即Vue 實例(即一個 Vue 應(yīng)用)只會被 install 一次 Vuex 插件

          let?Vue?//?這個Vue的作用和樓上的instance作用一樣
          ...

          export?function?install?(_Vue)?{
          ??//?判斷傳入的Vue實例對象是否已經(jīng)被install過Vuex插件(是否有了唯一的state)
          ??if?(Vue?&&?_Vue?===?Vue)?{
          ????if?(process.env.NODE_ENV?!==?'production')?{
          ??????console.error(
          ????????'[vuex]?already?installed.?Vue.use(Vuex)?should?be?called?only?once.'
          ??????)
          ????}
          ????return
          ??}
          ??//?若沒有,則為這個Vue實例對象install一個唯一的Vuex
          ??Vue?=?_Vue
          ??//?將Vuex的初始化邏輯寫進Vue的鉤子函數(shù)里
          ??applyMixin(Vue)
          }

          裝飾器模式

          裝飾器模式 - 實現(xiàn)只添加不修改

          拓展彈窗功能

          //?定義打開按鈕
          class?OpenButton?{
          ????//?點擊后展示彈框(舊邏輯)
          ????onClick()?{
          ????????const?modal?=?new?Modal()
          ?????modal.style.display?=?'block'
          ????}
          }

          //?定義按鈕對應(yīng)的裝飾器
          class?Decorator?{
          ????//?將按鈕實例傳入
          ????constructor(open_button)?{
          ????????this.open_button?=?open_button
          ????}
          ????onClick()?{
          ????????this.open_button.onClick()
          ????????//?“包裝”了一層新邏輯
          ????????this.changeButtonStatus()
          ????}
          ????changeButtonStatus()?{
          ????????this.changeButtonText()
          ????????this.disableButton()
          ????}
          ????disableButton()?{
          ????????const?btn?=??document.getElementById('open')
          ????????btn.setAttribute("disabled",?true)
          ????}
          ????changeButtonText()?{
          ????????const?btn?=?document.getElementById('open')
          ????????btn.innerText?=?'快去登錄'
          ????}
          }

          const?openButton?=?new?OpenButton()
          const?decorator?=?new?Decorator(openButton)

          document.getElementById('open').addEventListener('click',?function()?{
          ????//?openButton.onClick()
          ????//?此處可以分別嘗試兩個實例的onClick方法,驗證裝飾器是否生效

          ????decorator.onClick()
          })


          es7 裝飾器

          function?classDecorator(target)?{
          ????target.hasDecorator?=?true
          ???return?target
          }

          //?將裝飾器“安裝”到Button類上
          @classDecorator
          class?Button?{
          ????//?Button類的相關(guān)邏輯
          }

          裝飾器的原型

          @decorator
          class?A?{}

          //?等同于

          class?A?{}
          A?=?decorator(A)?||?A;

          裝飾器只能用于類或者類的方法原因:普通函數(shù)聲明會提升


          實現(xiàn) react 的高階函數(shù)

          定義裝飾器

          import?React,?{?Component?}?from?'react'

          const?BorderHoc?=?WrappedComponent?=>?class?extends?Component?{
          ??render()?{
          ????return?<div?style={{?border:?'solid?1px?red'?}}>
          ??????<WrappedComponent?/>
          ????</div>

          ??}
          }
          export?default?borderHoc

          應(yīng)用裝飾器

          import?React,?{?Component?}?from?'react'
          import?BorderHoc?from?'./BorderHoc'

          //?用BorderHoc裝飾目標(biāo)組件
          @BorderHoc?
          class?TargetComponent?extends?React.Component?{
          ??render()?{
          ????//?目標(biāo)組件具體的業(yè)務(wù)邏輯
          ??}
          }

          //?export出去的其實是一個被包裹后的組件
          export?default?TargetComponent

          適配器模式

          適配器模式 - 兼容就是一把梭

          原 ajax 定義

          function?Ajax(type,?url,?data,?success,?failed){
          ????//?創(chuàng)建ajax對象
          ????var?xhr?=?null;
          ????if(window.XMLHttpRequest){
          ????????xhr?=?new?XMLHttpRequest();
          ????}?else?{
          ????????xhr?=?new?ActiveXObject('Microsoft.XMLHTTP')
          ????}
          ?
          ???...(此處省略一系列的業(yè)務(wù)邏輯細(xì)節(jié))
          ???
          ???var?type?=?type.toUpperCase();
          ????
          ????//?識別請求類型
          ????if(type?==?'GET'){
          ????????if(data){
          ??????????xhr.open('GET',?url?+?'?'?+?data,?true);?//如果有數(shù)據(jù)就拼接
          ????????}?
          ????????//?發(fā)送get請求
          ????????xhr.send();
          ?
          ????}?else?if(type?==?'POST'){
          ????????xhr.open('POST',?url,?true);
          ????????//?如果需要像 html 表單那樣 POST 數(shù)據(jù),使用 setRequestHeader()?來添加 http 頭。
          ????????xhr.setRequestHeader("Content-type",?"application/x-www-form-urlencoded");
          ????????//?發(fā)送post請求
          ????????xhr.send(data);
          ????}
          ?
          ????//?處理返回數(shù)據(jù)
          ????xhr.onreadystatechange?=?function(){
          ????????if(xhr.readyState?==?4){
          ????????????if(xhr.status?==?200){
          ????????????????success(xhr.responseText);
          ????????????}?else?{
          ????????????????if(failed){
          ????????????????????failed(xhr.status);
          ????????????????}
          ????????????}
          ????????}
          ????}
          }

          fetch 請求封裝

          export?default?class?HttpUtils?{
          ??//?get方法
          ??static?get(url)?{
          ????return?new?Promise((resolve,?reject)?=>?{
          ??????//?調(diào)用fetch
          ??????fetch(url)
          ????????.then(response?=>?response.json())
          ????????.then(result?=>?{
          ??????????resolve(result)
          ????????})
          ????????.catch(error?=>?{
          ??????????reject(error)
          ????????})
          ????})
          ??}
          ??
          ??//?post方法,data以object形式傳入
          ??static?post(url,?data)?{
          ????return?new?Promise((resolve,?reject)?=>?{
          ??????//?調(diào)用fetch
          ??????fetch(url,?{
          ????????method:?'POST',
          ????????headers:?{
          ??????????Accept:?'application/json',
          ??????????'Content-Type':?'application/x-www-form-urlencoded'
          ????????},
          ????????//?將object類型的數(shù)據(jù)格式化為合法的body參數(shù)
          ????????body:?this.changeData(data)
          ??????})
          ????????.then(response?=>?response.json())
          ????????.then(result?=>?{
          ??????????resolve(result)
          ????????})
          ????????.catch(error?=>?{
          ??????????reject(error)
          ????????})
          ????})
          ??}
          ??
          ??//?body請求體的格式化方法
          ??static?changeData(obj)?{
          ????var?prop,
          ??????str?=?''
          ????var?i?=?0
          ????for?(prop?in?obj)?{
          ??????if?(!prop)?{
          ????????return
          ??????}
          ??????if?(i?==?0)?{
          ????????str?+=?prop?+?'='?+?obj[prop]
          ??????}?else?{
          ????????str?+=?'&'?+?prop?+?'='?+?obj[prop]
          ??????}
          ??????i++
          ????}
          ????return?str
          ??}
          }


          fetch 兼容 ajax(放棄ajax)

          //?Ajax適配器函數(shù),入?yún)⑴c舊接口保持一致
          async?function?AjaxAdapter(type,?url,?data,?success,?failed)?{
          ????const?type?=?type.toUpperCase()
          ????let?result
          ????try?{
          ?????????//?實際的請求全部由新接口發(fā)起
          ?????????if(type?===?'GET')?{
          ????????????result?=?await?HttpUtils.get(url)?||?{}
          ????????}?else?if(type?===?'POST')?{
          ????????????result?=?await?HttpUtils.post(url,?data)?||?{}
          ????????}
          ????????//?假設(shè)請求成功對應(yīng)的狀態(tài)碼是1
          ????????result.statusCode?===?1?&&?success???success(result)?:?failed(result.statusCode)
          ????}?catch(error)?{
          ????????//?捕捉網(wǎng)絡(luò)錯誤
          ????????if(failed){
          ????????????failed(error.statusCode);
          ????????}
          ????}
          }

          //?用適配器適配舊的Ajax方法
          async?function?Ajax(type,?url,?data,?success,?failed)?{
          ????await?AjaxAdapter(type,?url,?data,?success,?failed)
          }

          代理模式

          事件代理:點擊子元素,用父元素代理

          緩存代理

          const?addAll?=?function()?{
          ????console.log('進行了一次新計算')
          ????let?result?=?0
          ????const?len?=?arguments.length
          ????for(let?i?=?0;?i?<?len;?i++)?{
          ????????result?+=?arguments[i]
          ????}
          ????return?result
          }

          //?為求和方法創(chuàng)建代理
          const?proxyAddAll?=?(function(){
          ????//?求和結(jié)果的緩存池
          ????const?resultCache?=?{}
          ????return?function()?{
          ????????//?將入?yún)⑥D(zhuǎn)化為一個唯一的入?yún)⒆址?/span>
          ????????const?args?=?Array.prototype.join.call(arguments,?',')
          ????????
          ????????//?檢查本次入?yún)⑹欠裼袑?yīng)的計算結(jié)果
          ????????if(args?in?resultCache)?{
          ????????????//?如果有,則返回緩存池里現(xiàn)成的結(jié)果
          ????????????return?resultCache[args]
          ????????}
          ????????return?resultCache[args]?=?addAll(...arguments)
          ????}
          })()

          策略模式 - 消除 if-else 能手

          //?定義一個詢價處理器對象
          const?priceProcessor?=?{
          ??pre(originPrice)?{
          ????if?(originPrice?>=?100)?{
          ??????return?originPrice?-?20;
          ????}
          ????return?originPrice?*?0.9;
          ??},
          ??onSale(originPrice)?{
          ????if?(originPrice?>=?100)?{
          ??????return?originPrice?-?30;
          ????}
          ????return?originPrice?*?0.8;
          ??},
          ??back(originPrice)?{
          ????if?(originPrice?>=?200)?{
          ??????return?originPrice?-?50;
          ????}
          ????return?originPrice;
          ??},
          ??fresh(originPrice)?{
          ????return?originPrice?*?0.5;
          ??},
          };
          //?詢價函數(shù)
          function?askPrice(tag,?originPrice)?{
          ??return?priceProcessor[tag](originPrice)
          }

          觀察者模式

          應(yīng)用例子:需求發(fā)布

          流程產(chǎn)品經(jīng)理開群然后拉人(開發(fā))進群,需求更新時通知開發(fā)者,開發(fā)者接到到需求開始工作。
          ????//?發(fā)布者
          ????class?Publisher{
          ??????constructor(){
          ????????this.observers=[]
          ??????}
          ??????add(observer){
          ????????this.observers.push(observer)
          ????????
          ??????}
          ??????remove(observer){
          ????????const?index?=this.observers.findIndex(item=>item===observer)
          ????????this.observers.splice(index,1)
          ??????}
          ??????notify(state){
          ????????this.observers.forEach(observer=>observer.update(state))
          ??????}
          ????}
          ??//?觀察者
          ????class?Observer{
          ??????constructor()?{
          ????????console.log('Observer?created')
          ????}
          ??????update(){
          ????????console.log('我干活辣')
          ??????}
          ????}
          ????//?產(chǎn)品經(jīng)理類?(文檔發(fā)布者)
          ????class?Prdpublisher?extends?Publisher{
          ??????constructor(){
          ????????super()
          ????????this.prdState?=?{}
          ????????this.observers=[]
          ????????console.log('Prdpublisher?created')
          ??????}
          ??????getState(){
          ????????return?this.prdState
          ??????}
          ??????setState(state){
          ????????console.log('this.observers',this.observers)
          ???????this.prdState?=?state
          ???????this.notify(state)
          ??????}
          ????}
          ????//?開發(fā)者類
          ????class?DeveloperObserver?extends?Observer{
          ?????constructor(){
          ???????super()
          ???????this.prdState={}
          ???????console.log('DeveloperObserver?created')
          ?????}
          ?????update(state){
          ??????this.prdState?=?state
          ??????this.word()
          ?????}
          ?????word(){
          ???????const?prdState?=?this.prdState
          ???????console.log('start?wording',prdState)
          ?????}
          ????}
          ????const?observeA?=?new?DeveloperObserver()?//前端
          ????const?observeB?=?new?DeveloperObserver()?//后端
          ????const?lilei?=?new?Prdpublisher()?//?產(chǎn)品經(jīng)理
          ????lilei.add(observeA)?//?拉群
          ????lilei.add(observeB)
          ????let?prd={
          ??????//?需求內(nèi)容
          ??????'login':3,
          ??????'auth':2
          ????}
          ????//?更新需求?同時通知開發(fā)者
          ????lilei.setState(prd)

          vue 響應(yīng)試原理-觀察者模式

          觀察者模式和發(fā)布-訂閱模式的區(qū)別是:發(fā)布-訂閱模式,事件的注冊和觸發(fā)發(fā)生在獨立于雙方的第三方平臺。觀察者模式:發(fā)布者會直接觸及到訂閱者
          ?function?observe(target){
          ?????if(target?&&?typeof?target==='object'){
          ???????Object.keys(target).forEach(key=>{
          ????????defineReactive(target,key,target[key])
          ???????})
          ?????}
          ????}
          ????function?defineReactive(target,?key,val)?{
          ????????observe(val)
          ????????let?dep?=?new?Dep()
          ????????Object.defineProperty(target,?key,?{
          ??????????enumerable:true,
          ??????????configurable:false,
          ??????????get()?{
          ????????????return?val
          ??????????},
          ??????????set(value)?{
          ????????????val=value
          ????????????dep.notify()
          ??????????},
          ????????});
          ??????}
          ????class?Dep?{
          ??????constructor(dep)?{
          ????????this.deps?=?[];
          ??????}
          ??????add(dep)?{
          ????????this.deps.push(dep);
          ??????}
          ??????notify()?{
          ????????this.deps.forEach((dep)?=>?dep.update());
          ??????}
          ????}

          vue eventBus

          ?class?EventEmitter?{
          ??????constructor()?{
          ????????this.handlers?=?{};
          ??????}
          ??????on(eventName,?cb)?{
          ????????if?(!this.handlers[eventName])?{
          ??????????this.handlers[eventName]?=?[cb];
          ????????}?else?{
          ??????????this.handlers[eventName].push(cb);
          ????????}
          ??????}
          ??????emit(eventName,?data)?{
          ????????if?(!this.handlers[eventName])?{
          ??????????console.log('監(jiān)聽器不存在');
          ??????????return;
          ????????}
          ????????const?events?=?this.handlers[eventName].slice();
          ????????events.forEach((cb)?=>?{
          ??????????cb(data);
          ????????});
          ??????}
          ??????off(eventName,?cb)?{
          ????????if?(!this.handlers[eventName])?{
          ??????????console.log('監(jiān)聽器不存在');
          ??????????return;
          ????????}
          ????????const?callBacks?=?this.handlers[eventName];
          ????????const?index?=?callBacks.findIndex((item)?=>?item?===?cb);
          ????????callBacks.splice(index,?1);
          ??????}
          ??????once(eventName,?cb)?{
          ????????const?wrap?=?(data)?=>?{
          ??????????let?fn?=?this.handlers[eventName];
          ??????????cb(data);
          ??????????this.off(eventName,?fn);
          ????????};
          ???????
          ????????this.on(eventName,?wrap);
          ??????}
          ????}
          ????let?eventBus?=?new?EventEmitter();
          ????eventBus.once('success',?(data)?=>?{
          ??????console.log('data',?data);
          ????});
          ????eventBus.emit('success',?456);
          ????eventBus.emit('success',?577);

          六、HTTP

          從輸入 URL 到頁面加載完成,發(fā)生了什么?

          1、HTTP 請求準(zhǔn)備階段

          • 構(gòu)建請求
          瀏覽器構(gòu)建請求行信息,準(zhǔn)備發(fā)起網(wǎng)絡(luò)請求 GET /index.html HTTP1.1
          • 查找緩存
          如果瀏覽器發(fā)現(xiàn)請問資源在瀏覽器中存在副本,根據(jù)強緩存規(guī)則,如沒有過期那么直接返回資源,如何查找失敗進入下一個環(huán)節(jié):
          • 準(zhǔn)備 ip 地址和端口
          • DNS(域名和ip的映射系統(tǒng)) 域名解析,拿到ip之后找端口,默認(rèn)為80
          • 建立tcp鏈接(三次握手)
          • 如果是https 還需要建立TLS連接

          2、HTTP 發(fā)送請求

          • 瀏覽器向服務(wù)端發(fā)起http請求,把請求頭和請求行一起發(fā)送個服務(wù)器,服務(wù)端解析請求頭如發(fā)現(xiàn)cache-control和etag(if-none-match),if-modified(if-modified-since)字段就會判斷緩存是否過期,如果沒有返回304,否則返回200

          3、HTTP 響應(yīng)返回

          • 瀏覽器拿到響應(yīng)數(shù)據(jù),首先判斷是否是4XX或者5XX是就報錯,如果是3XX就重定向,2XX就開始解析文件,如果是gzip就解壓文件
          • TCP斷開連接4次揮手
          • 瀏覽器解析渲染建立根據(jù)html建立dom樹和css樹,如果遇到script首選判斷是否defer和async否則會阻塞渲染并編譯執(zhí)行js,如果沒有則組合生成render tree,最后瀏覽器開啟GPU進行繪制合成圖層,將內(nèi)容顯示屏幕。


          HTTP0.9 特性

          • 沒有請問頭和請求體只有請求行

          • 只能發(fā)送html文件


          HTTP1.0 特性

          • 可以發(fā)送javaScript、CSS、圖片、音頻

          • 加上請求頭和請求體

          • 狀態(tài)碼

          • cache 機制

          • 每進行一次 HTTP 通信,都需要經(jīng)歷建立 TCP 連接、傳輸 HTTP 數(shù)據(jù)和斷開 TCP 連接三個階段


          HTTP1.1 特性

          • 持久連接的方法,它的特點是在一個 TCP 連接上可以傳輸多個 HTTP 請求,只要瀏覽器或者服務(wù)器沒有明確斷開連接,那么該 TCP 連接會一直保持(提高性能)
          • 持久連接雖然能減少 TCP 的建立和斷開次數(shù),但是它需要等待前面的請求返回之后,才能進行下一次請求。如果 TCP 通道中的某個請求因為某些原因沒有及時返回,那么就會阻塞后面的所有請求,這就是著名的隊頭阻塞的問題(在 HTTP 1.1 中,每一個鏈接都默認(rèn)是長鏈接,因此對于同一個 TCP 鏈接,HTTP 1.1 規(guī)定:服務(wù)端的響應(yīng)返回順序需要遵循其接收到相應(yīng)的順序。但這樣存在一個問題:如果第一個請求處理需要較長時間,響應(yīng)較慢,將會“拖累”其他后續(xù)請求的響應(yīng),這是一種隊頭阻塞。)
          • 引入了 Cookie、虛擬主機的支持、對動態(tài)內(nèi)容的支持
          • 瀏覽器為每個域名最多同時維護 6 個 TCP 持久連接(提高性能)
          • 使用 CDN 的實現(xiàn)域名分片機制。(提高性能)
          • 對帶寬的利用率卻并不理想(tcp 啟動慢、頭部堵塞、tcp 競爭)


          HTTP2 特點

          • 只使用一個 TCP 長連接來傳輸數(shù)據(jù),實現(xiàn)資源的并行請求,也就是任何時候都可以將請求發(fā)送給服務(wù)器,解決頭部堵塞(多路復(fù)用)
          • 二進制傳輸
          • 多路復(fù)用(原理二進制分幀層,攜帶id的幀流到服務(wù)器)
          單一的長連接,減少了 SSL 握手的開銷,多路復(fù)用能大幅提高傳輸效率,不用等待上一個請求的響應(yīng),加大cpu 的使用率
          • 頭部壓縮
          頭部被壓縮,減少了數(shù)據(jù)傳輸量
          • 服務(wù)端推送
          有了二進制分幀層還能夠?qū)崿F(xiàn)請求的優(yōu)先級、服務(wù)器推送、頭部壓縮等特性,從而大大提升了文件傳輸效率但是HTTP2還是會存在 tcp 堵塞(理解,http 堵塞就是需要等待前面的http包發(fā)送完成而造成的等待,tcp 堵塞是TCP 傳輸過程中,由于單個數(shù)據(jù)包的丟失而造成的阻塞)


          HTTP3

          QUIC 看成是集成了“TCP+HTTP/2 的多路復(fù)用 +TLS 等功能


          TCP與UDP的區(qū)別

          1、基于連接與無連接;

          2、對系統(tǒng)資源的要求(TCP較多,UDP少);

          3、UDP程序結(jié)構(gòu)較簡單;

          4、流模式與數(shù)據(jù)報模式 ;

          5、TCP保證數(shù)據(jù)正確性,UDP可能丟包;

          6、TCP保證數(shù)據(jù)順序,UDP不保證。


          TCP 握手過程

          • 建立tcp鏈接(三次握手,客戶端發(fā)送 syn=j 給服務(wù)端然后處于 syn_send 狀態(tài);

          • 服務(wù)端接受到syn,然后發(fā)送自己的syn包syn=k,和 ack=j+1(確認(rèn)客戶端包),狀態(tài)為 syn_recv;

          • 客戶端收到ack和syn則發(fā)送 ack=k+1給服務(wù)端表示確認(rèn),服務(wù)端和客戶端都進入了establish狀態(tài)),

          • 為什么要3次握手:確認(rèn)客戶端的接收、發(fā)送能力正常,服務(wù)器自己的發(fā)送、接收能力也正常


          HTTP 緩存

          強緩存(瀏覽器內(nèi)部完成)

          max-age:數(shù)值,單位是秒,從請求時間開始到過期時間之間的秒數(shù)。基于請求時間(Date字段)的相對時間間隔,而不是絕對過期時間

          expires:和max-age一樣指緩存過期時間,但是他的指定了具體時間GMT格式,HTTP/1.1,Expire已經(jīng)被Cache-Control替代,原因在于Expires控制緩存的原理是使用客戶端的時間與服務(wù)端返回的時間做對比,那么如果客戶端與服務(wù)端的時間因為某些原因(例如時區(qū)不同;客戶端和服務(wù)端有一方的時間不準(zhǔn)確)發(fā)生誤差,那么強制緩存則會直接失效,這樣的話強制緩存的存在則毫無意義


          協(xié)商緩存(需要詢問服務(wù)器)

          Last-Modified/If-Modified-Since(服務(wù)端時間對比):本地文件在服務(wù)器上的最后一次修改時間。緩存過期時把瀏覽器端緩存頁面的最后修改時間發(fā)送到服務(wù)器去,服務(wù)器會把這個時間與服務(wù)器上實際文件的最后修改時間進行對比,如果時間一致,那么返回304,客戶端就直接使用本地緩存文件。(瀏覽器最后修改時候和服務(wù)端對比,如果一致則走緩存)

          問題:

          • 現(xiàn)在大多數(shù)服務(wù)端都采用了負(fù)載均衡策略,可能導(dǎo)致不同虛擬主機返回的Last-Modified時間戳不一致,導(dǎo)致對比失敗~

          • 文件也許會周期性的更改,但是他的內(nèi)容并不改變,不希望客戶端重新get

          Etag/If-None-Match:(EntityTags,內(nèi)容摘要)是URL的tag,用來標(biāo)示URL對象是否改變,一般為資源實體的哈希值。和Last-Modified類似,如果服務(wù)器驗證資源的ETag沒有改變(該資源沒有更新),將返回一個304狀態(tài)告訴客戶端使用本地緩存文件。Etag的優(yōu)先級高于Last-Modified,Etag主要為了解決 Last-Modified無法解決的一些問題。


          文件緩存策略

          1. 有文件指紋:index.html 用不用緩存,其他用強緩存+文件指紋

          2. 無指紋:index.html 用不用緩存,其他用協(xié)商etag緩存(文件變了就緩存)


          HTTPS

          原理

          服務(wù)器端的公鑰和私鑰,用來進行非對稱加密

          客戶端生成的隨機密鑰,用來進行對稱加密

          • A 與 B 通信(被監(jiān)聽)

          • A 給 B 一個對成密鑰(對稱加密)(密鑰可能被劫持)

          • A 有自己的公鑰和私鑰(并且只要自己的私鑰解開公鑰)B也是有自己的公私鑰,但每次通信都要解密特別麻煩(非對稱加密)

          • A 直接把公鑰發(fā)給B,然后讓B通過公鑰加密‘對稱密鑰’,效率就快很多

          • 那么這里又存在一個問題 A 傳給 B 的公鑰被截取了然后冒充 A跟B通信

          • 所以就有了ca驗證中心證明 A就是A

          完整流程:

          那么A直接把公鑰發(fā)和證書給 B,B 通過 ca 驗證A身份后后,通過A的公鑰加密‘對稱密鑰’發(fā)給A,A用自己的私鑰解密得到了‘對稱密鑰’,以后就通過對稱加密方式交流

          通訊流程

          d465ecb93a38c78d1210de489b076792.webp

          • 瀏覽器發(fā)起請求

          • 服務(wù)器返回公鑰+簽名證書

          • 瀏覽器向CA認(rèn)證中心詢問證書是否有效

          • CA認(rèn)證返回結(jié)果

          • 瀏覽器用公鑰加密對稱秘鑰

          • 服務(wù)器用自己的私鑰解密 對稱稱秘鑰,發(fā)起通訊


          http 請求方法

          post 和 put 的區(qū)別

          • 都能新增和修改數(shù)據(jù)

          • put 無副作用,調(diào)用一次和多次效果相同,post 調(diào)用每次都會新增

          • post 面向資源集合,put 面向單資源

          put 和 patch 的區(qū)別

          他兩都是同來更新數(shù)據(jù)的,而patch是同來更新局部資源比如某個對象只更改了某個字段則可以用 patch

          常見狀態(tài)碼

          • 206 部分內(nèi)容,代表服務(wù)器已經(jīng)成功處理了部分GET請求,可以應(yīng)用斷點陸續(xù)上傳

          • 301 永久重定向

          • 302 臨時重定向

          • 304 命中協(xié)商緩存

          • 400 請求報文存在語法錯誤

          • 401 需求http認(rèn)證

          • 403 請求資源被服務(wù)器拒絕

          • 404 服務(wù)端找不到資源

          • 500 服務(wù)端執(zhí)行請求發(fā)送錯誤

          • 501 請求超出服務(wù)端能力范圍(不支持某個方法)

          • 502 作為網(wǎng)關(guān)或者代理工作的服務(wù)器嘗試執(zhí)行請求時,從遠(yuǎn)程服務(wù)器接收到了一個無效的響應(yīng)

          • 503 由于超載或系統(tǒng)維護,服務(wù)器暫時的無法處理客戶端的請求


          HTTP 緩存

          http 緩存過程

          1. 客戶端向服務(wù)端發(fā)起第一次請求資源

          • 服務(wù)端返回資源,并通過響應(yīng)頭返回緩存策略

          • 客服端根據(jù)緩存策略將策略和資源緩存起來

          結(jié)論:

          • 瀏覽器每次發(fā)起請求,都會先在瀏覽器緩存中查找該請求的結(jié)果以及緩存標(biāo)識

          • 瀏覽器每次拿到返回的請求結(jié)果都會將該結(jié)果和緩存標(biāo)識存入瀏覽器緩存中

          64be0665f05398031ff5583792feaaf1.webp

          1. 當(dāng)客戶端再次向服務(wù)端發(fā)起請求時

          • 客戶端判斷是否有緩存,有則判斷是否存在 cache-control,并根據(jù)max-age 判斷其是否已過期,沒有過期直接讀取緩存,返回200

          • 若已過期則攜帶緩存策略if-none-match發(fā)送到服務(wù)端

          • 服務(wù)端根據(jù) etag 與if-none-match 相同則返回304繼續(xù)使用緩存,更新新鮮度

          • 不相同則重新返回資源

          19129c8d8350e043e2d7889c0e882ce4.webp

          緩存位置

          • from memory cache代表使用內(nèi)存中的緩存,
          • from disk cache則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序為memory –> disk
          • 內(nèi)存緩存(from memory cache):內(nèi)存緩存具有兩個特點,分別是快速讀取和時效性:
          • 快速讀取:內(nèi)存緩存會將編譯解析后的文件,直接存入該進程的內(nèi)存中,占據(jù)該進程一定的內(nèi)存資源,以方便下次運行使用時的快速讀取。
          • 時效性:一旦該進程關(guān)閉,則該進程的內(nèi)存則會清空。
          • 硬盤緩存(from disk cache):硬盤緩存則是直接將緩存寫入硬盤文件中,讀取緩存需要對該緩存存放的硬盤文件進行I/O操作,然后重新解析該緩存內(nèi)容,讀取復(fù)雜,速度比內(nèi)存緩存慢。
          在瀏覽器中,瀏覽器會在js和圖片等文件解析執(zhí)行后直接存入內(nèi)存緩存中,那么當(dāng)刷新頁面時只需直接從內(nèi)存緩存中讀取(from memory cache);而css文件則會存入硬盤文件中,所以每次渲染頁面都需要從硬盤讀取緩存(from disk cache)


          Web安全防御

          xss 跨域腳本攻擊(利用網(wǎng)站對用戶的信任)

          1. 非持久型 - url參數(shù)直接注入 腳本,偽造成正常域名誘惑用戶點擊訪問(瀏覽器默認(rèn)防范?)

          2. 持久型 - 存儲到DB后讀取時注入 (災(zāi)難)

          兩者都是通過腳本獲取cookies 然后通過img或者ajax請求發(fā)送到黑客服務(wù)器?

          防范手段:

          • html字符轉(zhuǎn)譯,或者只過濾script這些腳步

          • 限制資源請求來源csp

          • cookies 防范: value 如果用于保存用戶登錄態(tài),應(yīng)該將該值加密,不能使用明文的用戶標(biāo)識 http-only 不能通過 JS 訪問 Cookie,減少 XSS 攻擊
            Content-Security-Policy: img-src https://* 只允許加載 HTTPS 協(xié)議圖片 Content-Security-Policy: default-src ‘self’ 只允許加載本站資源

          • 前端方案:設(shè)置 meta 標(biāo)簽的方式

          Csrf 跨域請求偽造(利用用戶對網(wǎng)站的信任):

          即跨站請求偽造,是一種常見的Web攻擊,它利用用戶已登錄的身份,在用戶毫不知情的情況下,以用戶的名義完成非法操作 在A網(wǎng)站沒有退出的情況下,引誘用戶訪問第三方網(wǎng)站(評論發(fā)鏈接)

          訪問頁面自動發(fā)起請求 第三方默認(rèn)給A發(fā)起請求


          防御:

          • Get 請求不對數(shù)據(jù)進行修改

          • 不讓第三方網(wǎng)站訪問到用戶 Cookie

          • 阻止第三方網(wǎng)站請求接口

          • 請求時附帶驗證信息,比如驗證碼或者 Token


          http 和 websocket 的區(qū)別

          相同點主要

          1. 都是基于TCP的應(yīng)用層協(xié)議;
          2. 都使用Request/Response模型進行連接的建立;
          3. 在連接的建立過程中對錯誤的處理方式相同,在這個階段WS可能返回和HTTP相同的返回碼;
          4. 都可以在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。

          不同之處

          1. WS使用HTTP來建立連接,但是定義了一系列新的header域,這些域在HTTP中并不會使用;
          2. WS的連接不能通過中間人來轉(zhuǎn)發(fā),它必須是一個直接連接;
          3. WS連接建立之后,通信雙方都可以在任何時刻向另一方發(fā)送數(shù)據(jù);
          4. WS連接建立之后,數(shù)據(jù)的傳輸使用幀來傳遞,不再需要Request消息;
          5. WS的數(shù)據(jù)幀有序。

          七、webpack

          一、Webpack 是什么

          Webpack 是一個現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) Webpack 處理應(yīng)用程序時,它會遞歸地構(gòu)建一個依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle。核心概念:

          • 入口(entry)

          ? ? 其指示 Webpack 應(yīng)該用哪個模塊,來作為構(gòu)建其內(nèi)部依賴圖的開始,進入入口起點后,Webpack 會找出有哪些模塊和庫是入口起點(直接和間接)依賴的。每個依賴項隨即被處理,最后輸出到稱之為 bundles 的文件中。

          • 輸出(output)

          ? ? output 屬性告訴 Webpack 在哪里輸出它所創(chuàng)建的bundles,以及如何命名這些文件,默認(rèn)值為 ./dist。基本上,整個應(yīng)用程序結(jié)構(gòu),都會被編譯到你指定的輸出路徑的文件夾中。

          • module

          ? ? 模塊,在 Webpack 里一切皆模塊,在Webpack中,CSS、HTML、js、靜態(tài)資源文件等都可以視作模塊,Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊,一 個模塊對應(yīng)著一個文件。

          • Chunk

          ? ? 代碼塊,一個 Chunk 由多個模塊組合而成,用于代碼合并與分割

          • loader

          ? ? loader 讓 Webpack 能夠去處理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。loader 可以將所有類型的文件轉(zhuǎn)換為 Webpack 能夠處理的有效模塊,然后你就可以利用 Webpack 的打包能力,對它們進行處理。本質(zhì)上,Webpack loader 將所有類型的文件,轉(zhuǎn)換為應(yīng)用程序的依賴圖(和最終的 bundle)可以直接引用的模塊

          • 插件(Plugins)

          ? ? plugin 用來擴展Webpack的功能,其通過構(gòu)建流程的特定時機注入鉤子實現(xiàn)的,插件接口功能極其強大,給Webpack帶來很大的靈活性。

          二、Webpack 有什么特點

          模塊化,壓縮,打包 具體作用:

          • 搭建開發(fā)環(huán)境開啟服務(wù)器,監(jiān)視文件改動,熱更新
          • 通過建立依賴圖把模塊打包成一個或者多個chuck。
          • 通過loader 把將sass/less、圖片、vue等文件轉(zhuǎn)成Webpack可以識別的格式的文件
          • 能夠通過插件對資源進行模塊分離,壓縮,整合等


          三、webapck 打包流程

          詳細(xì)流程:

          1. 初始化參數(shù):從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù)。
          2. 開始編譯:用上一步得到的參數(shù)初始化 Compiler 對象,加載所有配置的插件,執(zhí)行對象的 run 方法開始執(zhí)行編譯。
          3. 確定入口:根據(jù)配置中的 entry 找出所有的入口文件。
          4. 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理。
          5. 完成模塊編譯:在經(jīng)過第 4 步使用 Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系。
          6. 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk轉(zhuǎn)換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機會。
          7. 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)

          簡單流程:

          1. 入口文件開始分析:

          • 哪些依賴文件
          • 轉(zhuǎn)換代碼


          1. 遞歸分析依賴代碼

          • 哪些依賴文件
          • 轉(zhuǎn)換代碼
          1. 生成瀏覽器可以識別執(zhí)行的bundle文件


          四、Webapck 打包原理

          1. 通過fs.readFileSync讀取入口文件,然后通過@babel/parser獲取ast抽象語法樹,借助@babel/core和 @babel/preset-env,把ast語法樹轉(zhuǎn)換成合適的代碼最后輸出一個文件對象,下面舉個栗子:

          打包入口文件為index.js:

          import?fn1?from?'./a.js'
          fn1()

          依賴文件a.js

          const?fn1?=()=>{
          ??console.log('1111111111')
          }
          export?default?fn1

          生成文件對象

          ?{
          ???filename:'index.js,'?//?文件路徑
          ???dependencies:'./a.js',//依賴文件
          ?? code:'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....'?//?代碼
          ??}

          然后遞歸dependencies,最后生成:

          ?[{
          ???filename:'index.js,'?//?文件路徑
          ???dependencies:'./a.js',//依賴文件
          ?? code:'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....'?//?代碼
          ??},{
          ???filename:'a.js,'?//?文件路徑
          ???dependencies:{},
          ???code:"\n\nObject.defineProperty(exports,?"__esModule",?({\n????value:?true\n}));...
          ??}
          ]
          ?

          轉(zhuǎn)換格式

          ?{
          ???'index.js':'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....',
          ???'a.js':'?'\n\nObject.defineProperty(exports,?"__esModule",?({\n????value:?true\n}));...'
          ??}

          1. 由于生成的代碼還包含了瀏覽器無法識別 require 函數(shù),所以實現(xiàn)了一個__webpack_require__替換require來實現(xiàn)模塊化,通過自執(zhí)行函數(shù)傳入index文件對象。流程是先通過eval函數(shù)運行index.js的代碼,當(dāng)遇到__webpack_require__時通過__webpack_modules__字典對象獲取 a.js 的代碼并且通過eval運行其代碼。

          詳細(xì)看下圖:

          a79406305326cf921aad759c2580f0d3.webp

          五、常見優(yōu)化手段

          • MiniCssExtractPlugin插件:對CSS進行分離和壓縮
          • happypack插件:HappyPack開啟多個線程打包資源文件
          • DllPlugin、DllReferencePlugin插件:DllPlugin通過配置Webpack.dll.conf.js把第三方庫:vue、vuex、element-ui等打包到一個bundle的dll文件里面,同時會生成一個名為 manifest.json映射文件,最后使用 DllReferencePlugin檢測manifest.json映射,過濾掉已經(jīng)存在映射的包,避免再次打包進bundle.js。
          • ParallelUglifyPlugin插件:開啟多個子進程壓縮輸出的 JS 代碼(Webpack4.0 默認(rèn)使用了 TerserWebpackPlugin,默認(rèn)就開啟了多進程和緩存)
          • optimization.splitChunks:抽離公共文件
          • 其他:exclude/include配置、externals配置、使用cache-loader等

          六、Webpack 常見面試題

          1. 熱更新原理

          ? ? 監(jiān)聽文件變動,通過websocket協(xié)議自動刷新網(wǎng)頁(詳細(xì)內(nèi)容還沒有深入研究)

          1. loader與plugin的區(qū)別

          loader:loader是一個轉(zhuǎn)換器是在 import 或"加載"模塊時預(yù)處理文件,將A語言轉(zhuǎn)成B語言,如 TypeScript轉(zhuǎn)換為 JavaScript,less轉(zhuǎn)成CSS,單純的文件轉(zhuǎn)換成瀏覽器可以識別的文件。
          plugin:插件是一個擴展器,目的在于解決 loader 無法實現(xiàn)的其他事。
          1. 常用的loader和常見plugin有哪些

          loader:

          • babel-loader:把 ES6 轉(zhuǎn)換成 ES5
          • less-loader:將less代碼轉(zhuǎn)換成CSS
          • css-loader:加載 CSS,支持模塊化、壓縮、文件導(dǎo)入等特性
          • style-loader:把 CSS 代碼注入到 JavaScript 中,通過 DOM 操作去加載 CSS
          • eslint-loader:通過 ESLint 檢查 JavaScript 代碼
          • vue-loader:加載 Vue.js 單文件組件
          • cache-loader: 可以在一些性能開銷較大的 Loader 之前添加,目的是將結(jié)果緩存到磁盤里
          • file-loader:把文件輸出到一個文件夾中,在代碼中通過相對 URL 去引用輸出的文件 (處理圖片和字體)
          • url-loader:與 file-loader 類似,區(qū)別是用戶可以設(shè)置一個閾值,大于閾值時返回其 publicPath,小于閾值時返回文件 base64 形式編碼 (處理圖片和字體)


          plugin:

          • CopyWebpackPlugin:將單個文件或整個目錄復(fù)制到構(gòu)建目錄
          • HtmlWebapckPlugin:簡單創(chuàng)建 HTML 文件,用于服務(wù)器訪問
          • ParallelUglifyPlugin: 多進程執(zhí)行代碼壓縮,提升構(gòu)建速度
          • MiniCssExtractPlugin: 分離樣式文件,CSS 提取為獨立文件,支持按需加載 (替代extract-text-Webpack-plugin)
          1. 用過哪些可以提高效率的webapck插件

          此答案請參考目錄五、常見優(yōu)化手段


          1. 實現(xiàn)一個簡單的loader

          實現(xiàn)一個替換源碼中字符的loader

          //index.js
          console.log("hello");

          //replaceLoader.js
          module.exports?=?function(source)?{
          ??//?source是源碼
          ??return?source.replace('hello','hello?loader')?
          };

          在配置文件中使用loader

          //需要node模塊path來處理路徑?
          const?path?=?require('path')
          module:?{
          rules:?[?{
          ?????????test:?/\.js$/,
          ?????????use:?path.resolve(__dirname,"./loader/replaceLoader.js")
          }]
          }

          1. Webpack插件原理,如何寫一個插件

          原理:在 Webpack 運行的生命周期中會廣播出許多事件(run、compile、emit等),Plugin 可以監(jiān)聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結(jié)果

          實現(xiàn)一個copy功能的Plugin

          class?CopyrightWebpackPlugin?{
          ??//compiler:webpack實例
          ??apply(compiler)?{
          ????//emit?生成資源文件到輸出目錄之前
          ????compiler.hooks.emit.tapAsync(
          ??????"CopyrightWebpackPlugin",
          ??????(compilation,?cb)?=>?{
          ????????//?assets目錄輸出copyright.txt
          ????????compilation.assets["copyright.txt"]?=?{
          ??????????//?文件內(nèi)容
          ??????????source:?function()?{
          ????????????return?"hello?copy";
          ??????????},
          ??????????//?文件大小
          ??????????size:?function()?{
          ????????????return?20;
          ??????????}
          ????????};
          ????????//?完成之后?走回調(diào),告訴compilation事情結(jié)束
          ????????cb();
          ??????}
          ????);
          ????//?同步的寫法;
          ????compiler.hooks.compile.tap("CopyrightWebpackPlugin",?compilation?=>?{
          ??????console.log("開始了");
          ????});
          ??}
          }
          module.exports?=?CopyrightWebpackPlugin;

          使用CopyrightWebpackPlugin插件

          const?CopyrightWebpackPlugin?=?require("./plugins/copyright-webpack-plugin");
          plugins:?[
          ????new?CopyrightWebpackPlugin()
          ??]
          1. Webpack的require是如何如何查找依賴的

          • 解析相對路徑:查找相對當(dāng)前模塊的路徑下是否有對應(yīng)文件或文件夾是文件則直接加載,是文件夾則繼續(xù)查找文件夾下的 package.json 文件
          有 package.json 文件則按照文件中 main 字段的文件名來查找文件 無 package.json 或者無 main 字段則查找 index.js 文件
          • 解析模塊名:查找當(dāng)前文件目錄下,父級目錄及以上目錄下的 node_modules 文件夾,看是否有對應(yīng)名稱的模塊
          • 解析絕對路徑:直接查找對應(yīng)路徑的文件

          八、性能優(yōu)化

          性能優(yōu)化思路

          1. 性能監(jiān)控:lighthouse 插件 和瀏覽器 performance 工具
          2. 網(wǎng)絡(luò)層面性能優(yōu)化
          • 減少HTTP請求次數(shù)(緩存、本地存儲)
          • 減少HTTP請求文件體積(文件壓縮)
          • 加快HTTP請求速度(CDN)
          1. 渲染層面性能優(yōu)化
          • DOM 優(yōu)化
          • CSS 優(yōu)化
          • JS 優(yōu)化
          • 首屏渲染
          • 服務(wù)端渲染


          網(wǎng)絡(luò)層面性能優(yōu)化

          減少HTTP請求次數(shù)

          1. 瀏覽器緩存
          • Memory Cache
          • HTTP Cache
          • Service Worker Cache
          • Push Cache
          1. 本地存儲
          • Web Storage(容量5-10M)
          • indexdb 運行在瀏覽上的非關(guān)系型數(shù)據(jù)庫(容量>100M)
          • Cookies (容量4KB)


          瀏覽器緩存--Memory Cache
          MemoryCache,是指存在內(nèi)存中的緩存。從優(yōu)先級上來說,它是瀏覽器最先嘗試去命中的一種緩存。從效率上來說,它是響應(yīng)速度最快的一種緩存。Memory Cache一般存儲體積較小的文件。

          瀏覽器緩存--HTTP Cache
          設(shè)置HTTP Header里面緩存相關(guān)的字段,實現(xiàn)HTTP緩存

          • 強緩存:Cache-Control,expires

          Cache-Control 的幾個取值含義:

          private: 僅瀏覽器可以緩存(HTML文件可以設(shè)置避免CDN緩存)
          public: 瀏覽器和代理服務(wù)器都可以緩存
          max-age=xxx: 過期時間
          no-cache: 不進行強緩存
          no-store:禁用任何緩存,每次都會向服務(wù)端發(fā)起新的請求
          在設(shè)置強緩存情況下,請求返回Response Headers Cache-Control的值,如果有max-age=xxx秒,則命中強緩存,如果Cache-Control的值是no-cache,說明沒命中強緩存,走協(xié)商緩存
          • 協(xié)商緩存:Etag/If-None-Match,Last-Modified/If-Modified-Since
          ETag:每個文件有一個,改動文件了就變了,可以看似md5
          Last-Modified:文件的修改時間
          協(xié)商緩存觸發(fā)條件:Cache-Control 的值為 no-cache (不強緩存)或者 max-age 過期了 (強緩存,但總有過期的時候)

          協(xié)商緩存步驟總結(jié):在設(shè)置協(xié)商緩存情況下,在第一次請求的時候,返回的Response Headers會帶有Etag/Last-Modified值。當(dāng)再次請求沒有命中強制緩存的時候,這個時候我們的Request Headers就會攜帶If-None-Match/If-Modified-Since字段,它們的值就是我們第一次請求返回給我們的Etag/Last-Modified值。服務(wù)端再拿Etag和If-None-Match,Last-Modified和If-Modified-Since來進行比較較,如果相同(命中就直接使 緩存),返回304,瀏覽器讀取本地緩存,如果不同(沒有命中)則返回200,再重新從服務(wù)端拉取新的資源。?

          瀏覽器緩存-Service Worker Cache

          Service Worker 是一種獨立于主線程之外的 Javascript 線程。它脫離于瀏覽器窗體,因此無法直接訪問 DOM。這樣獨立的個性使得 Service Worker 的“個人行為”無法干擾頁面的性能,這個“幕后工作者”可以幫我們實現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能。我們借助 Service worker 實現(xiàn)的離線緩存就稱為Service Worker Cache ,其實現(xiàn)基于HTTPS。


          本地存儲--Web Storage

          Web Storage 又分Local Storage 和 Session Storage,特色容量大5-10M它們的區(qū)別是Local Storage 永久緩存除非手動刪除,Session Storage瀏覽器窗口關(guān)閉即失效。


          減少HTTP請求文件體積

          1. 圖片優(yōu)化
          • 壓縮圖片
          性能優(yōu)化最有效果主要是圖片壓縮,因為圖片的體積比較大,壓縮圖片效果比較顯著。壓縮工具 tinypng.com/
          • 使用webP
          webP支持透明、支持動態(tài)、體積小,缺點是兼容性不好
          • 大圖盡量用JPG代替PNG
          • 使用 CSS Sprites雪碧圖(HTTP2不需要)
          • 使用iconfont(icon首選)
          • 使用base64(小圖解決方案)
          • 復(fù)雜圖形地圖等使用SVG
          • 圖片懶加載
          1. 資源打包壓縮(webpack打包抽離公共CSS/JS)
          • MiniCssExtractPlugin插件:對CSS進行分離和壓縮
          • optimization.splitChunks:抽離公共JS文件
          • optimization.minimizer :文件壓縮
          1. 服務(wù)端開啟gzip壓縮
          accept-encoding:gzip

          Gzip 壓縮背后的原理,是在一個文本文件中找出一些重復(fù)出現(xiàn)的字符串、臨時替換它們,從而使整個文件變小。根據(jù)這個原理,文件中代碼的重復(fù)率越高,那么壓縮的效率就越高,使用 Gzip 的收益也就越大,反之亦然。開啟Gzip省下了一些傳輸過程中的時間開銷,而其代價是服務(wù)器壓縮的時間開銷和 CPU 開銷(以及瀏覽器解析壓縮文件的開銷)。

          使用CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))

          CDN (Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò))指的是一組分布在各個地區(qū)的服務(wù)器。這些服務(wù)器存儲著數(shù)據(jù)的副本,因此服務(wù)器可以根據(jù)哪些服務(wù)器與用戶距離最近,來滿足數(shù)據(jù)的請求。CDN 提供快速服務(wù),較少受高流量影響,

          CDN 是靜態(tài)資源提速的重要手段。


          渲染層次性能優(yōu)化

          DOM 優(yōu)化

          1.減少DOM操作
          • 使用虛擬DOM
          • 使用事件委托
          • 使用DOM Fragment
          DocumentFragment 接口表示一個沒有父級文件的最小文檔對象。它被當(dāng)做一個輕量版的 Document 使用,用于存儲已排好版的或尚未打理好格式的XML片段。因為 DocumentFragment 不是真實 DOM 樹的一部分,它的變化不會引起 DOM 樹的重新渲染的操作(reflow),且不會導(dǎo)致性能等問題。
          • 緩存DOM結(jié)點
          //?只獲取一次container
          let?container?=?document.getElementById('container')
          for(let?count=0;count<10000;count++){?
          ??container.innerHTML?+=?'<span>我是一個小測試</span>'
          }?
          • 減少使用iframe


          JS 優(yōu)化

          • 使用防抖截流

          //?防抖:定時執(zhí)行
          function?debounce(fn,wait=2000)?{
          ??let?setTime
          ??return?function(...args)?{
          ????if(setTime)?clearTimeout(setTime)
          ?????setTime?=?setTimeout(function()?{
          ???????fn.apply(null,?args)
          ???????clearTimeout(setTime)
          ?????},?2000)
          ??}
          }
          //?節(jié)流:規(guī)定時間內(nèi)只取最后一次執(zhí)行
          ?function?throttle(fn,wait=3000)?{
          ?????????let?start=?new?Date()
          ????????????return?function(...args)?{
          ????????????if(new?Date()-?start>?wait){
          ??????????????start=?new?Date()
          ??????????????console.log('args',?args)
          ??????????????fn.apply(null,?args)
          ????????????}
          ????????}
          ????}??
          • 避免使用全局變量

          • 避免內(nèi)存泄漏

          • 刪除多余代碼

          Tree-Shaking:ES6的mudule 已經(jīng)實現(xiàn)的Tree-Shaking,在Webpack打包中會把冗余模塊剔除掉栗子:

          import?{?add1,?add2?}?from?'./pages'
          console.log(add1)
          //?本栗子add2模塊代碼不會被打包bundle文件


          CSS 優(yōu)化

          1. 有效使用選擇器,避免使用calc表達式、CSS濾鏡
          2. 減少重排和重繪
          • 布局優(yōu)先使用flex
          • 盡量避免使用getBoundingClientRect,getComputedStyle等屬性
          1. 觸發(fā)渲染層(transform: translateZ(0);backface-visibility: hidden;)

          服務(wù)端渲染

          服務(wù)器把需要的組件或頁面渲染成 HTML 字符串,然后把它返回給客戶端。客戶端拿到手的,是可以直接渲染然后呈現(xiàn)給用戶的 HTML 內(nèi)容,不需要為了生成 DOM 內(nèi)容自己再去跑一遍 JS 代碼。

          const?Vue?=?require('vue')
          //?創(chuàng)建?個express應(yīng)?
          const?server?=?require('express')()
          //?提取出renderer實?
          const?renderer?=?require('vue-server-renderer').createRenderer()
          server.get('*',?(req,?res)?=>?{?//?編寫Vue實?(虛擬DOM節(jié)點)?const?app?=?new?Vue({
          ????data:?{
          ??????url:?req.url
          },
          //?編寫模板HTML的內(nèi)容
          template:?`<div>訪問的?URL?是:?{{?url?}}</div>`?})
          //?renderToString?是把Vue實?轉(zhuǎn)化為真實DOM的關(guān)鍵?法?renderer.renderToString(app,?(err,?html)?=>?{
          ????if?(err)?{
          ??????res.status(500).end('Internal?Server?Error')
          ??????return
          }
          //?把渲染出來的真實DOM字符?插?HTML模板中?res.end(`
          ??????<!DOCTYPE?html>
          ??????<html?lang="en">
          ????????<head><title>Hello</title></head>
          ????????<body>${html}</body>
          ??????</html>

          `)?})
          })
          server.listen(8080)


          首屏渲染

          1. 內(nèi)聯(lián)首屏關(guān)鍵CSS
          2. 異步加載CSS(動態(tài)插入)
          3. JS放body 底部,或者異步加載js(defer、async)
          defer、async 都不會阻塞頁面渲染,他們的區(qū)別是async編譯完成立刻執(zhí)行,defer編譯完成同時還要等整個文檔解析完成、DOMContentLoaded 事件即將被觸發(fā)才執(zhí)行
          1. 預(yù)加載 preload
          <link?rel="preload"?href="index.js"?as="script">
          1. 頁面loading動畫
          2. 骨架圖
          使用page-skeleton-webpack-plugin插件

          vue 層面優(yōu)化

          1. 引入生產(chǎn)環(huán)境的 Vue 文件
          2. 使用單文件組件預(yù)編譯模板
          3. 提取組件的 CSS 到單獨到文件
          4. 利用Object.freeze()提升性能
          5. 扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
          6. 組件懶加載
          7. 路由懶加載
          8. 虛擬滾動


          九、Vue

          Vue 雙向綁定原理

          vue.js 是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。

          數(shù)據(jù)劫持

          遍歷 data 屬性通過Object.defineproprety 劫持 setter 和 getter


          vue 依賴收集過程

          1. new wacther 時,通過 pushTarget 把該 Dep.target 指向改 wacther,在 render 執(zhí)行渲染 Vnode 過程中觸發(fā)了 getter

          function?pushTarget?(_target:??Watcher)?{
          ??if?(Dep.target)?targetStack.push(Dep.target)
          ??Dep.target?=?_target
          }

          updateComponent?=?()?=>?{
          ?vm._update(vm._render(),?hydrating)
          }

          new?Watcher(vm,?updateComponent,?noop,?{
          ??before?()?{
          ????if?(vm._isMounted)?{
          ????callHook(vm,?'beforeUpdate')
          ???}
          }},?true?/*?isRenderWatcher?*/)
          1. getter 觸發(fā) dep.depend() 即執(zhí)行 Dep.target.addDep(this) ,即執(zhí)行wacther 里面的 addDep 方法

          //?Dep?類
          depend?()?{
          ?if?(Dep.target)?{?//?Dep.target?=》watcher
          ??Dep.target.addDep(this)?//?this?=》dep
          ?}
          }
          1. dep作為參數(shù)在 addDep 里通過map has 判斷 dep 的 id 是否已經(jīng)存在了 dep,如果沒有就壓入棧 (過濾重復(fù)的dep)

          2. 后執(zhí)行 剛才傳入的 dep 的 addSub 方法收集wacther

          //?Watcher?類
          addDep?(dep:?Dep)?{
          ?const?id?=?dep.id
          ?if?(!this.newDepIds.has(id))?{
          ?this.newDepIds.add(id)
          ?this.newDeps.push(dep)
          ?if?(!this.depIds.has(id))?{?//?防止重復(fù)收集?dep
          ????dep.addSub(this)?//?dep?收集?watcher
          ???}
          }}

          watch 和 computd 的區(qū)別

          • computd 在getter執(zhí)行了之后會緩存

          • computd 適合比較耗性能的計算場景

          • watch 更多是監(jiān)聽,監(jiān)聽某個變化而執(zhí)行回調(diào)

          Vue key 的作用

          key 是給每一個 vnode 的唯一 id,可以依靠 key,更準(zhǔn)確, 更快的拿到 oldVnode 中對應(yīng)的 vnode 節(jié)點。

          • 更準(zhǔn)確

          因為帶 key 就不是就地復(fù)用了,在sameNode函數(shù) a.key === b.key對比中可以避免就地復(fù)用的情況。所以會更加準(zhǔn)確。

          • 更快

          利用 key 的唯一性生成map對象來獲取對應(yīng)節(jié)點,比遍歷方式更快

          不帶 key 時或者以 index 作為 key 時:比如可能不會產(chǎn)生過渡效果,或者在某些節(jié)點有綁定數(shù)據(jù)(表單)狀態(tài),會出現(xiàn)狀態(tài)錯位


          Vue nextTick 原理

          • 它可以在 DOM 更新完畢之后執(zhí)行一個回調(diào),使我們可以操作更新后的 dom
          • 可以監(jiān)聽 dom 的變化就是 h5 新特性的 mutationObserver,但是 Vue 并不是通過監(jiān)聽 dom 變化的方式實現(xiàn)的
          • 而是通過 eventloop 原理,因為 eventloop 的 task 執(zhí)行完后(完成一次事件循環(huán))進行一次 DOM 更新
          • 而完成一個 task 分界點就是 微任務(wù)完成 所以 Vue 首先是用 promise.then 然后就是 mutationObserver。
          • 為了兼容性然后降級到宏任務(wù) setImmediate 和 setTimeout
          //?timerFunc?收集異步?task?事件
          function?flushCallbacks?()?{
          ??pending?=?false
          ??const?copies?=?callbacks.slice(0)
          ??callbacks.length?=?0
          ??for?(let?i?=?0;?i?<?copies.length;?i++)?{
          ????copies[i]()
          ??}
          }
          let?timerFunc

          if?(typeof?Promise?!==?'undefined'?&&?isNative(Promise))?{
          ??const?p?=?Promise.resolve()
          ??timerFunc?=?()?=>?{
          ????p.then(flushCallbacks)
          ????if?(isIOS)?setTimeout(noop)
          ??}
          ??isUsingMicroTask?=?true
          }?else?if?(!isIE?&&?typeof?MutationObserver?!==?'undefined'?&&?(
          ??isNative(MutationObserver)?||
          ??MutationObserver.toString()?===?'[object?MutationObserverConstructor]'
          ))?{
          ??let?counter?=?1
          ??const?observer?=?new?MutationObserver(flushCallbacks)
          ??const?textNode?=?document.createTextNode(String(counter))
          ??observer.observe(textNode,?{
          ????characterData:?true
          ??})
          ??timerFunc?=?()?=>?{
          ????counter?=?(counter?+?1)?%?2
          ????textNode.data?=?String(counter)
          ??}
          ??isUsingMicroTask?=?true
          }?else?if?(typeof?setImmediate?!==?'undefined'?&&?isNative(setImmediate))?{
          ??timerFunc?=?()?=>?{
          ????setImmediate(flushCallbacks)
          ??}
          }?else?{
          ??timerFunc?=?()?=>?{
          ????setTimeout(flushCallbacks,?0)
          ??}
          }

          //?用?callbacks?收集回調(diào)函數(shù)
          export?function?nextTick?(cb?:?Function,?ctx?:?Object)?{
          ??let?_resolve
          ??callbacks.push(()?=>?{
          ????if?(cb)?{
          ??????try?{
          ????????cb.call(ctx)
          ??????}?catch?(e)?{
          ????????handleError(e,?ctx,?'nextTick')
          ??????}
          ????}?else?if?(_resolve)?{
          ??????_resolve(ctx)
          ????}
          ??})
          ??if?(!pending)?{
          ????pending?=?true
          ????timerFunc()
          ??}
          ??//?$flow-disable-line
          ??if?(!cb?&&?typeof?Promise?!==?'undefined')?{
          ????return?new?Promise(resolve?=>?{
          ??????_resolve?=?resolve
          ????})
          ??}
          }

          diff 算法流程(patch)

          ? ? 第一次走 createElm 生成真實 dom,以后通過 sameVnode 判斷同結(jié)點則進行 patchVNode 結(jié)點的 child ,如果新舊結(jié)點都存在則走 updateChildren 流程,不斷通過新舊頭頭、尾尾、交叉 sameVnode 對,都比對不成功 ,則直接拿key去映射表查找,如找到有相同的 key 結(jié)點則進行 sameVnode 對比,成立則又進入下子結(jié)點的patchVNode,直到遍歷完成。

          add8e1d56b6b7924a60aa73a5bb1658a.webp

          new vue() 流程

          06c06bed6e35f392b6980bba2a9e2f43.webp


          Vuex 有什么特點

          ? ? 首先說明 Vuex 是一個專為 Vue 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。Vuex 核心概念重點同步異步實現(xiàn) action mutation


          Vue 組件為什么 data 不能是對象

          Vue 組件可能存在多個實例,如果使用對象形式定義 data,則會導(dǎo)致它們共用一個data對象,那么狀態(tài)變更將會影響所有組件實例,

          mvvm 的理解

          通過指令的方式實現(xiàn) model 和 view 主動數(shù)據(jù)響應(yīng)和view更新

          Vue 有哪些性能優(yōu)化

          • 路由懶加載
          • keep-alive 緩存頁面
          • 用 v-show 復(fù)用dom
          • 長列表純數(shù)據(jù)展示用 Object.freeze 凍結(jié)
          • 長列表大數(shù)據(jù)用虛擬滾動
          • 事件及時銷毀
          • 服務(wù)端渲染 ssr


          服務(wù)端渲染原理

          在客戶端請求服務(wù)器的時候,服務(wù)器到數(shù)據(jù)庫中獲取到相關(guān)的數(shù)據(jù),并且在服務(wù)器內(nèi)部將Vue組件渲染成HTML,并且將數(shù)據(jù)、HTML一并返回給客戶端,這個在服務(wù)器將數(shù)據(jù)和組件轉(zhuǎn)化為HTML的過程,叫做服務(wù)端渲染SSReed765dc7b3459e7f67c11171132477a.webp

          使用SSR的好處

          352957c310fba4bd570b00df26e8108b.webp

          1. 有利于SEO

          其實就是有利于爬蟲來爬你的頁面,因為部分頁面爬蟲是不支持執(zhí)行JavaScript的,這種不支持執(zhí)行JavaScript的爬蟲抓取到的非SSR的頁面會是一個空的HTML頁面,而有了SSR以后,這些爬蟲就可以獲取到完整的HTML結(jié)構(gòu)的數(shù)據(jù),進而收錄到搜索引擎中。
          1. 白屏?xí)r間更短

          相對于客戶端渲染,服務(wù)端渲染在瀏覽器請求URL之后已經(jīng)得到了一個帶有數(shù)據(jù)的HTML文本,瀏覽器只需要解析HTML,直接構(gòu)建DOM樹就可以。而客戶端渲染,需要先得到一個空的HTML頁面,這個時候頁面已經(jīng)進入白屏,之后還需要經(jīng)過加載并執(zhí)行JavaScript、請求后端服務(wù)器獲取數(shù)據(jù)、JavaScript 渲染頁面幾個過程才可以看到最后的頁面。特別是在復(fù)雜應(yīng)用中,由于需要加載 JavaScript 腳本,越是復(fù)雜的應(yīng)用,需要加載的 JavaScript 腳本就越多、越大,這會導(dǎo)致應(yīng)用的首屏加載時間非常長,進而降低了體驗感。


          Vue3 有什么新特性

          • 數(shù)據(jù)劫持:用 proxy 做代理
          • 虛擬 dom 重構(gòu):v2會把靜態(tài)結(jié)點轉(zhuǎn)成vdom,新只會構(gòu)造動態(tài)結(jié)點的vdom
          • 將大多數(shù)全局API和內(nèi)部組件移至ES模塊導(dǎo)出,tree-shaking更友好
          • 支持了 ts
          • Composition api:新增 setup 函數(shù)有利于代碼復(fù)用(替代mixin,mixin會容易存在命名沖突)


          Vue2 響應(yīng)式弊端

          ? ? 響應(yīng)化過程需要遞歸遍歷,消耗較大新加或刪除屬性無法監(jiān)聽數(shù)組響應(yīng)化需要額外實現(xiàn)Map、Set、Class等無法響應(yīng)式修改語法有限制


          生命周期2.x與Composition之間的映射關(guān)系

          • beforeCreate -> use setup()
          • created -> use setup()
          • beforeMount -> onBeforeMount
          • mounted -> onMounted
          • beforeUpdate -> onBeforeUpdate
          • updated -> onUpdated
          • beforeDestroy -> onBeforeUnmount
          • destroyed -> onUnmounted
          • errorCaptured -> onErrorCaptured

          Vue 組件通信

          props★★(父傳子)emit/emit/emit/on★★事件總線(跨層級通信)vuex★★★(狀態(tài)管理常用皆可)優(yōu)點:一次存儲數(shù)據(jù),所有頁面都可訪問parent/parent/parent/children(父=子項目中不建議使用)缺點:不可跨層級attrs/attrs/attrs/listeners(皆可如果搞不明白不建議和面試官說這一種)provide/inject★★★(高階用法=推薦使用)優(yōu)點:使用簡單 缺點:不是響應(yīng)式


          v-model vs .sync

          區(qū)別:

          一個組件可以多個屬性用.sync修飾符,可以同時"雙向綁定多個“prop”,而并不像v-model那樣,一個組件只能有一個

          使用場景:

          v-model針對更多的是最終操作結(jié)果,是雙向綁定的結(jié)果,是value,是一種change操作

          sync針對更多的是各種各樣的狀態(tài),是狀態(tài)的互相傳遞,是status,是一種update操作

          v-model 可以在只需子組件更新父組件的場景使用如:

          //?父組件接收
          <CreativityGroup?:planTemplateModel.sync="planParams"/>?
          ?//?子組件傳值
          @Watch('formModle',?{?deep:?true?})
          ??formModleChange(nVal)?{
          ????this.$emit('input',?nVal)
          ??}

          v-model vs .sync 使用例子

          <template>
          ??<div>
          ????my?myParam?{{?value?}}<br?/>
          ????paramsync?{{?paramsync?}}
          ????<button?type="button"?@click="change">change?my</button>
          ??</div>

          </template>

          <script>
          export?default?{
          ??props:?{
          ????value:?{
          ??????type:?String,
          ??????default:?""
          ????},
          ????paramsync:?{
          ??????type:?Number,
          ??????default:?0
          ????}
          ??},
          ??computed:?{
          ????value1:?{
          ??????set(val)?{
          ????????this.$emit("input",?val);
          ??????},
          ??????get()?{
          ????????return?this.value;
          ??????}
          ????}
          ??},
          ??methods:?{
          ????change()?{
          ??????this.value1?=?"rtrtr";
          ??????console.log("this.value",?this.value);
          ????//???this.paramsync?=?78;
          ????//?this.$emit('input','更新之后')
          ??????this.$emit("update:paramsync",5555);
          ????}
          ??}
          };
          </
          script>

          Vue 生命周期

          beforeCreate:在實例初始化之后,數(shù)據(jù)觀測(data observe)和event/watcher事件配置之前被調(diào)用,這時無法訪問data及props等數(shù)據(jù);


          created:在實例創(chuàng)建完成后被立即調(diào)用,此時實例已完成數(shù)據(jù)觀測(data observer),屬性和方法的運算,watch/event事件回調(diào),掛載階段還沒開始,$el尚不可用。


          beforemount: 在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用。


          mounted:實例被掛載后調(diào)用,這時el被新創(chuàng)建的vm.el替換,若根實例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時vm.el替換,若根實例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時vm.el替換,若根實例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時vm.el也在文檔內(nèi)。注意mounted不會保證所有子組件一起掛載。


          beforeupdata:數(shù)據(jù)更新時調(diào)用,發(fā)生在虛擬dom打補丁前,這時適合在更新前訪問現(xiàn)有dom,如手動移除已添加的事件監(jiān)聽器。


          updated:在數(shù)據(jù)變更導(dǎo)致的虛擬dom重新渲染和打補丁后,調(diào)用該鉤子。當(dāng)這個鉤子被調(diào)用時,組件dom已更新,可執(zhí)行依賴于dom的操作。多數(shù)情況下應(yīng)在此期間更改狀態(tài)。如需改變,最好使用watcher或計算屬性取代。注意updated不會保證所有的子組件都能一起被重繪。


          beforedestory:在實例銷毀之前調(diào)用。在這時,實例仍可用。


          destroyed:實例銷毀后調(diào)用,這時 Vue 實例的所有指令都被解綁,所有事件監(jiān)聽器被移除,所有子實例也被銷毀。


          Vue compile 過程

          編譯過程整體分為解析、優(yōu)化和生成


          解析 - parse

          解析器將模板解析為抽象語法樹,基于AST可以做優(yōu)化或者代碼生成工作

          優(yōu)化- optimize

          優(yōu)化器的作用是在AST中找出靜態(tài)子樹并打上標(biāo)記。靜態(tài)子樹是在AST中永遠(yuǎn)不變的節(jié)點,如純文本節(jié)點。標(biāo)記靜態(tài)子樹的好處:每次重新渲染,不需要為靜態(tài)子樹創(chuàng)建新節(jié)點虛擬DOM中patch時,可以跳過靜態(tài)子樹

          代碼生成- generate

          將AST轉(zhuǎn)換成渲染函數(shù)中的內(nèi)容,即代碼字符串。

          Vue2 vs Vue3

          Vue2 響應(yīng)式弊端:響應(yīng)化過程需要遞歸遍歷,消耗較大新加或刪除屬性無法監(jiān)聽數(shù)組響應(yīng)化需要額外實現(xiàn)Map、Set、Class等無法響應(yīng)式修改語法有限制

          十、React

          React 是什么?

          用于構(gòu)建界面的 javascript 庫

          特點:

          • 聲明式

          • 組件式

          優(yōu)點:

          • 開發(fā)團隊和社區(qū)非常強大

          • api 簡潔

          缺點:

          • 沒有官方系統(tǒng)解決方案,選型成本高

          • 過于靈活,對代碼設(shè)計要求高

          jsx 是 React.createElement 的語法糖


          React 的 class 組件和函數(shù)組件的區(qū)別

          相同:都可以接收 props 并返回 react 對象

          不同:

          • 編程思想和內(nèi)存:類組件需要創(chuàng)建實例面向?qū)ο缶幊蹋鼤4鎸嵗枰欢ǖ膬?nèi)存開銷,而函數(shù)組件面向函數(shù)式編程,可節(jié)約內(nèi)存

          • 可測試性:函數(shù)式更利用編寫單元測試

          • 捕獲特性:函數(shù)組件具有值捕獲特性(只能得到渲染前的值)

          • 狀態(tài):class 組件定義定義狀態(tài),函數(shù)式需要使用 useState

          • 生命周期:class 組件有完整的生命周期,而組件沒有,可以用useEffect 實現(xiàn)類生命周期功能

          • 邏輯復(fù)用:類組件通過繼承或者高階組件實現(xiàn)邏輯復(fù)用,函數(shù)組件通過自定義組件實現(xiàn)復(fù)用

          • 跳過更新:類組件可以通過shouldComponents 和 PureComponents(淺比較,深比較可以用immer) 來跳過更新,函數(shù)組件react.memo 跳過更新

          • 發(fā)展前景:函數(shù)組件將成為主流,因為他更好屏蔽this問題,和復(fù)用邏輯,更好的適合時間分片和并發(fā)渲染

          React 設(shè)計理念

          • 跨平臺渲染=>虛擬dom

          • 快速響應(yīng)=>異步可中斷(fiber)+增量更新


          fiber

          fiber 是一個執(zhí)行單元,每次執(zhí)行完成一個執(zhí)行單元,react 會檢測當(dāng)前幀還剩多少時間,如果沒有時間就將控制器讓出去

          fiber 是一種數(shù)據(jù)結(jié)構(gòu)

          • react 目前的做法使用鏈表,每個vdom結(jié)點內(nèi)部表示為一個fider

          • 從頂點開始遍歷

          • 如果有第一個兒子,則先遍歷第一個兒子

          • 如果沒有第一個兒子,則標(biāo)志此結(jié)點遍歷完成

          • 如果有弟弟則遍歷弟弟

          • 如果沒有弟弟,則返回父節(jié)點標(biāo)志父節(jié)點遍歷完成,如果有叔叔則遍歷叔叔

          • 沒有叔叔則遍歷結(jié)束

          (兒子=〉弟弟=〉叔叔)

          494cb72562b181713a5dc321e27c5ffc.webp

          fiber 出現(xiàn)背景

          React15 架構(gòu)不能支撐異步更新,當(dāng)渲染出現(xiàn)大量的組件遞歸時間超過 16.7ms 就會出現(xiàn)卡幀

          React16 架構(gòu)可以分為三層:

          • Scheduler(調(diào)度器類似 requestIdleCallback 功能)— 調(diào)度任務(wù)的優(yōu)先級,高優(yōu)任務(wù)優(yōu)先進入Reconciler

          • Reconciler(協(xié)調(diào)器)— 負(fù)責(zé)找出變化的組件

          • Renderer(渲染器)— 負(fù)責(zé)將變化的組件渲染到頁面上


          react 渲染過程分為三步驟

          • 調(diào)度

          • 調(diào)和

          • 提交(不可暫停)

          fe3a0f055a4103a1b74d48bf526ff26c.webp

          fiber 實現(xiàn)關(guān)鍵原理

          • 基于requestIdleCallback,獲取當(dāng)前一幀還有剩余時間deedline,(將在瀏覽器的空閑時段內(nèi)調(diào)用的函數(shù)排隊)

          • 由于兼容性實際是用Messagechannel + requestAnimationFrame 模擬requestIdleCallback

          effect 副作用,表示將要對一個dom 元素進行操作,副作用鏈就是子孫后代,作用倒掛fiber構(gòu)建dom結(jié)點


          fiber 的作用

          • 能夠把可中斷的任務(wù)切片處理。

          • 能夠在父元素與子元素之間交錯處理,以支持 React 中的布局。

          • 能夠在render()中返回多個元素。

          • 更好地支持錯誤邊界。

          原理利用 requestIdleCallback 函數(shù)將在瀏覽器?的空閑時段內(nèi)調(diào)用的函數(shù)排隊。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)?行行后臺和低優(yōu)先級?工作,?而不不會影響延遲關(guān)鍵事件,如動畫和輸?入響應(yīng)。


          fiber 遞歸流程

          function?performUnitOfWork(fiber)?{
          ??//?執(zhí)行beginWork

          ??if?(fiber.child)?{
          ????performUnitOfWork(fiber.child);
          ??}

          ??//?執(zhí)行completeWork

          ??if?(fiber.sibling)?{
          ????performUnitOfWork(fiber.sibling);
          ??}
          }

          React 與 Vue 的區(qū)別

          相同:

          • 都是前端界面實現(xiàn) javascript 庫
          • 都可以實現(xiàn)數(shù)據(jù)驅(qū)動模版更新,而不需直接操作dom,核心都是 vdom
          不同:
          • Vue通過數(shù)據(jù)劫持,當(dāng)數(shù)據(jù)改動時,界面就會自動更新,而React里面需要調(diào)用方法setState。
          • Vue 的更新的顆粒度是當(dāng)前組件,而React除了當(dāng)前組件還包括子組件(會有性能瓶頸)
          • 在設(shè)計上,Vue 數(shù)據(jù)和模版和方法是分開的,而 React 不分開,Vue 會有很多的指令,React 只有 js
          • 從 diff 上,當(dāng) Vue 的className不一致是認(rèn)為不同的結(jié)點,而 React 認(rèn)為是同一個結(jié)點,Vue 有頭頭,尾尾,交叉對比,而 React 沒有。
          • React16 之后版本有 fider 之后可以實現(xiàn)異步切片更新
          • React 難得是它沒有類似vue現(xiàn)成的全家桶技術(shù)棧,需要自己去選擇和衡量,這就需要時間去踩坑,而且 React 周邊使用起來并不太簡潔明朗,需要了解所選周邊的編寫方式和最佳實踐,這就耗費時間和增加入門門檻了
          • React 的生態(tài)比 Vue 完善


          hook

          Hook 是一些可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù),可以使你在函數(shù)組件使用狀態(tài)。


          setState 是異步還是同步

          React的setState本身并不是異步的,是因為其批處理機制給人一種異步的假象。

          【React的更新機制】

          生命周期函數(shù)和合成事件中:

          1. 無論調(diào)用多少次setState,都不會立即執(zhí)行更新。而是將要更新的state存入'_pendingStateQuene',將要更新的組件存入'dirtyComponent';

          2. 當(dāng)根組件didMount后,批處理機制更新為false。此時再取出'_pendingStateQuene'和'dirtyComponent'中的state和組件進行合并更新;

          原生事件和異步代碼中:

          1. 原生事件(原生js綁定的事件)不會觸發(fā)react的批處理機制,因而調(diào)用setState會直接更新;

          2. 異步代碼中調(diào)用setState,由于js的異步處理機制,異步代碼會暫存,等待同步代碼執(zhí)行完畢再執(zhí)行,此時react的批處理機制已經(jīng)結(jié)束,因而直接更新。

          總結(jié):
          react會表現(xiàn)出同步和異步(setTimeout/setInterval)的現(xiàn)象,但本質(zhì)上是同步的,是其批處理機制造成了一種異步的假象。(其實完全可以在開發(fā)過程中,在合成事件和生命周期函數(shù)里,完全可以將其視為異步)


          React 合成事件

          16 事件流(沒有分別注冊捕獲和冒泡事件 bug )

          bac9ffbfa5b0723795939aca774990c7.webp

          3b3fe8297215bb89d7045bb81c8ba17d.webp

          bug :點擊彈窗沒反應(yīng)

          e01f379fb9a1ae2af080a4a8513d0c83.webp

          原因:點擊事件直接冒泡到 document 原生事件了,state 變成了 false

          解決:

          68d7a55a44dd045903a4574a2155bfa9.webp

          原理:

          39b8733457c1f91c6e4a7abfb02a9409.webp

          17 事件流

          事件委托不再是 document 而是 掛載點容器,可以讓一個頁面可以使用多個 React 版本

          c8b4a3f52c905a71e9decabb255a7e66.webp

          不掛載到 document ,結(jié)點變成父子關(guān)系所以,stopPropagation(阻止冒泡) 生效


          redux

          什么時候使用redux:

          • 某個組件的狀態(tài),需要共享

          • 某個狀態(tài)需要在任何地方都可以拿到

          • 一個組件需要改變?nèi)譅顟B(tài)

          • 一個組件需要改變另一個組件的狀態(tài)? ?

          原文:https://juejin.cn/user/2524134427068199/posts

          十一、總結(jié)

          ? ? 在我們閱讀完官方文檔后,我們一定會進行更深層次的學(xué)習(xí),比如看下框架底層是如何運行的,以及源碼的閱讀。? ? 這里廣東靚仔給下一些小建議:
          • 在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計理念、源碼分層設(shè)計
          • 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
          • 借助框架的調(diào)用棧來進行源碼的閱讀,通過這個執(zhí)行流程,我們就完整的對源碼進行了一個初步的了解
          • 接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍

          關(guān)注我,一起攜手進階

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~

          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲高清无码视频在线免费观看 | 人人摸在线精品视频 | 久久久一区二区 | 操屄簧片一级毛片 | 人妻无码精品 |