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

二、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ù)器的正常,于是引入了async和defer來優(yōu)化這個問題。再來談?wù)剆cript的默認(rèn),async,defer的之前的差異。默認(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?//?23.函數(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)建請求
- 查找緩存
- 準(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ù)器)
- 頭部壓縮
- 服務(wù)端推送
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無法解決的一些問題。
文件緩存策略
有文件指紋:index.html 用不用緩存,其他用強緩存+文件指紋
無指紋: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用自己的私鑰解密得到了‘對稱密鑰’,以后就通過對稱加密方式交流
通訊流程

瀏覽器發(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 緩存過程
客戶端向服務(wù)端發(fā)起第一次請求資源
服務(wù)端返回資源,并通過響應(yīng)頭返回緩存策略
客服端根據(jù)緩存策略將策略和資源緩存起來
結(jié)論:
瀏覽器每次發(fā)起請求,都會先在瀏覽器緩存中查找該請求的結(jié)果以及緩存標(biāo)識
瀏覽器每次拿到返回的請求結(jié)果都會將該結(jié)果和緩存標(biāo)識存入瀏覽器緩存中

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

緩存位置
- 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)存緩存慢。
Web安全防御
xss 跨域腳本攻擊(利用網(wǎng)站對用戶的信任)
非持久型 - url參數(shù)直接注入 腳本,偽造成正常域名誘惑用戶點擊訪問(瀏覽器默認(rèn)防范?)
持久型 - 存儲到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ū)別
相同點主要
- 都是基于TCP的應(yīng)用層協(xié)議;
- 都使用Request/Response模型進行連接的建立;
- 在連接的建立過程中對錯誤的處理方式相同,在這個階段WS可能返回和HTTP相同的返回碼;
- 都可以在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。
不同之處
- WS使用HTTP來建立連接,但是定義了一系列新的header域,這些域在HTTP中并不會使用;
- WS的連接不能通過中間人來轉(zhuǎn)發(fā),它必須是一個直接連接;
- WS連接建立之后,通信雙方都可以在任何時刻向另一方發(fā)送數(shù)據(jù);
- WS連接建立之后,數(shù)據(jù)的傳輸使用幀來傳遞,不再需要Request消息;
- 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ì)流程:
- 初始化參數(shù):從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù)。
- 開始編譯:用上一步得到的參數(shù)初始化 Compiler 對象,加載所有配置的插件,執(zhí)行對象的 run 方法開始執(zhí)行編譯。
- 確定入口:根據(jù)配置中的 entry 找出所有的入口文件。
- 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理。
- 完成模塊編譯:在經(jīng)過第 4 步使用 Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系。
- 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk轉(zhuǎn)換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機會。
- 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)
簡單流程:
入口文件開始分析:
- 哪些依賴文件
- 轉(zhuǎn)換代碼
遞歸分析依賴代碼
- 哪些依賴文件
- 轉(zhuǎn)換代碼
生成瀏覽器可以識別執(zhí)行的bundle文件
四、Webapck 打包原理
- 通過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}));...'
??}
- 由于生成的代碼還包含了瀏覽器無法識別 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ì)看下圖:

五、常見優(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 常見面試題
熱更新原理
? ? 監(jiān)聽文件變動,通過websocket協(xié)議自動刷新網(wǎng)頁(詳細(xì)內(nèi)容還沒有深入研究)
loader與plugin的區(qū)別
plugin:插件是一個擴展器,目的在于解決 loader 無法實現(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 中,通過 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)
用過哪些可以提高效率的webapck插件
此答案請參考目錄五、常見優(yōu)化手段
實現(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")
}]
}
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()
??]
Webpack的require是如何如何查找依賴的
- 解析相對路徑:查找相對當(dāng)前模塊的路徑下是否有對應(yīng)文件或文件夾是文件則直接加載,是文件夾則繼續(xù)查找文件夾下的 package.json 文件
- 解析模塊名:查找當(dāng)前文件目錄下,父級目錄及以上目錄下的 node_modules 文件夾,看是否有對應(yīng)名稱的模塊
- 解析絕對路徑:直接查找對應(yīng)路徑的文件
八、性能優(yōu)化
性能優(yōu)化思路
- 性能監(jiān)控:lighthouse 插件 和瀏覽器 performance 工具
- 網(wǎng)絡(luò)層面性能優(yōu)化
- 減少HTTP請求次數(shù)(緩存、本地存儲)
- 減少HTTP請求文件體積(文件壓縮)
- 加快HTTP請求速度(CDN)
- 渲染層面性能優(yōu)化
- DOM 優(yōu)化
- CSS 優(yōu)化
- JS 優(yōu)化
- 首屏渲染
- 服務(wù)端渲染
網(wǎng)絡(luò)層面性能優(yōu)化
減少HTTP請求次數(shù)
- 瀏覽器緩存
- Memory Cache
- HTTP Cache
- Service Worker Cache
- Push Cache
- 本地存儲
- Web Storage(容量5-10M)
- indexdb 運行在瀏覽上的非關(guān)系型數(shù)據(jù)庫(容量>100M)
- Cookies (容量4KB)
MemoryCache,是指存在內(nèi)存中的緩存。從優(yōu)先級上來說,它是瀏覽器最先嘗試去命中的一種緩存。從效率上來說,它是響應(yīng)速度最快的一種緩存。Memory Cache一般存儲體積較小的文件。
瀏覽器緩存--HTTP Cache
設(shè)置HTTP Header里面緩存相關(guān)的字段,實現(xiàn)HTTP緩存
強緩存:Cache-Control,expires
private: 僅瀏覽器可以緩存(HTML文件可以設(shè)置避免CDN緩存)Cache-Control 的幾個取值含義:
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
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請求文件體積
- 圖片優(yōu)化
- 壓縮圖片
- 使用webP
- 大圖盡量用JPG代替PNG
- 使用 CSS Sprites雪碧圖(HTTP2不需要)
- 使用iconfont(icon首選)
- 使用base64(小圖解決方案)
- 復(fù)雜圖形地圖等使用SVG
- 圖片懶加載
- 資源打包壓縮(webpack打包抽離公共CSS/JS)
- MiniCssExtractPlugin插件:對CSS進行分離和壓縮
- optimization.splitChunks:抽離公共JS文件
- optimization.minimizer :文件壓縮
- 服務(wù)端開啟gzip壓縮
accept-encoding:gzipGzip 壓縮背后的原理,是在一個文本文件中找出一些重復(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)化
- 有效使用選擇器,避免使用calc表達式、CSS濾鏡
- 減少重排和重繪
- 布局優(yōu)先使用flex
- 盡量避免使用getBoundingClientRect,getComputedStyle等屬性
- 觸發(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)
首屏渲染
- 內(nèi)聯(lián)首屏關(guān)鍵CSS
- 異步加載CSS(動態(tài)插入)
- JS放body 底部,或者異步加載js(defer、async)
- 預(yù)加載 preload
<link?rel="preload"?href="index.js"?as="script">
- 頁面loading動畫
- 骨架圖
vue 層面優(yōu)化
- 引入生產(chǎn)環(huán)境的 Vue 文件
- 使用單文件組件預(yù)編譯模板
- 提取組件的 CSS 到單獨到文件
- 利用Object.freeze()提升性能
- 扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
- 組件懶加載
- 路由懶加載
- 虛擬滾動
九、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 依賴收集過程
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?*/)
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 里通過map has 判斷 dep 的 id 是否已經(jīng)存在了 dep,如果沒有就壓入棧 (過濾重復(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í)行了之后會緩存
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,直到遍歷完成。
new vue() 流程

Vuex 有什么特點
? ? 首先說明 Vuex 是一個專為 Vue 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。Vuex 核心概念重點同步異步實現(xiàn) action mutationVue 組件為什么 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ù)端渲染SSR
使用SSR的好處

有利于SEO
白屏?xí)r間更短
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é)束
(兒子=〉弟弟=〉叔叔)

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)和
提交(不可暫停)

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ù)和合成事件中:
無論調(diào)用多少次setState,都不會立即執(zhí)行更新。而是將要更新的state存入'_pendingStateQuene',將要更新的組件存入'dirtyComponent';
當(dāng)根組件didMount后,批處理機制更新為false。此時再取出'_pendingStateQuene'和'dirtyComponent'中的state和組件進行合并更新;
原生事件和異步代碼中:
原生事件(原生js綁定的事件)不會觸發(fā)react的批處理機制,因而調(diào)用setState會直接更新;
異步代碼中調(diào)用setState,由于js的異步處理機制,異步代碼會暫存,等待同步代碼執(zhí)行完畢再執(zhí)行,此時react的批處理機制已經(jīng)結(jié)束,因而直接更新。
總結(jié):
react會表現(xiàn)出同步和異步(setTimeout/setInterval)的現(xiàn)象,但本質(zhì)上是同步的,是其批處理機制造成了一種異步的假象。(其實完全可以在開發(fā)過程中,在合成事件和生命周期函數(shù)里,完全可以將其視為異步)
React 合成事件
16 事件流(沒有分別注冊捕獲和冒泡事件 bug )


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

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

原理:

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

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