核心前端體系知識(shí)點(diǎn)
本文適合想對(duì)前端體系知識(shí)進(jìn)行梳理的小伙伴閱讀。
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
一、前言
? ? 最近有小伙伴私聊廣東靚仔,快到年尾了,想充實(shí)下自己的前端知識(shí)體系,但是沒(méi)有個(gè)方向。我們都知道前端知識(shí)縱橫交錯(cuò),知識(shí)體系龐大,相信有不少小伙伴無(wú)從下手、囫圇吞棗。
? ? 廣東靚仔收集了一些比較重要的知識(shí)點(diǎn),下面我們展開一起來(lái)看看。

二、HTML
1.語(yǔ)義化
? ? 所謂,語(yǔ)義化的標(biāo)簽,說(shuō)明讓標(biāo)簽有自己的含義。也是近十年。最典型的栗子就是header,footer等,它可以讓你在沒(méi)有樣式的情況下,就大概能想到,他就是個(gè)頭部或者底部。他存在的意義,就是讓前端開發(fā)人員,在開發(fā)過(guò)程中,更容易去閱讀代碼,以及明白這些代碼的意義。好處:
- 能夠更好的展示內(nèi)容結(jié)構(gòu)
- 便于團(tuán)隊(duì)的維護(hù)與開發(fā)
- 有利于SEO
- 爬蟲可以分析每個(gè)關(guān)鍵詞的權(quán)重
- 方便其他設(shè)備解析?(如屏幕閱讀器)
2.SEO
? ? 身為前端,我們不得不知道的SEO,這涉及到公司的網(wǎng)站推廣。SEO,中文稱搜索引擎優(yōu)化,一種利用搜索引擎的搜索規(guī)則來(lái)提高目前網(wǎng)站在有關(guān)搜索引擎內(nèi)的自然排名的方式。他的實(shí)現(xiàn)原來(lái)分別為,頁(yè)面抓取,分析入庫(kù),檢索排序。如何優(yōu)化SEO:
- title、description、keywords
- 利用好html語(yǔ)義化
- 重要的東西放前面
- 少用iframe
3.doctype
? ? 前端經(jīng)常在html頭部看到DOCTYPE的聲明,一般常位于文檔的第一行。那么他的作用是什么,可能對(duì)新的瀏覽器或者新的網(wǎng)站暫無(wú)什么影響,但是相對(duì)古老的瀏覽器或者是網(wǎng)站,可能會(huì)出現(xiàn)不同。因?yàn)闉g覽器有標(biāo)準(zhǔn)模式與兼容模式,差異相對(duì)比較大。標(biāo)準(zhǔn)模式的渲染方式和 JS 引擎的解析方式都是以該瀏覽器支持的最高標(biāo)準(zhǔn)運(yùn)行。兼容模式中,頁(yè)面以寬松的向后兼容的方式顯示 ,模擬老式瀏覽器的行為以防止站點(diǎn)無(wú)法工作。而DOCTYPE的存在,就是為了聲明,該頁(yè)面使用標(biāo)準(zhǔn)模式。不聲明,可能一些舊的網(wǎng)站會(huì)出現(xiàn)兼容模式。4.link與@import
link與import , 本質(zhì)使用上,我們都是用他來(lái)引入css。
區(qū)別:
- link是一種引入資源的標(biāo)簽,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的資源,包括圖片,RSS等。
- 加載順序上也有一些差異。link引用的CSS會(huì)同時(shí)被加載。import引用的CSS會(huì)等到頁(yè)面全部被下載完再加載。
- 兼容性的差別。link無(wú)任何兼容問(wèn)題,import兼容IE5以上。(當(dāng)然,IE5估計(jì)也找不到了)
- 動(dòng)態(tài)引入樣式 link可以后期引入樣式,而import是不可以后期引入的,只能初始化頁(yè)面之前引入。
- 復(fù)用率的問(wèn)題 import可以復(fù)用之前的css文件,而link只能一次引用一個(gè)文件。當(dāng)然,import復(fù)用文件時(shí),在瀏覽器實(shí)際上是加載了多個(gè)文件,會(huì)有多個(gè)請(qǐng)求。而每一個(gè)link只是一個(gè)http請(qǐng)求。
5.async與defer
? ? 首先這兩個(gè)東西為什么而存在的問(wèn)題。在日漸復(fù)雜的前端,異常已經(jīng)是程序的一部分。如果出現(xiàn)一些小問(wèn)題,或者服務(wù)器加載上出現(xiàn)延遲。而我們默認(rèn)的引入的script腳本,會(huì)阻塞后續(xù)的DOM渲染。一旦沒(méi)有部分異常無(wú)法及時(shí)加載完成,那么我們的頁(yè)面因?yàn)樽枞麊?wèn)題,將整個(gè)白屏。也許我們可以保證自己服務(wù)器的正常,但是你決定保證不了第三方服務(wù)器的正常,于是引入了async和defer來(lái)優(yōu)化這個(gè)問(wèn)題。再來(lái)談?wù)剆cript的默認(rèn),async,defer的之前的差異。默認(rèn)情況下:瀏覽器會(huì)立即加載并執(zhí)行指定的腳本。指定的腳本,指在script標(biāo)簽之上的腳本。所以,如果script放在header中,而對(duì)應(yīng)的文件還未加載完成,會(huì)形成阻塞。所以這就是現(xiàn)在很多頁(yè)面,都會(huì)使用默認(rèn)且把scipt放在頁(yè)面結(jié)尾的原因。async情況下:async ,加載和渲染后續(xù)文檔元素的過(guò)程將和 script.js 的加載與執(zhí)行并行進(jìn)行(異步)。async是亂序的。defer情況下:defer,加載后續(xù)文檔元素的過(guò)程將和 script.js 的加載并行進(jìn)行(異步),但是 script.js 的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。defer是順序執(zhí)行。此外,async跟defer,不支持或者不兼容IE9一下瀏覽器,總體來(lái)說(shuō),script放最下方靠譜一些。6.捕捉,冒泡與委托
? ? 適合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。執(zhí)行順序:捕捉--》目標(biāo)--》冒泡event.stopPropagation()阻止事件的傳遞行為. event.preventDefault();阻止默認(rèn)行為,比如阻止a的href優(yōu)點(diǎn):
- 減少事件注冊(cè),節(jié)省內(nèi)存。例如上面代碼,只指定 父元素的處理程序,即可管理所有所有子元素的“click”事件;
- 簡(jiǎn)化了dom節(jié)點(diǎn)更新時(shí),相應(yīng)事件的更新
缺點(diǎn):
- 利用事件冒泡的原理,不支持不冒泡的事件;
- 層級(jí)過(guò)多,冒泡過(guò)程中,可能會(huì)被某層阻止掉;
- 理論上委托會(huì)導(dǎo)致瀏覽器頻繁調(diào)用處理函數(shù),雖然很可能不需要處理。所以建議就近委托,比如在ol上代理li,而不是在document上代理li。
- 把所有事件都用代理就可能會(huì)出現(xiàn)事件誤判。比如,在document中代理了所有button的click事件,另外的人在引用改js時(shí),可能不知道,造成單擊button觸發(fā)了兩個(gè)click事件。
7.漸進(jìn)增強(qiáng)與優(yōu)雅降級(jí)
? ? 漸進(jìn)增強(qiáng):針對(duì)低版本瀏覽器進(jìn)行構(gòu)建頁(yè)面,保證最基本的功能,然后再針對(duì)高級(jí)瀏覽器進(jìn)行效果、交互等改進(jìn),達(dá)到更好的用戶體驗(yàn)。優(yōu)雅降級(jí):一開始就構(gòu)建完整的功能,然后再針對(duì)低版本瀏覽器進(jìn)行兼容。三、js函數(shù)根基
1.函數(shù)的獨(dú)特之處? ? 函數(shù)是第一型對(duì)象,因?yàn)楹瘮?shù)可以像對(duì)象通過(guò)字面量進(jìn)行創(chuàng)建,賦值變量、數(shù)組,作為函數(shù)的返回值,擁有動(dòng)態(tài)創(chuàng)建并返回賦值的屬性。? ? 函數(shù)最重要的特性是它可以被調(diào)用,而通常都是以異步的方式進(jìn)行調(diào)用,其原理是瀏覽器事件輪詢是單線程的,即每個(gè)事件都是按照在隊(duì)列放放置的順序類執(zhí)行的,就是FIFO(先進(jìn)先出)列表,在任何情況下單線程不可能同時(shí)執(zhí)行兩個(gè)程序,所以必須等待當(dāng)前事件結(jié)束之后才能執(zhí)行另外一個(gè)事件,就是說(shuō)事件執(zhí)行的時(shí)間不可知所以事件處理函數(shù)調(diào)用異步。2.函數(shù)聲明
函數(shù)是使用字面量進(jìn)行聲明從而創(chuàng)建函數(shù)值的,函數(shù)字面量由四個(gè)部分組成
1.function關(guān)鍵詞?
2.可選名稱
3.括號(hào)內(nèi)部,一個(gè)以逗號(hào)分隔開的參數(shù)列表
4.函數(shù)體函數(shù)名是可選的,它只是函數(shù)的引用,匿名函數(shù)就是一個(gè)栗子??梢酝ㄟ^(guò)訪問(wèn)函數(shù)的name屬性獲取函數(shù)名
function?ninja(){}
ninja.name?//ninja另外值得注意的是書上說(shuō)創(chuàng)建一個(gè)匿名函數(shù)并賦值給一個(gè)變量,這個(gè)變量并不是該函數(shù)的name,但是本人發(fā)現(xiàn)在支持ES6語(yǔ)法的chrome下獲取函數(shù)name不是空而是匿名函數(shù)賦值給該變量的變量名。
var?fn?=?function?()?{};
//?ES5
fn.name?//?""
//?ES6
fn.name?//?"fn"也可以通過(guò)屬性訪問(wèn)的方式獲取形參長(zhǎng)度,注意是形參不是實(shí)參,形參和實(shí)參的區(qū)別是:形參是函數(shù)聲明時(shí)定義的參數(shù),而實(shí)參是函數(shù)調(diào)用時(shí)傳給函數(shù)的參數(shù)。下面會(huì)講怎么獲取實(shí)參。
var?fn?=?function?(a,b)?{};
fn.length?//?23.函數(shù)調(diào)用? ??函數(shù)被調(diào)用的時(shí)候到底發(fā)生了什么?事實(shí)上函數(shù)被調(diào)用的方式對(duì)其函數(shù)內(nèi)部代碼執(zhí)行有著巨大的影響。有四種不同的方式進(jìn)行函數(shù)調(diào)用,每個(gè)方式都有細(xì)微的差別。
- 作為一個(gè)函數(shù)調(diào)用,是簡(jiǎn)單的形式
- 作為一個(gè)方法調(diào)用,在對(duì)象上進(jìn)行調(diào)用
- 作為構(gòu)造器調(diào)用,創(chuàng)建一個(gè)對(duì)象
- 通過(guò)apply()和call()方法進(jìn)行調(diào)用
有趣的是在函數(shù)調(diào)用的時(shí)候都會(huì)傳遞兩個(gè)隱式參數(shù):arguments和this,所謂隱式就是這些參數(shù)不會(huì)顯示在函數(shù)簽名里,但是它們會(huì)傳遞給函數(shù)并在函數(shù)作用域內(nèi),在函數(shù)內(nèi)可以進(jìn)行訪問(wèn)和使用。
arguments
arguments是函數(shù)調(diào)用時(shí)傳給函數(shù)的實(shí)際參數(shù)集合,可以用length屬性獲取集合的長(zhǎng)度,但是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對(duì)象
function?ninja(){console.log(this)}
ninja()?//window當(dāng)函數(shù)作為“方法調(diào)用”,是指當(dāng)函數(shù)被賦值給對(duì)象的一個(gè)屬性,并使用引用該函數(shù)的這個(gè)屬性進(jìn)行調(diào)用,this指向該對(duì)象,實(shí)例如下:
var?o={};
o.ninja=function(){?console.log(this)};
o.ninja();?//?o
當(dāng)函數(shù)作為“構(gòu)造器”進(jìn)行調(diào)用時(shí):
- 創(chuàng)建一個(gè)新的對(duì)象
- 將構(gòu)造器函數(shù)作用域賦值給新對(duì)象(因此this指向了這個(gè)新對(duì)象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
- 如果沒(méi)有顯式的返回值,返回新對(duì)象
function?Ninja(){
????this.shulk=function(){console.log(this)}
}
var?Ninja1=new?Ninja();
var?Ninja1=new?Ninja();
Ninja1.shulk()?//Ninja1
Ninja2.shulk()?//Ninja2
使用apply()和call()方法進(jìn)行調(diào)用 在函數(shù)調(diào)用的時(shí)候JavaScript為我們提供一種可以顯式指定任何一個(gè)對(duì)象作為函數(shù)的上下文,使用apply()和call()方法可以實(shí)現(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對(duì)象,call()方法把函數(shù)juggle上下文指定給了ninja2。常見使用apply()、call()方法是在實(shí)現(xiàn)函數(shù)回調(diào)的時(shí)候,比如下面實(shí)現(xiàn)一個(gè)簡(jiǎ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()的功能基本相同,我們?cè)撨x擇哪個(gè)比較好呢?如果在變量里有很多無(wú)關(guān)的值或者是指定為字面量,使用call()則可以直接將其作為參數(shù)列表傳進(jìn)去,但是如果這些參數(shù)已經(jīng)在一個(gè)數(shù)組里了,或者很容易將其收集到數(shù)組里,apply()是更好選擇。
4.async/await 原理
? ? async/await語(yǔ)法糖就是使用Generator函數(shù)+自動(dòng)執(zhí)行器來(lái)運(yùn)作的
5.判斷數(shù)組的方法以及優(yōu)缺點(diǎn)
- Array.isArray(arr) :兼容性不好
- arr?instanceof Array
- arr.proto?=== Array.prototype
- arr.constructor === Array
- Object.prototype.toString.call(arr) === '[object Array]'
6.js腳本加載問(wèn)題,async、defer問(wèn)題
- 如果依賴其他腳本和 DOM 結(jié)果,使用 defer
- 如果與 DOM 和其他腳本依賴不強(qiáng)時(shí),使用 async
7.什么是作用域鏈?
JavaScript 引擎會(huì)沿著“當(dāng)前執(zhí)行上下文–>內(nèi)嵌函數(shù)閉包–> 全局執(zhí)行上下文”的冒泡的方式順序查找變量,就形成了一條作用域鏈
8.this 的指向
- 當(dāng)函數(shù)作為對(duì)象的方法調(diào)用時(shí),函數(shù)中的 this 就是該對(duì)象;
- 當(dāng)函數(shù)被正常調(diào)用時(shí),在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對(duì)象 window;
9.改變 this 指向的方式:
- 用 call、apply、bind設(shè)置
- 通過(guò)對(duì)象調(diào)用方法設(shè)置(指向該對(duì)象)
- 通過(guò)構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對(duì)象)
10. 用過(guò)哪些設(shè)計(jì)模式
11. Javascript垃圾回收
當(dāng)對(duì)象不再被?引用?時(shí)被垃圾回收機(jī)制回收(“對(duì)象有沒(méi)有其他對(duì)象引用到它”)。
內(nèi)存泄露就是不再被需要的內(nèi)存, 由于某種原因, 無(wú)法被釋放
- 引用計(jì)數(shù)法:對(duì)象是否不再需要(限制:循環(huán)引入不能被回收)
- 標(biāo)記清除法:從根開始,找所有從根開始引用的對(duì)象標(biāo)記,然后找這些對(duì)象引用的對(duì)象標(biāo)記,把不能達(dá)到的回收(這個(gè)算法把“對(duì)象是否不再需要”簡(jiǎn)化定義為“對(duì)象是否可以獲得”。)
- 通過(guò)構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對(duì)象)
避免內(nèi)存泄漏:
- 盡少創(chuàng)建全局變量
- 手動(dòng)清除計(jì)時(shí)器
- 少用閉包
- 使用弱引用WeakMap和WeakSet
12. 0.1 + 0.2 === 0.3 嘛?為什么?
計(jì)算機(jī)存儲(chǔ)以二進(jìn)制的方式,而0.1 在二進(jìn)制中是無(wú)限循環(huán)的一些數(shù)字,所以會(huì)出現(xiàn)裁剪,精度丟失會(huì)出現(xiàn),0.100000000000000002 === 0.1,0.200000000000000002 === 0.2 // true 這兩加起來(lái)肯定不等于0.3解決:parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true四、es6
es6新特性
1. Proxy 有什么特點(diǎn)
用于修改某些操作的默認(rèn)行為,(攔截對(duì)象對(duì)其內(nèi)置方法進(jìn)行重載)
可以直接攔截對(duì)象和數(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
可以攔截的對(duì)象:
- get、set、has、apply、construct、ownKeys
- deleteProperty、defineProperty、isExtensible、preventExtensions
- getPrototypeOf、setPrototypeOf、getOwnPropertyDescriptor
實(shí)際用途可以攔截的對(duì)象:
- 設(shè)置對(duì)象的初始值覆蓋undefined
- 緩存封裝是否過(guò)期攔截
2.CommonJs 和 ES6 module 區(qū)別
- 前者支持動(dòng)態(tài)引入即require(${path}/xx.js),后者不支持
- ES6 module 靜態(tài)引入,即編譯時(shí)引入,CommonJs 動(dòng)態(tài)引入,即執(zhí)行時(shí)引入
- 前者是對(duì)模塊的淺拷貝,后者是對(duì)模塊的引用
- 前者可以對(duì)導(dǎo)出對(duì)值賦值(改變指針),而后者不能相當(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 的集合,它僅允許對(duì)象作為鍵,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)它們,便會(huì)將它們與其關(guān)聯(lián)值一同刪除。WeakSet 是類似于 Set 的集合,它僅存儲(chǔ)對(duì)象,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)它們,便會(huì)將其刪除。5. WebAssembly 是什么
WebAssembly 是一種可以使用非 JavaScript 編程語(yǔ)言編寫代碼并且能在瀏覽器上運(yùn)行的技術(shù)方案6.柯里化有什么作用
主要有3個(gè)作用:參數(shù)復(fù)用、提前返回和?延遲執(zhí)行
es6解構(gòu)用法
1.獲取用戶對(duì)象數(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.刪除對(duì)象
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請(qǐng)求設(shè)置預(yù)警
假設(shè) this.$http(url) 返回的對(duì)象正確格式為{code:0,data:{id:'123',name:'tony'} },如data返回為空則拋出異常
?function?toastErr?(){
????alert('俄歐,沒(méi)有獲取到任何數(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è)計(jì)模式
工廠模式
簡(jiǎn)單工廠- 處理變與不變的
工廠模式:將創(chuàng)建對(duì)象的過(guò)程單獨(dú)封裝,實(shí)現(xiàn)無(wú)腦傳參,核心:處理變與不變的
改進(jì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?=?['訂會(huì)議室',?'寫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
????????...
}
改進(jìn)后
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=['寫原型','催進(jìn)度']
???????break;?
?????case?'boss':
???????word=['喝茶','見客戶','談合作']
???????break;?
??}
?return?new?User(name,age,career,word)
}
抽象工廠- 開放封閉原則
簡(jiǎn)單工廠因?yàn)?strong>沒(méi)有遵守開放封閉原則, 暴露一個(gè)很大的缺陷。例如若我們添加管理層一些考評(píng)權(quán)限,難道我們要重新去修改Factory函數(shù)嗎?這樣做會(huì)導(dǎo)致Factory會(huì)變得異常龐大,而且很容易出bug,最后非常難維護(hù)class?MobilePhoneFactory?{
????//?提供操作系統(tǒng)的接口
????createOS(){
????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
????}
????//?提供硬件的接口
????createHardWare(){
????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
????}
}
//?具體工廠繼承自抽象工廠
class?FakeStarFactory?extends?MobilePhoneFactory?{
????createOS()?{
????????//?提供安卓系統(tǒng)實(shí)例
????????return?new?AndroidOS()
????}
????createHardWare()?{
????????//?提供高通硬件實(shí)例
????????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('我會(huì)用安卓的方式去操作硬件')
????}
}
class?AppleOS?extends?OS?{
????controlHardWare()?{
????????console.log('我會(huì)用??的方式去操作硬件')
????}
}
//?定義手機(jī)硬件這類產(chǎn)品的抽象產(chǎn)品類
class?HardWare?{
????//?手機(jī)硬件的共性方法,這里提取了“根據(jù)命令運(yùn)轉(zhuǎn)”這個(gè)共性
????operateByOrder()?{
????????throw?new?Error('抽象產(chǎn)品方法不允許直接調(diào)用,你需要將我重寫!');
????}
}
//?定義具體硬件的具體產(chǎn)品類
class?QualcommHardWare?extends?HardWare?{
????operateByOrder()?{
????????console.log('我會(huì)用高通的方式去運(yùn)轉(zhuǎn)')
????}
}
class?MiWare?extends?HardWare?{
????operateByOrder()?{
????????console.log('我會(huì)用小米的方式去運(yùn)轉(zhuǎn)')
????}
}
//?這是我的手機(jī)
const?myPhone?=?new?FakeStarFactory()
//?讓它擁有操作系統(tǒng)
const?myOS?=?myPhone.createOS()
//?讓它擁有硬件
const?myHardWare?=?myPhone.createHardWare()
//?啟動(dòng)操作系統(tǒng)(輸出‘我會(huì)用安卓的方式去操作硬件’)
myOS.controlHardWare()
//?喚醒硬件(輸出‘我會(huì)用高通的方式去運(yùn)轉(zhuǎn)’)
myHardWare.operateByOrder()
對(duì)原有的系統(tǒng)不會(huì)造成任何潛在影響所謂的“對(duì)拓展開放,對(duì)修改封閉”
單例模式
單例模式 - 保證一個(gè)類只有一個(gè)實(shí)例
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。class?SingleDog?{
?show(){
??console.log('單例方法')
?}
}
let?single1=new?SingleDog()
let?single2=new?SingleDog()
single1===single2?//?false
單例模式要求不管我們嘗試去創(chuàng)建多少次,它都只給你返回第一次所創(chuàng)建的那唯一的一個(gè)實(shí)例。
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
單例實(shí)際應(yīng)用 vuex
Store 存放共享數(shù)據(jù)的唯一數(shù)據(jù)源,要求一個(gè) Vue 實(shí)例只能對(duì)應(yīng)一個(gè) Store,即Vue 實(shí)例(即一個(gè) Vue 應(yīng)用)只會(huì)被 install 一次 Vuex 插件
let?Vue?//?這個(gè)Vue的作用和樓上的instance作用一樣
...
export?function?install?(_Vue)?{
??//?判斷傳入的Vue實(shí)例對(duì)象是否已經(jīng)被install過(guò)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
??}
??//?若沒(méi)有,則為這個(gè)Vue實(shí)例對(duì)象install一個(gè)唯一的Vuex
??Vue?=?_Vue
??//?將Vuex的初始化邏輯寫進(jìn)Vue的鉤子函數(shù)里
??applyMixin(Vue)
}
裝飾器模式
裝飾器模式 - 實(shí)現(xiàn)只添加不修改
拓展彈窗功能
//?定義打開按鈕
class?OpenButton?{
????//?點(diǎn)擊后展示彈框(舊邏輯)
????onClick()?{
????????const?modal?=?new?Modal()
?????modal.style.display?=?'block'
????}
}
//?定義按鈕對(duì)應(yīng)的裝飾器
class?Decorator?{
????//?將按鈕實(shí)例傳入
????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()
????//?此處可以分別嘗試兩個(gè)實(shí)例的onClick方法,驗(yàn)證裝飾器是否生效
????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ù)聲明會(huì)提升
實(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出去的其實(shí)是一個(gè)被包裹后的組件
export?default?TargetComponent
適配器模式
適配器模式 - 兼容就是一把梭
原 ajax 定義
function?Ajax(type,?url,?data,?success,?failed){
????//?創(chuàng)建ajax對(duì)象
????var?xhr?=?null;
????if(window.XMLHttpRequest){
????????xhr?=?new?XMLHttpRequest();
????}?else?{
????????xhr?=?new?ActiveXObject('Microsoft.XMLHTTP')
????}
?
???...(此處省略一系列的業(yè)務(wù)邏輯細(xì)節(jié))
???
???var?type?=?type.toUpperCase();
????
????//?識(shí)別請(qǐng)求類型
????if(type?==?'GET'){
????????if(data){
??????????xhr.open('GET',?url?+?'?'?+?data,?true);?//如果有數(shù)據(jù)就拼接
????????}?
????????//?發(fā)送get請(qǐng)求
????????xhr.send();
?
????}?else?if(type?==?'POST'){
????????xhr.open('POST',?url,?true);
????????//?如果需要像 html 表單那樣 POST 數(shù)據(jù),使用 setRequestHeader()?來(lái)添加 http 頭。
????????xhr.setRequestHeader("Content-type",?"application/x-www-form-urlencoded");
????????//?發(fā)送post請(qǐng)求
????????xhr.send(data);
????}
?
????//?處理返回?cái)?shù)據(jù)
????xhr.onreadystatechange?=?function(){
????????if(xhr.readyState?==?4){
????????????if(xhr.status?==?200){
????????????????success(xhr.responseText);
????????????}?else?{
????????????????if(failed){
????????????????????failed(xhr.status);
????????????????}
????????????}
????????}
????}
}
fetch 請(qǐng)求封裝
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請(qǐng)求體的格式化方法
??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?{
?????????//?實(shí)際的請(qǐng)求全部由新接口發(fā)起
?????????if(type?===?'GET')?{
????????????result?=?await?HttpUtils.get(url)?||?{}
????????}?else?if(type?===?'POST')?{
????????????result?=?await?HttpUtils.post(url,?data)?||?{}
????????}
????????//?假設(shè)請(qǐng)求成功對(duì)應(yīng)的狀態(tài)碼是1
????????result.statusCode?===?1?&&?success???success(result)?:?failed(result.statusCode)
????}?catch(error)?{
????????//?捕捉網(wǎng)絡(luò)錯(cuò)誤
????????if(failed){
????????????failed(error.statusCode);
????????}
????}
}
//?用適配器適配舊的Ajax方法
async?function?Ajax(type,?url,?data,?success,?failed)?{
????await?AjaxAdapter(type,?url,?data,?success,?failed)
}
代理模式
事件代理:點(diǎn)擊子元素,用父元素代理
緩存代理
const?addAll?=?function()?{
????console.log('進(jìn)行了一次新計(jì)算')
????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)化為一個(gè)唯一的入?yún)⒆址?/span>
????????const?args?=?Array.prototype.join.call(arguments,?',')
????????
????????//?檢查本次入?yún)⑹欠裼袑?duì)應(yīng)的計(jì)算結(jié)果
????????if(args?in?resultCache)?{
????????????//?如果有,則返回緩存池里現(xiàn)成的結(jié)果
????????????return?resultCache[args]
????????}
????????return?resultCache[args]?=?addAll(...arguments)
????}
})()
策略模式 - 消除 if-else 能手
//?定義一個(gè)詢價(jià)處理器對(duì)象
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;
??},
};
//?詢價(jià)函數(shù)
function?askPrice(tag,?originPrice)?{
??return?priceProcessor[tag](originPrice)
}
觀察者模式
應(yīng)用例子:需求發(fā)布
流程產(chǎn)品經(jīng)理開群然后拉人(開發(fā))進(jìn)群,需求更新時(shí)通知開發(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
????}
????//?更新需求?同時(shí)通知開發(fā)者
????lilei.setState(prd)
vue 響應(yīng)試原理-觀察者模式
觀察者模式和發(fā)布-訂閱模式的區(qū)別是:發(fā)布-訂閱模式,事件的注冊(cè)和觸發(fā)發(fā)生在獨(dú)立于雙方的第三方平臺(tái)。觀察者模式:發(fā)布者會(huì)直接觸及到訂閱者?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 到頁(yè)面加載完成,發(fā)生了什么?
1、HTTP 請(qǐng)求準(zhǔn)備階段
- 構(gòu)建請(qǐng)求
- 查找緩存
- 準(zhǔn)備 ip 地址和端口
- DNS(域名和ip的映射系統(tǒng)) 域名解析,拿到ip之后找端口,默認(rèn)為80
- 建立tcp鏈接(三次握手)
- 如果是https 還需要建立TLS連接
2、HTTP 發(fā)送請(qǐng)求
- 瀏覽器向服務(wù)端發(fā)起http請(qǐng)求,把請(qǐng)求頭和請(qǐng)求行一起發(fā)送個(gè)服務(wù)器,服務(wù)端解析請(qǐng)求頭如發(fā)現(xiàn)cache-control和etag(if-none-match),if-modified(if-modified-since)字段就會(huì)判斷緩存是否過(guò)期,如果沒(méi)有返回304,否則返回200
3、HTTP 響應(yīng)返回
- 瀏覽器拿到響應(yīng)數(shù)據(jù),首先判斷是否是4XX或者5XX是就報(bào)錯(cuò),如果是3XX就重定向,2XX就開始解析文件,如果是gzip就解壓文件
- TCP斷開連接4次揮手
- 瀏覽器解析渲染建立根據(jù)html建立dom樹和css樹,如果遇到script首選判斷是否defer和async否則會(huì)阻塞渲染并編譯執(zhí)行js,如果沒(méi)有則組合生成render tree,最后瀏覽器開啟GPU進(jìn)行繪制合成圖層,將內(nèi)容顯示屏幕。
HTTP0.9 特性
沒(méi)有請(qǐng)問(wèn)頭和請(qǐng)求體只有請(qǐng)求行
只能發(fā)送html文件
HTTP1.0 特性
可以發(fā)送javaScript、CSS、圖片、音頻
加上請(qǐng)求頭和請(qǐng)求體
狀態(tài)碼
cache 機(jī)制
每進(jìn)行一次 HTTP 通信,都需要經(jīng)歷建立 TCP 連接、傳輸 HTTP 數(shù)據(jù)和斷開 TCP 連接三個(gè)階段
HTTP1.1 特性
- 持久連接的方法,它的特點(diǎn)是在一個(gè) TCP 連接上可以傳輸多個(gè) HTTP 請(qǐng)求,只要瀏覽器或者服務(wù)器沒(méi)有明確斷開連接,那么該 TCP 連接會(huì)一直保持(提高性能)
- 持久連接雖然能減少 TCP 的建立和斷開次數(shù),但是它需要等待前面的請(qǐng)求返回之后,才能進(jìn)行下一次請(qǐng)求。如果 TCP 通道中的某個(gè)請(qǐng)求因?yàn)槟承┰驔](méi)有及時(shí)返回,那么就會(huì)阻塞后面的所有請(qǐng)求,這就是著名的隊(duì)頭阻塞的問(wèn)題(在 HTTP 1.1 中,每一個(gè)鏈接都默認(rèn)是長(zhǎng)鏈接,因此對(duì)于同一個(gè) TCP 鏈接,HTTP 1.1 規(guī)定:服務(wù)端的響應(yīng)返回順序需要遵循其接收到相應(yīng)的順序。但這樣存在一個(gè)問(wèn)題:如果第一個(gè)請(qǐng)求處理需要較長(zhǎng)時(shí)間,響應(yīng)較慢,將會(huì)“拖累”其他后續(xù)請(qǐng)求的響應(yīng),這是一種隊(duì)頭阻塞。)
- 引入了 Cookie、虛擬主機(jī)的支持、對(duì)動(dòng)態(tài)內(nèi)容的支持
- 瀏覽器為每個(gè)域名最多同時(shí)維護(hù) 6 個(gè) TCP 持久連接(提高性能)
- 使用 CDN 的實(shí)現(xiàn)域名分片機(jī)制。(提高性能)
- 對(duì)帶寬的利用率卻并不理想(tcp 啟動(dòng)慢、頭部堵塞、tcp 競(jìng)爭(zhēng))
HTTP2 特點(diǎn)
- 只使用一個(gè) TCP 長(zhǎng)連接來(lái)傳輸數(shù)據(jù),實(shí)現(xiàn)資源的并行請(qǐng)求,也就是任何時(shí)候都可以將請(qǐng)求發(fā)送給服務(wù)器,解決頭部堵塞(多路復(fù)用)
- 二進(jìn)制傳輸
- 多路復(fù)用(原理二進(jìn)制分幀層,攜帶id的幀流到服務(wù)器)
- 頭部壓縮
- 服務(wù)端推送
HTTP3
QUIC 看成是集成了“TCP+HTTP/2 的多路復(fù)用 +TLS 等功能
TCP與UDP的區(qū)別
1、基于連接與無(wú)連接;
2、對(duì)系統(tǒng)資源的要求(TCP較多,UDP少);
3、UDP程序結(jié)構(gòu)較簡(jiǎn)單;
4、流模式與數(shù)據(jù)報(bào)模式 ;
5、TCP保證數(shù)據(jù)正確性,UDP可能丟包;
6、TCP保證數(shù)據(jù)順序,UDP不保證。
TCP 握手過(guò)程
建立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ù)端和客戶端都進(jìn)入了establish狀態(tài)),
為什么要3次握手:確認(rèn)客戶端的接收、發(fā)送能力正常,服務(wù)器自己的發(fā)送、接收能力也正常
HTTP 緩存
強(qiáng)緩存(瀏覽器內(nèi)部完成)
max-age:數(shù)值,單位是秒,從請(qǐng)求時(shí)間開始到過(guò)期時(shí)間之間的秒數(shù)?;谡?qǐng)求時(shí)間(Date字段)的相對(duì)時(shí)間間隔,而不是絕對(duì)過(guò)期時(shí)間
expires:和max-age一樣指緩存過(guò)期時(shí)間,但是他的指定了具體時(shí)間GMT格式,HTTP/1.1,Expire已經(jīng)被Cache-Control替代,原因在于Expires控制緩存的原理是使用客戶端的時(shí)間與服務(wù)端返回的時(shí)間做對(duì)比,那么如果客戶端與服務(wù)端的時(shí)間因?yàn)槟承┰颍ɡ鐣r(shí)區(qū)不同;客戶端和服務(wù)端有一方的時(shí)間不準(zhǔn)確)發(fā)生誤差,那么強(qiáng)制緩存則會(huì)直接失效,這樣的話強(qiáng)制緩存的存在則毫無(wú)意義
協(xié)商緩存(需要詢問(wèn)服務(wù)器)
Last-Modified/If-Modified-Since(服務(wù)端時(shí)間對(duì)比):本地文件在服務(wù)器上的最后一次修改時(shí)間。緩存過(guò)期時(shí)把瀏覽器端緩存頁(yè)面的最后修改時(shí)間發(fā)送到服務(wù)器去,服務(wù)器會(huì)把這個(gè)時(shí)間與服務(wù)器上實(shí)際文件的最后修改時(shí)間進(jìn)行對(duì)比,如果時(shí)間一致,那么返回304,客戶端就直接使用本地緩存文件。(瀏覽器最后修改時(shí)候和服務(wù)端對(duì)比,如果一致則走緩存)
問(wèn)題:
現(xiàn)在大多數(shù)服務(wù)端都采用了負(fù)載均衡策略,可能導(dǎo)致不同虛擬主機(jī)返回的Last-Modified時(shí)間戳不一致,導(dǎo)致對(duì)比失敗~
文件也許會(huì)周期性的更改,但是他的內(nèi)容并不改變,不希望客戶端重新get
Etag/If-None-Match:(EntityTags,內(nèi)容摘要)是URL的tag,用來(lái)標(biāo)示URL對(duì)象是否改變,一般為資源實(shí)體的哈希值。和Last-Modified類似,如果服務(wù)器驗(yàn)證資源的ETag沒(méi)有改變(該資源沒(méi)有更新),將返回一個(gè)304狀態(tài)告訴客戶端使用本地緩存文件。Etag的優(yōu)先級(jí)高于Last-Modified,Etag主要為了解決 Last-Modified無(wú)法解決的一些問(wèn)題。
文件緩存策略
有文件指紋:index.html 用不用緩存,其他用強(qiáng)緩存+文件指紋
無(wú)指紋:index.html 用不用緩存,其他用協(xié)商etag緩存(文件變了就緩存)
HTTPS
原理
服務(wù)器端的公鑰和私鑰,用來(lái)進(jìn)行非對(duì)稱加密
客戶端生成的隨機(jī)密鑰,用來(lái)進(jìn)行對(duì)稱加密
A 與 B 通信(被監(jiān)聽)
A 給 B 一個(gè)對(duì)成密鑰(對(duì)稱加密)(密鑰可能被劫持)
A 有自己的公鑰和私鑰(并且只要自己的私鑰解開公鑰)B也是有自己的公私鑰,但每次通信都要解密特別麻煩(非對(duì)稱加密)
A 直接把公鑰發(fā)給B,然后讓B通過(guò)公鑰加密‘對(duì)稱密鑰’,效率就快很多
那么這里又存在一個(gè)問(wèn)題 A 傳給 B 的公鑰被截取了然后冒充 A跟B通信
所以就有了ca驗(yàn)證中心證明 A就是A
完整流程:
那么A直接把公鑰發(fā)和證書給 B,B 通過(guò) ca 驗(yàn)證A身份后后,通過(guò)A的公鑰加密‘對(duì)稱密鑰’發(fā)給A,A用自己的私鑰解密得到了‘對(duì)稱密鑰’,以后就通過(guò)對(duì)稱加密方式交流
通訊流程

瀏覽器發(fā)起請(qǐng)求
服務(wù)器返回公鑰+簽名證書
瀏覽器向CA認(rèn)證中心詢問(wèn)證書是否有效
CA認(rèn)證返回結(jié)果
瀏覽器用公鑰加密對(duì)稱秘鑰
服務(wù)器用自己的私鑰解密 對(duì)稱稱秘鑰,發(fā)起通訊
http 請(qǐng)求方法
post 和 put 的區(qū)別
都能新增和修改數(shù)據(jù)
put 無(wú)副作用,調(diào)用一次和多次效果相同,post 調(diào)用每次都會(huì)新增
post 面向資源集合,put 面向單資源
put 和 patch 的區(qū)別
他兩都是同來(lái)更新數(shù)據(jù)的,而patch是同來(lái)更新局部資源比如某個(gè)對(duì)象只更改了某個(gè)字段則可以用 patch
常見狀態(tài)碼
206 部分內(nèi)容,代表服務(wù)器已經(jīng)成功處理了部分GET請(qǐng)求,可以應(yīng)用斷點(diǎn)陸續(xù)上傳
301 永久重定向
302 臨時(shí)重定向
304 命中協(xié)商緩存
400 請(qǐng)求報(bào)文存在語(yǔ)法錯(cuò)誤
401 需求http認(rèn)證
403 請(qǐng)求資源被服務(wù)器拒絕
404 服務(wù)端找不到資源
500 服務(wù)端執(zhí)行請(qǐng)求發(fā)送錯(cuò)誤
501 請(qǐng)求超出服務(wù)端能力范圍(不支持某個(gè)方法)
502 作為網(wǎng)關(guān)或者代理工作的服務(wù)器嘗試執(zhí)行請(qǐng)求時(shí),從遠(yuǎn)程服務(wù)器接收到了一個(gè)無(wú)效的響應(yīng)
503 由于超載或系統(tǒng)維護(hù),服務(wù)器暫時(shí)的無(wú)法處理客戶端的請(qǐng)求
HTTP 緩存
http 緩存過(guò)程
客戶端向服務(wù)端發(fā)起第一次請(qǐng)求資源
服務(wù)端返回資源,并通過(guò)響應(yīng)頭返回緩存策略
客服端根據(jù)緩存策略將策略和資源緩存起來(lái)
結(jié)論:
瀏覽器每次發(fā)起請(qǐng)求,都會(huì)先在瀏覽器緩存中查找該請(qǐng)求的結(jié)果以及緩存標(biāo)識(shí)
瀏覽器每次拿到返回的請(qǐng)求結(jié)果都會(huì)將該結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中

當(dāng)客戶端再次向服務(wù)端發(fā)起請(qǐng)求時(shí)
客戶端判斷是否有緩存,有則判斷是否存在 cache-control,并根據(jù)max-age 判斷其是否已過(guò)期,沒(méi)有過(guò)期直接讀取緩存,返回200
若已過(guò)期則攜帶緩存策略if-none-match發(fā)送到服務(wù)端
服務(wù)端根據(jù) etag 與if-none-match 相同則返回304繼續(xù)使用緩存,更新新鮮度
不相同則重新返回資源

緩存位置
- from memory cache代表使用內(nèi)存中的緩存,
- from disk cache則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)閙emory –> disk
- 內(nèi)存緩存(from memory cache):內(nèi)存緩存具有兩個(gè)特點(diǎn),分別是快速讀取和時(shí)效性:
- 快速讀取:內(nèi)存緩存會(huì)將編譯解析后的文件,直接存入該進(jìn)程的內(nèi)存中,占據(jù)該進(jìn)程一定的內(nèi)存資源,以方便下次運(yùn)行使用時(shí)的快速讀取。
- 時(shí)效性:一旦該進(jìn)程關(guān)閉,則該進(jìn)程的內(nèi)存則會(huì)清空。
- 硬盤緩存(from disk cache):硬盤緩存則是直接將緩存寫入硬盤文件中,讀取緩存需要對(duì)該緩存存放的硬盤文件進(jìn)行I/O操作,然后重新解析該緩存內(nèi)容,讀取復(fù)雜,速度比內(nèi)存緩存慢。
Web安全防御
xss 跨域腳本攻擊(利用網(wǎng)站對(duì)用戶的信任)
非持久型 - url參數(shù)直接注入 腳本,偽造成正常域名誘惑用戶點(diǎn)擊訪問(wèn)(瀏覽器默認(rèn)防范?)
持久型 - 存儲(chǔ)到DB后讀取時(shí)注入 (災(zāi)難)
兩者都是通過(guò)腳本獲取cookies 然后通過(guò)img或者ajax請(qǐng)求發(fā)送到黑客服務(wù)器?
防范手段:
html字符轉(zhuǎn)譯,或者只過(guò)濾script這些腳步
限制資源請(qǐng)求來(lái)源csp
cookies 防范: value 如果用于保存用戶登錄態(tài),應(yīng)該將該值加密,不能使用明文的用戶標(biāo)識(shí) http-only 不能通過(guò) JS 訪問(wèn) Cookie,減少 XSS 攻擊
Content-Security-Policy: img-src https://* 只允許加載 HTTPS 協(xié)議圖片 Content-Security-Policy: default-src ‘self’ 只允許加載本站資源前端方案:設(shè)置 meta 標(biāo)簽的方式
Csrf 跨域請(qǐng)求偽造(利用用戶對(duì)網(wǎng)站的信任):
即跨站請(qǐng)求偽造,是一種常見的Web攻擊,它利用用戶已登錄的身份,在用戶毫不知情的情況下,以用戶的名義完成非法操作 在A網(wǎng)站沒(méi)有退出的情況下,引誘用戶訪問(wèn)第三方網(wǎng)站(評(píng)論發(fā)鏈接)
訪問(wèn)頁(yè)面自動(dòng)發(fā)起請(qǐng)求 第三方默認(rèn)給A發(fā)起請(qǐng)求
防御:
Get 請(qǐng)求不對(duì)數(shù)據(jù)進(jìn)行修改
不讓第三方網(wǎng)站訪問(wèn)到用戶 Cookie
阻止第三方網(wǎng)站請(qǐng)求接口
請(qǐng)求時(shí)附帶驗(yàn)證信息,比如驗(yàn)證碼或者 Token
http 和 websocket 的區(qū)別
相同點(diǎn)主要
- 都是基于TCP的應(yīng)用層協(xié)議;
- 都使用Request/Response模型進(jìn)行連接的建立;
- 在連接的建立過(guò)程中對(duì)錯(cuò)誤的處理方式相同,在這個(gè)階段WS可能返回和HTTP相同的返回碼;
- 都可以在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。
不同之處
- WS使用HTTP來(lái)建立連接,但是定義了一系列新的header域,這些域在HTTP中并不會(huì)使用;
- WS的連接不能通過(guò)中間人來(lái)轉(zhuǎn)發(fā),它必須是一個(gè)直接連接;
- WS連接建立之后,通信雙方都可以在任何時(shí)刻向另一方發(fā)送數(shù)據(jù);
- WS連接建立之后,數(shù)據(jù)的傳輸使用幀來(lái)傳遞,不再需要Request消息;
- WS的數(shù)據(jù)幀有序。
七、webpack
一、Webpack 是什么
Webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) Webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。核心概念:
入口(entry)
? ? 其指示 Webpack 應(yīng)該用哪個(gè)模塊,來(lái)作為構(gòu)建其內(nèi)部依賴圖的開始,進(jìn)入入口起點(diǎn)后,Webpack 會(huì)找出有哪些模塊和庫(kù)是入口起點(diǎn)(直接和間接)依賴的。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為 bundles 的文件中。
輸出(output)
? ? output 屬性告訴 Webpack 在哪里輸出它所創(chuàng)建的bundles,以及如何命名這些文件,默認(rèn)值為 ./dist?;旧?,整個(gè)應(yīng)用程序結(jié)構(gòu),都會(huì)被編譯到你指定的輸出路徑的文件夾中。
module
? ? 模塊,在 Webpack 里一切皆模塊,在Webpack中,CSS、HTML、js、靜態(tài)資源文件等都可以視作模塊,Webpack 會(huì)從配置的 Entry 開始遞歸找出所有依賴的模塊,一 個(gè)模塊對(duì)應(yīng)著一個(gè)文件。
Chunk
? ? 代碼塊,一個(gè) Chunk 由多個(gè)模塊組合而成,用于代碼合并與分割
loader
? ? loader 讓 Webpack 能夠去處理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。loader 可以將所有類型的文件轉(zhuǎn)換為 Webpack 能夠處理的有效模塊,然后你就可以利用 Webpack 的打包能力,對(duì)它們進(jìn)行處理。本質(zhì)上,Webpack loader 將所有類型的文件,轉(zhuǎn)換為應(yīng)用程序的依賴圖(和最終的 bundle)可以直接引用的模塊
插件(Plugins)
? ? plugin 用來(lái)擴(kuò)展Webpack的功能,其通過(guò)構(gòu)建流程的特定時(shí)機(jī)注入鉤子實(shí)現(xiàn)的,插件接口功能極其強(qiáng)大,給Webpack帶來(lái)很大的靈活性。
二、Webpack 有什么特點(diǎn)
模塊化,壓縮,打包 具體作用:
- 搭建開發(fā)環(huán)境開啟服務(wù)器,監(jiān)視文件改動(dòng),熱更新
- 通過(guò)建立依賴圖把模塊打包成一個(gè)或者多個(gè)chuck。
- 通過(guò)loader 把將sass/less、圖片、vue等文件轉(zhuǎn)成Webpack可以識(shí)別的格式的文件
- 能夠通過(guò)插件對(duì)資源進(jìn)行模塊分離,壓縮,整合等
三、webapck 打包流程
詳細(xì)流程:
- 初始化參數(shù):從配置文件和 Shell 語(yǔ)句中讀取與合并參數(shù),得出最終的參數(shù)。
- 開始編譯:用上一步得到的參數(shù)初始化 Compiler 對(duì)象,加載所有配置的插件,執(zhí)行對(duì)象的 run 方法開始執(zhí)行編譯。
- 確定入口:根據(jù)配置中的 entry 找出所有的入口文件。
- 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對(duì)模塊進(jìn)行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過(guò)了本步驟的處理。
- 完成模塊編譯:在經(jīng)過(guò)第 4 步使用 Loader 翻譯完所有模塊后,得到了每個(gè)模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系。
- 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個(gè)個(gè)包含多個(gè)模塊的 Chunk,再把每個(gè) Chunk轉(zhuǎn)換成一個(gè)單獨(dú)的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會(huì)。
- 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)
簡(jiǎn)單流程:
入口文件開始分析:
- 哪些依賴文件
- 轉(zhuǎn)換代碼
遞歸分析依賴代碼
- 哪些依賴文件
- 轉(zhuǎn)換代碼
生成瀏覽器可以識(shí)別執(zhí)行的bundle文件
四、Webapck 打包原理
- 通過(guò)fs.readFileSync讀取入口文件,然后通過(guò)@babel/parser獲取ast抽象語(yǔ)法樹,借助@babel/core和 @babel/preset-env,把a(bǔ)st語(yǔ)法樹轉(zhuǎn)換成合適的代碼最后輸出一個(gè)文件對(duì)象,下面舉個(gè)栗子:
打包入口文件為index.js:
import?fn1?from?'./a.js'
fn1()
依賴文件a.js
const?fn1?=()=>{
??console.log('1111111111')
}
export?default?fn1
生成文件對(duì)象
?{
???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}));...'
??}
- 由于生成的代碼還包含了瀏覽器無(wú)法識(shí)別 require 函數(shù),所以實(shí)現(xiàn)了一個(gè)__webpack_require__替換require來(lái)實(shí)現(xiàn)模塊化,通過(guò)自執(zhí)行函數(shù)傳入index文件對(duì)象。流程是先通過(guò)eval函數(shù)運(yùn)行index.js的代碼,當(dāng)遇到__webpack_require__時(shí)通過(guò)__webpack_modules__字典對(duì)象獲取 a.js 的代碼并且通過(guò)eval運(yùn)行其代碼。
詳細(xì)看下圖:

五、常見優(yōu)化手段
- MiniCssExtractPlugin插件:對(duì)CSS進(jìn)行分離和壓縮
- happypack插件:HappyPack開啟多個(gè)線程打包資源文件
- DllPlugin、DllReferencePlugin插件:DllPlugin通過(guò)配置Webpack.dll.conf.js把第三方庫(kù):vue、vuex、element-ui等打包到一個(gè)bundle的dll文件里面,同時(shí)會(huì)生成一個(gè)名為 manifest.json映射文件,最后使用 DllReferencePlugin檢測(cè)manifest.json映射,過(guò)濾掉已經(jīng)存在映射的包,避免再次打包進(jìn)bundle.js。
- ParallelUglifyPlugin插件:開啟多個(gè)子進(jìn)程壓縮輸出的 JS 代碼(Webpack4.0 默認(rèn)使用了 TerserWebpackPlugin,默認(rèn)就開啟了多進(jìn)程和緩存)
- optimization.splitChunks:抽離公共文件
- 其他:exclude/include配置、externals配置、使用cache-loader等
六、Webpack 常見面試題
熱更新原理
? ? 監(jiān)聽文件變動(dòng),通過(guò)websocket協(xié)議自動(dòng)刷新網(wǎng)頁(yè)(詳細(xì)內(nèi)容還沒(méi)有深入研究)
loader與plugin的區(qū)別
plugin:插件是一個(gè)擴(kuò)展器,目的在于解決 loader 無(wú)法實(shí)現(xiàn)的其他事。
常用的loader和常見plugin有哪些
loader:
- babel-loader:把 ES6 轉(zhuǎn)換成 ES5
- less-loader:將less代碼轉(zhuǎn)換成CSS
- css-loader:加載 CSS,支持模塊化、壓縮、文件導(dǎo)入等特性
- style-loader:把 CSS 代碼注入到 JavaScript 中,通過(guò) DOM 操作去加載 CSS
- eslint-loader:通過(guò) ESLint 檢查 JavaScript 代碼
- vue-loader:加載 Vue.js 單文件組件
- cache-loader: 可以在一些性能開銷較大的 Loader 之前添加,目的是將結(jié)果緩存到磁盤里
- file-loader:把文件輸出到一個(gè)文件夾中,在代碼中通過(guò)相對(duì) URL 去引用輸出的文件 (處理圖片和字體)
- url-loader:與 file-loader 類似,區(qū)別是用戶可以設(shè)置一個(gè)閾值,大于閾值時(shí)返回其 publicPath,小于閾值時(shí)返回文件 base64 形式編碼 (處理圖片和字體)
plugin:
- CopyWebpackPlugin:將單個(gè)文件或整個(gè)目錄復(fù)制到構(gòu)建目錄
- HtmlWebapckPlugin:簡(jiǎn)單創(chuàng)建 HTML 文件,用于服務(wù)器訪問(wèn)
- ParallelUglifyPlugin: 多進(jìn)程執(zhí)行代碼壓縮,提升構(gòu)建速度
- MiniCssExtractPlugin: 分離樣式文件,CSS 提取為獨(dú)立文件,支持按需加載 (替代extract-text-Webpack-plugin)
用過(guò)哪些可以提高效率的webapck插件
此答案請(qǐng)參考目錄五、常見優(yōu)化手段
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的loader
實(shí)現(xiàn)一個(gè)替換源碼中字符的loader
//index.js
console.log("hello");
//replaceLoader.js
module.exports?=?function(source)?{
??//?source是源碼
??return?source.replace('hello','hello?loader')?
};
在配置文件中使用loader
//需要node模塊path來(lái)處理路徑?
const?path?=?require('path')
module:?{
rules:?[?{
?????????test:?/\.js$/,
?????????use:?path.resolve(__dirname,"./loader/replaceLoader.js")
}]
}
Webpack插件原理,如何寫一個(gè)插件
原理:在 Webpack 運(yùn)行的生命周期中會(huì)廣播出許多事件(run、compile、emit等),Plugin 可以監(jiān)聽這些事件,在合適的時(shí)機(jī)通過(guò) Webpack 提供的 API 改變輸出結(jié)果
實(shí)現(xiàn)一個(gè)copy功能的Plugin
class?CopyrightWebpackPlugin?{
??//compiler:webpack實(shí)例
??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()
??]
Webpack的require是如何如何查找依賴的
- 解析相對(duì)路徑:查找相對(duì)當(dāng)前模塊的路徑下是否有對(duì)應(yīng)文件或文件夾是文件則直接加載,是文件夾則繼續(xù)查找文件夾下的 package.json 文件
- 解析模塊名:查找當(dāng)前文件目錄下,父級(jí)目錄及以上目錄下的 node_modules 文件夾,看是否有對(duì)應(yīng)名稱的模塊
- 解析絕對(duì)路徑:直接查找對(duì)應(yīng)路徑的文件
八、性能優(yōu)化
性能優(yōu)化思路
- 性能監(jiān)控:lighthouse 插件 和瀏覽器 performance 工具
- 網(wǎng)絡(luò)層面性能優(yōu)化
- 減少HTTP請(qǐng)求次數(shù)(緩存、本地存儲(chǔ))
- 減少HTTP請(qǐng)求文件體積(文件壓縮)
- 加快HTTP請(qǐng)求速度(CDN)
- 渲染層面性能優(yōu)化
- DOM 優(yōu)化
- CSS 優(yōu)化
- JS 優(yōu)化
- 首屏渲染
- 服務(wù)端渲染
網(wǎng)絡(luò)層面性能優(yōu)化
減少HTTP請(qǐng)求次數(shù)
- 瀏覽器緩存
- Memory Cache
- HTTP Cache
- Service Worker Cache
- Push Cache
- 本地存儲(chǔ)
- Web Storage(容量5-10M)
- indexdb 運(yùn)行在瀏覽上的非關(guān)系型數(shù)據(jù)庫(kù)(容量>100M)
- Cookies (容量4KB)
MemoryCache,是指存在內(nèi)存中的緩存。從優(yōu)先級(jí)上來(lái)說(shuō),它是瀏覽器最先嘗試去命中的一種緩存。從效率上來(lái)說(shuō),它是響應(yīng)速度最快的一種緩存。Memory Cache一般存儲(chǔ)體積較小的文件。
瀏覽器緩存--HTTP Cache
設(shè)置HTTP Header里面緩存相關(guān)的字段,實(shí)現(xiàn)HTTP緩存
強(qiáng)緩存:Cache-Control,expires
private: 僅瀏覽器可以緩存(HTML文件可以設(shè)置避免CDN緩存)Cache-Control 的幾個(gè)取值含義:
public: 瀏覽器和代理服務(wù)器都可以緩存
max-age=xxx: 過(guò)期時(shí)間
no-cache: 不進(jìn)行強(qiáng)緩存
no-store:禁用任何緩存,每次都會(huì)向服務(wù)端發(fā)起新的請(qǐng)求
在設(shè)置強(qiáng)緩存情況下,請(qǐng)求返回Response Headers Cache-Control的值,如果有max-age=xxx秒,則命中強(qiáng)緩存,如果Cache-Control的值是no-cache,說(shuō)明沒(méi)命中強(qiáng)緩存,走協(xié)商緩存
- 協(xié)商緩存:Etag/If-None-Match,Last-Modified/If-Modified-Since
Last-Modified:文件的修改時(shí)間
協(xié)商緩存觸發(fā)條件:Cache-Control 的值為 no-cache (不強(qiáng)緩存)或者 max-age 過(guò)期了 (強(qiáng)緩存,但總有過(guò)期的時(shí)候)
協(xié)商緩存步驟總結(jié):在設(shè)置協(xié)商緩存情況下,在第一次請(qǐng)求的時(shí)候,返回的Response Headers會(huì)帶有Etag/Last-Modified值。當(dāng)再次請(qǐng)求沒(méi)有命中強(qiáng)制緩存的時(shí)候,這個(gè)時(shí)候我們的Request Headers就會(huì)攜帶If-None-Match/If-Modified-Since字段,它們的值就是我們第一次請(qǐng)求返回給我們的Etag/Last-Modified值。服務(wù)端再拿Etag和If-None-Match,Last-Modified和If-Modified-Since來(lái)進(jìn)行比較較,如果相同(命中就直接使 緩存),返回304,瀏覽器讀取本地緩存,如果不同(沒(méi)有命中)則返回200,再重新從服務(wù)端拉取新的資源。?
瀏覽器緩存-Service Worker Cache
Service Worker 是一種獨(dú)立于主線程之外的 Javascript 線程。它脫離于瀏覽器窗體,因此無(wú)法直接訪問(wèn) DOM。這樣獨(dú)立的個(gè)性使得 Service Worker 的“個(gè)人行為”無(wú)法干擾頁(yè)面的性能,這個(gè)“幕后工作者”可以幫我們實(shí)現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能。我們借助 Service worker 實(shí)現(xiàn)的離線緩存就稱為Service Worker Cache ,其實(shí)現(xiàn)基于HTTPS。
本地存儲(chǔ)--Web Storage
Web Storage 又分Local Storage 和 Session Storage,特色容量大5-10M它們的區(qū)別是Local Storage 永久緩存除非手動(dòng)刪除,Session Storage瀏覽器窗口關(guān)閉即失效。
減少HTTP請(qǐng)求文件體積
- 圖片優(yōu)化
- 壓縮圖片
- 使用webP
- 大圖盡量用JPG代替PNG
- 使用 CSS Sprites雪碧圖(HTTP2不需要)
- 使用iconfont(icon首選)
- 使用base64(小圖解決方案)
- 復(fù)雜圖形地圖等使用SVG
- 圖片懶加載
- 資源打包壓縮(webpack打包抽離公共CSS/JS)
- MiniCssExtractPlugin插件:對(duì)CSS進(jìn)行分離和壓縮
- optimization.splitChunks:抽離公共JS文件
- optimization.minimizer :文件壓縮
- 服務(wù)端開啟gzip壓縮
accept-encoding:gzipGzip 壓縮背后的原理,是在一個(gè)文本文件中找出一些重復(fù)出現(xiàn)的字符串、臨時(shí)替換它們,從而使整個(gè)文件變小。根據(jù)這個(gè)原理,文件中代碼的重復(fù)率越高,那么壓縮的效率就越高,使用 Gzip 的收益也就越大,反之亦然。開啟Gzip省下了一些傳輸過(guò)程中的時(shí)間開銷,而其代價(jià)是服務(wù)器壓縮的時(shí)間開銷和 CPU 開銷(以及瀏覽器解析壓縮文件的開銷)。
使用CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))
CDN (Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò))指的是一組分布在各個(gè)地區(qū)的服務(wù)器。這些服務(wù)器存儲(chǔ)著數(shù)據(jù)的副本,因此服務(wù)器可以根據(jù)哪些服務(wù)器與用戶距離最近,來(lái)滿足數(shù)據(jù)的請(qǐng)求。CDN 提供快速服務(wù),較少受高流量影響,
CDN 是靜態(tài)資源提速的重要手段。
渲染層次性能優(yōu)化
DOM 優(yōu)化
1.減少DOM操作- 使用虛擬DOM
- 使用事件委托
- 使用DOM Fragment
DocumentFragment 接口表示一個(gè)沒(méi)有父級(jí)文件的最小文檔對(duì)象。它被當(dāng)做一個(gè)輕量版的 Document 使用,用于存儲(chǔ)已排好版的或尚未打理好格式的XML片段。因?yàn)?DocumentFragment 不是真實(shí) DOM 樹的一部分,它的變化不會(huì)引起 DOM 樹的重新渲染的操作(reflow),且不會(huì)導(dǎo)致性能等問(wèn)題。
- 緩存DOM結(jié)點(diǎn)
//?只獲取一次container
let?container?=?document.getElementById('container')
for(let?count=0;count<10000;count++){?
??container.innerHTML?+=?'<span>我是一個(gè)小測(cè)試</span>'
}?
減少使用iframe
JS 優(yōu)化
使用防抖截流
//?防抖:定時(shí)執(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ī)定時(shí)間內(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)實(shí)現(xiàn)的Tree-Shaking,在Webpack打包中會(huì)把冗余模塊剔除掉栗子:
import?{?add1,?add2?}?from?'./pages'
console.log(add1)
//?本栗子add2模塊代碼不會(huì)被打包bundle文件
CSS 優(yōu)化
- 有效使用選擇器,避免使用calc表達(dá)式、CSS濾鏡
- 減少重排和重繪
- 布局優(yōu)先使用flex
- 盡量避免使用getBoundingClientRect,getComputedStyle等屬性
- 觸發(fā)渲染層(transform: translateZ(0);backface-visibility: hidden;)
服務(wù)端渲染
服務(wù)器把需要的組件或頁(yè)面渲染成 HTML 字符串,然后把它返回給客戶端??蛻舳四玫绞值模强梢灾苯愉秩救缓蟪尸F(xiàn)給用戶的 HTML 內(nèi)容,不需要為了生成 DOM 內(nèi)容自己再去跑一遍 JS 代碼。
const?Vue?=?require('vue')
//?創(chuàng)建?個(gè)express應(yīng)?
const?server?=?require('express')()
//?提取出renderer實(shí)?
const?renderer?=?require('vue-server-renderer').createRenderer()
server.get('*',?(req,?res)?=>?{?//?編寫Vue實(shí)?(虛擬DOM節(jié)點(diǎn))?const?app?=?new?Vue({
????data:?{
??????url:?req.url
},
//?編寫模板HTML的內(nèi)容
template:?`<div>訪問(wèn)的?URL?是:?{{?url?}}</div>`?})
//?renderToString?是把Vue實(shí)?轉(zhuǎn)化為真實(shí)DOM的關(guān)鍵?法?renderer.renderToString(app,?(err,?html)?=>?{
????if?(err)?{
??????res.status(500).end('Internal?Server?Error')
??????return
}
//?把渲染出來(lái)的真實(shí)DOM字符?插?HTML模板中?res.end(`
??????<!DOCTYPE?html>
??????<html?lang="en">
????????<head><title>Hello</title></head>
????????<body>${html}</body>
??????</html>
`)?})
})
server.listen(8080)
首屏渲染
- 內(nèi)聯(lián)首屏關(guān)鍵CSS
- 異步加載CSS(動(dòng)態(tài)插入)
- JS放body 底部,或者異步加載js(defer、async)
- 預(yù)加載 preload
<link?rel="preload"?href="index.js"?as="script">
- 頁(yè)面loading動(dòng)畫
- 骨架圖
vue 層面優(yōu)化
- 引入生產(chǎn)環(huán)境的 Vue 文件
- 使用單文件組件預(yù)編譯模板
- 提取組件的 CSS 到單獨(dú)到文件
- 利用Object.freeze()提升性能
- 扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
- 組件懶加載
- 路由懶加載
- 虛擬滾動(dòng)
九、Vue
Vue 雙向綁定原理
vue.js 是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過(guò)Object.defineProperty()來(lái)劫持各個(gè)屬性的setter,getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。數(shù)據(jù)劫持
遍歷 data 屬性通過(guò)Object.defineproprety 劫持 setter 和 getter
vue 依賴收集過(guò)程
new wacther 時(shí),通過(guò) pushTarget 把該 Dep.target 指向改 wacther,在 render 執(zhí)行渲染 Vnode 過(guò)程中觸發(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?*/)
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
?}
}
dep作為參數(shù)在 addDep 里通過(guò)map has 判斷 dep 的 id 是否已經(jīng)存在了 dep,如果沒(méi)有就壓入棧 (過(guò)濾重復(fù)的dep)
后執(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í)行了之后會(huì)緩存
computd 適合比較耗性能的計(jì)算場(chǎng)景
watch 更多是監(jiān)聽,監(jiān)聽某個(gè)變化而執(zhí)行回調(diào)
Vue key 的作用
key 是給每一個(gè) vnode 的唯一 id,可以依靠 key,更準(zhǔn)確, 更快的拿到 oldVnode 中對(duì)應(yīng)的 vnode 節(jié)點(diǎn)。
更準(zhǔn)確
因?yàn)閹?key 就不是就地復(fù)用了,在sameNode函數(shù) a.key === b.key對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確。
更快
利用 key 的唯一性生成map對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn),比遍歷方式更快
不帶 key 時(shí)或者以 index 作為 key 時(shí):比如可能不會(huì)產(chǎn)生過(guò)渡效果,或者在某些節(jié)點(diǎn)有綁定數(shù)據(jù)(表單)狀態(tài),會(huì)出現(xiàn)狀態(tài)錯(cuò)位
Vue nextTick 原理
- 它可以在 DOM 更新完畢之后執(zhí)行一個(gè)回調(diào),使我們可以操作更新后的 dom
- 可以監(jiān)聽 dom 的變化就是 h5 新特性的 mutationObserver,但是 Vue 并不是通過(guò)監(jiān)聽 dom 變化的方式實(shí)現(xiàn)的
- 而是通過(guò) eventloop 原理,因?yàn)?eventloop 的 task 執(zhí)行完后(完成一次事件循環(huán))進(jìn)行一次 DOM 更新
- 而完成一個(gè) task 分界點(diǎn)就是 微任務(wù)完成 所以 Vue 首先是用 promise.then 然后就是 mutationObserver。
- 為了兼容性然后降級(jí)到宏任務(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 生成真實(shí) dom,以后通過(guò) sameVnode 判斷同結(jié)點(diǎn)則進(jìn)行 patchVNode 結(jié)點(diǎn)的 child ,如果新舊結(jié)點(diǎn)都存在則走 updateChildren 流程,不斷通過(guò)新舊頭頭、尾尾、交叉 sameVnode 對(duì),都比對(duì)不成功 ,則直接拿key去映射表查找,如找到有相同的 key 結(jié)點(diǎn)則進(jìn)行 sameVnode 對(duì)比,成立則又進(jìn)入下子結(jié)點(diǎn)的patchVNode,直到遍歷完成。
new vue() 流程

Vuex 有什么特點(diǎn)
? ? 首先說(shuō)明 Vuex 是一個(gè)專為 Vue 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。Vuex 核心概念重點(diǎn)同步異步實(shí)現(xiàn) action mutationVue 組件為什么 data 不能是對(duì)象
Vue 組件可能存在多個(gè)實(shí)例,如果使用對(duì)象形式定義 data,則會(huì)導(dǎo)致它們共用一個(gè)data對(duì)象,那么狀態(tài)變更將會(huì)影響所有組件實(shí)例,mvvm 的理解
通過(guò)指令的方式實(shí)現(xiàn) model 和 view 主動(dòng)數(shù)據(jù)響應(yīng)和view更新Vue 有哪些性能優(yōu)化
- 路由懶加載
- keep-alive 緩存頁(yè)面
- 用 v-show 復(fù)用dom
- 長(zhǎng)列表純數(shù)據(jù)展示用 Object.freeze 凍結(jié)
- 長(zhǎng)列表大數(shù)據(jù)用虛擬滾動(dòng)
- 事件及時(shí)銷毀
- 服務(wù)端渲染 ssr
服務(wù)端渲染原理
在客戶端請(qǐng)求服務(wù)器的時(shí)候,服務(wù)器到數(shù)據(jù)庫(kù)中獲取到相關(guān)的數(shù)據(jù),并且在服務(wù)器內(nèi)部將Vue組件渲染成HTML,并且將數(shù)據(jù)、HTML一并返回給客戶端,這個(gè)在服務(wù)器將數(shù)據(jù)和組件轉(zhuǎn)化為HTML的過(guò)程,叫做服務(wù)端渲染SSR
使用SSR的好處

有利于SEO
白屏?xí)r間更短
Vue3 有什么新特性
- 數(shù)據(jù)劫持:用 proxy 做代理
- 虛擬 dom 重構(gòu):v2會(huì)把靜態(tài)結(jié)點(diǎn)轉(zhuǎn)成vdom,新只會(huì)構(gòu)造動(dòng)態(tài)結(jié)點(diǎn)的vdom
- 將大多數(shù)全局API和內(nèi)部組件移至ES模塊導(dǎo)出,tree-shaking更友好
- 支持了 ts
- Composition api:新增 setup 函數(shù)有利于代碼復(fù)用(替代mixin,mixin會(huì)容易存在命名沖突)
Vue2 響應(yīng)式弊端
? ? 響應(yīng)化過(guò)程需要遞歸遍歷,消耗較大新加或刪除屬性無(wú)法監(jiān)聽數(shù)組響應(yīng)化需要額外實(shí)現(xiàn)Map、Set、Class等無(wú)法響應(yīng)式修改語(yǔ)法有限制生命周期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★★(父?jìng)髯樱?/span>emit/emit/emit/on★★事件總線(跨層級(jí)通信)vuex★★★(狀態(tài)管理常用皆可)優(yōu)點(diǎn):一次存儲(chǔ)數(shù)據(jù),所有頁(yè)面都可訪問(wèn)parent/parent/parent/children(父=子項(xiàng)目中不建議使用)缺點(diǎn):不可跨層級(jí)attrs/attrs/attrs/listeners(皆可如果搞不明白不建議和面試官說(shuō)這一種)provide/inject★★★(高階用法=推薦使用)優(yōu)點(diǎn):使用簡(jiǎn)單 缺點(diǎn):不是響應(yīng)式v-model vs .sync
區(qū)別:
一個(gè)組件可以多個(gè)屬性用.sync修飾符,可以同時(shí)"雙向綁定多個(gè)“prop”,而并不像v-model那樣,一個(gè)組件只能有一個(gè)
使用場(chǎng)景:
v-model針對(duì)更多的是最終操作結(jié)果,是雙向綁定的結(jié)果,是value,是一種change操作
sync針對(duì)更多的是各種各樣的狀態(tài),是狀態(tài)的互相傳遞,是status,是一種update操作
v-model 可以在只需子組件更新父組件的場(chǎng)景使用如:
//?父組件接收
<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í)例初始化之后,數(shù)據(jù)觀測(cè)(data observe)和event/watcher事件配置之前被調(diào)用,這時(shí)無(wú)法訪問(wèn)data及props等數(shù)據(jù);
created:在實(shí)例創(chuàng)建完成后被立即調(diào)用,此時(shí)實(shí)例已完成數(shù)據(jù)觀測(cè)(data observer),屬性和方法的運(yùn)算,watch/event事件回調(diào),掛載階段還沒(méi)開始,$el尚不可用。
beforemount: 在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用。
mounted:實(shí)例被掛載后調(diào)用,這時(shí)el被新創(chuàng)建的vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el也在文檔內(nèi)。注意mounted不會(huì)保證所有子組件一起掛載。
beforeupdata:數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬dom打補(bǔ)丁前,這時(shí)適合在更新前訪問(wèn)現(xiàn)有dom,如手動(dòng)移除已添加的事件監(jiān)聽器。
updated:在數(shù)據(jù)變更導(dǎo)致的虛擬dom重新渲染和打補(bǔ)丁后,調(diào)用該鉤子。當(dāng)這個(gè)鉤子被調(diào)用時(shí),組件dom已更新,可執(zhí)行依賴于dom的操作。多數(shù)情況下應(yīng)在此期間更改狀態(tài)。如需改變,最好使用watcher或計(jì)算屬性取代。注意updated不會(huì)保證所有的子組件都能一起被重繪。
beforedestory:在實(shí)例銷毀之前調(diào)用。在這時(shí),實(shí)例仍可用。
destroyed:實(shí)例銷毀后調(diào)用,這時(shí) Vue 實(shí)例的所有指令都被解綁,所有事件監(jiān)聽器被移除,所有子實(shí)例也被銷毀。
Vue compile 過(guò)程
編譯過(guò)程整體分為解析、優(yōu)化和生成
解析 - parse
解析器將模板解析為抽象語(yǔ)法樹,基于AST可以做優(yōu)化或者代碼生成工作優(yōu)化- optimize
優(yōu)化器的作用是在AST中找出靜態(tài)子樹并打上標(biāo)記。靜態(tài)子樹是在AST中永遠(yuǎn)不變的節(jié)點(diǎn),如純文本節(jié)點(diǎn)。標(biāo)記靜態(tài)子樹的好處:每次重新渲染,不需要為靜態(tài)子樹創(chuàng)建新節(jié)點(diǎn)虛擬DOM中patch時(shí),可以跳過(guò)靜態(tài)子樹代碼生成- generate
將AST轉(zhuǎn)換成渲染函數(shù)中的內(nèi)容,即代碼字符串。Vue2 vs Vue3
Vue2 響應(yīng)式弊端:響應(yīng)化過(guò)程需要遞歸遍歷,消耗較大新加或刪除屬性無(wú)法監(jiān)聽數(shù)組響應(yīng)化需要額外實(shí)現(xiàn)Map、Set、Class等無(wú)法響應(yīng)式修改語(yǔ)法有限制十、React
React 是什么?
用于構(gòu)建界面的 javascript 庫(kù)
特點(diǎn):
聲明式
組件式
優(yōu)點(diǎn):
開發(fā)團(tuán)隊(duì)和社區(qū)非常強(qiáng)大
api 簡(jiǎn)潔
缺點(diǎn):
沒(méi)有官方系統(tǒng)解決方案,選型成本高
過(guò)于靈活,對(duì)代碼設(shè)計(jì)要求高
jsx 是 React.createElement 的語(yǔ)法糖
React 的 class 組件和函數(shù)組件的區(qū)別
相同:都可以接收 props 并返回 react 對(duì)象
不同:
編程思想和內(nèi)存:類組件需要?jiǎng)?chuàng)建實(shí)例面向?qū)ο缶幊?,它?huì)保存實(shí)例,需要一定的內(nèi)存開銷,而函數(shù)組件面向函數(shù)式編程,可節(jié)約內(nèi)存
可測(cè)試性:函數(shù)式更利用編寫單元測(cè)試
捕獲特性:函數(shù)組件具有值捕獲特性(只能得到渲染前的值)
狀態(tài):class 組件定義定義狀態(tài),函數(shù)式需要使用 useState
生命周期:class 組件有完整的生命周期,而組件沒(méi)有,可以用useEffect 實(shí)現(xiàn)類生命周期功能
邏輯復(fù)用:類組件通過(guò)繼承或者高階組件實(shí)現(xiàn)邏輯復(fù)用,函數(shù)組件通過(guò)自定義組件實(shí)現(xiàn)復(fù)用
跳過(guò)更新:類組件可以通過(guò)shouldComponents 和 PureComponents(淺比較,深比較可以用immer) 來(lái)跳過(guò)更新,函數(shù)組件react.memo 跳過(guò)更新
發(fā)展前景:函數(shù)組件將成為主流,因?yàn)樗闷帘蝨his問(wèn)題,和復(fù)用邏輯,更好的適合時(shí)間分片和并發(fā)渲染
React 設(shè)計(jì)理念
跨平臺(tái)渲染=>虛擬dom
快速響應(yīng)=>異步可中斷(fiber)+增量更新
fiber
fiber 是一個(gè)執(zhí)行單元,每次執(zhí)行完成一個(gè)執(zhí)行單元,react 會(huì)檢測(cè)當(dāng)前幀還剩多少時(shí)間,如果沒(méi)有時(shí)間就將控制器讓出去
fiber 是一種數(shù)據(jù)結(jié)構(gòu)
react 目前的做法使用鏈表,每個(gè)vdom結(jié)點(diǎn)內(nèi)部表示為一個(gè)fider
從頂點(diǎn)開始遍歷
如果有第一個(gè)兒子,則先遍歷第一個(gè)兒子
如果沒(méi)有第一個(gè)兒子,則標(biāo)志此結(jié)點(diǎn)遍歷完成
如果有弟弟則遍歷弟弟
如果沒(méi)有弟弟,則返回父節(jié)點(diǎn)標(biāo)志父節(jié)點(diǎn)遍歷完成,如果有叔叔則遍歷叔叔
沒(méi)有叔叔則遍歷結(jié)束
(兒子=〉弟弟=〉叔叔)

fiber 出現(xiàn)背景
React15 架構(gòu)不能支撐異步更新,當(dāng)渲染出現(xiàn)大量的組件遞歸時(shí)間超過(guò) 16.7ms 就會(huì)出現(xiàn)卡幀
React16 架構(gòu)可以分為三層:
Scheduler(調(diào)度器類似 requestIdleCallback 功能)— 調(diào)度任務(wù)的優(yōu)先級(jí),高優(yōu)任務(wù)優(yōu)先進(jìn)入Reconciler
Reconciler(協(xié)調(diào)器)— 負(fù)責(zé)找出變化的組件
Renderer(渲染器)— 負(fù)責(zé)將變化的組件渲染到頁(yè)面上
react 渲染過(guò)程分為三步驟
調(diào)度
調(diào)和
提交(不可暫停)

fiber 實(shí)現(xiàn)關(guān)鍵原理
基于requestIdleCallback,獲取當(dāng)前一幀還有剩余時(shí)間deedline,(將在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì))
由于兼容性實(shí)際是用Messagechannel + requestAnimationFrame 模擬requestIdleCallback
effect 副作用,表示將要對(duì)一個(gè)dom 元素進(jìn)行操作,副作用鏈就是子孫后代,作用倒掛fiber構(gòu)建dom結(jié)點(diǎn)
fiber 的作用
能夠把可中斷的任務(wù)切片處理。
能夠在父元素與子元素之間交錯(cuò)處理,以支持 React 中的布局。
能夠在render()中返回多個(gè)元素。
更好地支持錯(cuò)誤邊界。
原理利用 requestIdleCallback 函數(shù)將在瀏覽器?的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì)。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)?行行后臺(tái)和低優(yōu)先級(jí)?工作,?而不不會(huì)影響延遲關(guān)鍵事件,如動(dòng)畫和輸?入響應(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ū)別
相同:
- 都是前端界面實(shí)現(xiàn) javascript 庫(kù)
- 都可以實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)模版更新,而不需直接操作dom,核心都是 vdom
- Vue通過(guò)數(shù)據(jù)劫持,當(dāng)數(shù)據(jù)改動(dòng)時(shí),界面就會(huì)自動(dòng)更新,而React里面需要調(diào)用方法setState。
- Vue 的更新的顆粒度是當(dāng)前組件,而React除了當(dāng)前組件還包括子組件(會(huì)有性能瓶頸)
- 在設(shè)計(jì)上,Vue 數(shù)據(jù)和模版和方法是分開的,而 React 不分開,Vue 會(huì)有很多的指令,React 只有 js
- 從 diff 上,當(dāng) Vue 的className不一致是認(rèn)為不同的結(jié)點(diǎn),而 React 認(rèn)為是同一個(gè)結(jié)點(diǎn),Vue 有頭頭,尾尾,交叉對(duì)比,而 React 沒(méi)有。
- React16 之后版本有 fider 之后可以實(shí)現(xiàn)異步切片更新
- React 難得是它沒(méi)有類似vue現(xiàn)成的全家桶技術(shù)棧,需要自己去選擇和衡量,這就需要時(shí)間去踩坑,而且 React 周邊使用起來(lái)并不太簡(jiǎn)潔明朗,需要了解所選周邊的編寫方式和最佳實(shí)踐,這就耗費(fèi)時(shí)間和增加入門門檻了
- React 的生態(tài)比 Vue 完善
hook
Hook 是一些可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù),可以使你在函數(shù)組件使用狀態(tài)。
setState 是異步還是同步
React的setState本身并不是異步的,是因?yàn)槠渑幚頇C(jī)制給人一種異步的假象。
【React的更新機(jī)制】
生命周期函數(shù)和合成事件中:
無(wú)論調(diào)用多少次setState,都不會(huì)立即執(zhí)行更新。而是將要更新的state存入'_pendingStateQuene',將要更新的組件存入'dirtyComponent';
當(dāng)根組件didMount后,批處理機(jī)制更新為false。此時(shí)再取出'_pendingStateQuene'和'dirtyComponent'中的state和組件進(jìn)行合并更新;
原生事件和異步代碼中:
原生事件(原生js綁定的事件)不會(huì)觸發(fā)react的批處理機(jī)制,因而調(diào)用setState會(huì)直接更新;
異步代碼中調(diào)用setState,由于js的異步處理機(jī)制,異步代碼會(huì)暫存,等待同步代碼執(zhí)行完畢再執(zhí)行,此時(shí)react的批處理機(jī)制已經(jīng)結(jié)束,因而直接更新。
總結(jié):
react會(huì)表現(xiàn)出同步和異步(setTimeout/setInterval)的現(xiàn)象,但本質(zhì)上是同步的,是其批處理機(jī)制造成了一種異步的假象。(其實(shí)完全可以在開發(fā)過(guò)程中,在合成事件和生命周期函數(shù)里,完全可以將其視為異步)
React 合成事件
16 事件流(沒(méi)有分別注冊(cè)捕獲和冒泡事件 bug )


bug :點(diǎn)擊彈窗沒(méi)反應(yīng)

原因:點(diǎn)擊事件直接冒泡到 document 原生事件了,state 變成了 false
解決:

原理:

17 事件流
事件委托不再是 document 而是 掛載點(diǎn)容器,可以讓一個(gè)頁(yè)面可以使用多個(gè) React 版本

不掛載到 document ,結(jié)點(diǎn)變成父子關(guān)系所以,stopPropagation(阻止冒泡) 生效
redux
什么時(shí)候使用redux:
某個(gè)組件的狀態(tài),需要共享
某個(gè)狀態(tài)需要在任何地方都可以拿到
一個(gè)組件需要改變?nèi)譅顟B(tài)
一個(gè)組件需要改變另一個(gè)組件的狀態(tài)? ?
十一、總結(jié)
? ? 在我們閱讀完官方文檔后,我們一定會(huì)進(jìn)行更深層次的學(xué)習(xí),比如看下框架底層是如何運(yùn)行的,以及源碼的閱讀。? ? 這里廣東靚仔給下一些小建議:- 在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計(jì)理念、源碼分層設(shè)計(jì)
- 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
- 借助框架的調(diào)用棧來(lái)進(jìn)行源碼的閱讀,通過(guò)這個(gè)執(zhí)行流程,我們就完整的對(duì)源碼進(jìn)行了一個(gè)初步的了解
- 接下來(lái)再對(duì)源碼執(zhí)行過(guò)程中涉及的所有函數(shù)邏輯梳理一遍
關(guān)注我,一起攜手進(jìn)階
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
