1.7w字總結(jié)前端常規(guī)知識點(diǎn)
關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
一、JS相關(guān)
JS原型及原型鏈
function?Person()?{}
Person.prototype.name?=?'Zaxlct';
Person.prototype.sayName?=?function()?{
??alert(this.name);
}
var?person1?=?new?Person();
//JS 在創(chuàng)建對象的時(shí)候,都有一個(gè)__proto__?的內(nèi)置屬性,用于指向創(chuàng)建它的構(gòu)造函數(shù)的原型對象。
//每個(gè)對象都有?__proto__?屬性,但只有函數(shù)對象才有?prototype?屬性
//?對象?person1?有一個(gè)?__proto__屬性,創(chuàng)建它的構(gòu)造函數(shù)是?Person,構(gòu)造函數(shù)的原型對象是?Person.prototype
console.log(person1.__proto__?==?Person.prototype)?//true
//所有函數(shù)對象的__proto__都指向Function.prototype
String.__proto__?===?Function.prototype??//?true
String.constructor?==?Function?//true
prototype.jpgJS繼承的幾種方式
詳解
- 原型繼承
function?Parent?()?{
??this.name?=?'Parent'
??this.sex?=?'boy'
}
function?Child?()?{
??this.name?=?'child'
}
//?將子類的原型對象指向父類的實(shí)例
Child.prototype?=?new?Parent()
//優(yōu):繼承了父類的模板,又繼承了父類的原型對象
//缺:1.無法實(shí)現(xiàn)多繼承(因?yàn)橐呀?jīng)指定了原型對象了)
//???2.創(chuàng)建子類時(shí),無法向父類構(gòu)造函數(shù)傳參數(shù)
- 構(gòu)造函數(shù)繼承
在子類構(gòu)造函數(shù)內(nèi)部使用call或apply來調(diào)用父類構(gòu)造函數(shù),復(fù)制父類的實(shí)例屬性給子類。
function?Parent?(name)?{
??this.name?=?name
}
function?Child?()?{
??//用.call?來改變?Parent?構(gòu)造函數(shù)內(nèi)的指向
??Parent.call(this,?'child')
}
//優(yōu):解決了原型鏈繼承中子類實(shí)例共享父類引用對象的問題,實(shí)現(xiàn)多繼承,創(chuàng)建子類實(shí)例時(shí),可以向父類傳遞參數(shù)
//缺:構(gòu)造繼承只能繼承父類的實(shí)例屬性和方法,不能繼承父類原型的屬性和方法
組合繼承
組合繼承就是將原型鏈繼承與構(gòu)造函數(shù)繼承組合在一起。
- 使用原型鏈繼承來保證子類能繼承到父類原型中的屬性和方法
- 使用構(gòu)造繼承來保證子類能繼承到父類的實(shí)例屬性和方法
寄生組合繼承
class繼承
在class?中繼承主要是依靠兩個(gè)東西:
extendssuper
class?Parent?{
??constructor?(name)?{
????this.name?=?name
??}
??getName?()?{
????console.log(this.name)
??}
}
class?Child?extends?Parent?{
??constructor?(name)?{
????super(name)
????this.sex?=?'boy'
??}
}
Event Loop 事件循環(huán)
同步與異步、宏任務(wù)和微任務(wù)分別是函數(shù)兩個(gè)不同維度的描述。
同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入任務(wù)隊(duì)列(task queue)的任務(wù),只有等主線程任務(wù)執(zhí)行完畢,任務(wù)隊(duì)列開始通知主線程,請求執(zhí)行任務(wù),該任務(wù)才會進(jìn)入主線程執(zhí)行。
當(dāng)某個(gè)宏任務(wù)執(zhí)行完后,會查看是否有微任務(wù)隊(duì)列。如果有,先執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù);如果沒有,在執(zhí)行環(huán)境棧中會讀取宏任務(wù)隊(duì)列中排在最前的任務(wù);執(zhí)行宏任務(wù)的過程中,遇到微任務(wù),依次加入微任務(wù)隊(duì)列。??蘸?,再次讀取微任務(wù)隊(duì)列里的任務(wù),依次類推。
同步(Promise)>異步(微任務(wù)(process.nextTick ,Promises.then, Promise.catch ,resove,reject,MutationObserver)>宏任務(wù)(setTimeout,setInterval,setImmediate))
await阻塞?后面的代碼執(zhí)行,因此跳出async函數(shù)執(zhí)行下一個(gè)微任務(wù)
Promise 與?Async/Await??區(qū)別
async/await是基于Promise實(shí)現(xiàn)的,看起來更像同步代碼,
- 不需要寫匿名函數(shù)處理Promise的resolve值
- 錯(cuò)誤處理: Async/Await 讓 try/catch 可以同時(shí)處理同步和異步錯(cuò)誤。
- 條件語句也跟錯(cuò)誤處理一樣簡潔一點(diǎn)
- 中間值處理(第一個(gè)方法返回值,用作第二個(gè)方法參數(shù)) 解決嵌套問題
- 調(diào)試方便
const?makeRequest?=?()?=>?{
????try?{
????????getJSON().then(result?=>?{
????????????//?JSON.parse可能會出錯(cuò)
????????????const?data?=?JSON.parse(result)
????????????console.log(data)
????????})
????????//?取消注釋,處理異步代碼的錯(cuò)誤
????????//?.catch((err)?=>?{
????????//???console.log(err)
????????//?})
????}?catch?(err)?{
????????console.log(err)
????}
}
使用aync/await的話,catch能處理JSON.parse錯(cuò)誤:
const?makeRequest?=?async?()?=>?{
????try?{
????????//?this?parse?may?fail
????????const?data?=?JSON.parse(await?getJSON())
????????console.log(data)
????}?catch?(err)?{
????????console.log(err)
????}
}
promise怎么實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用跟返回不同的狀態(tài)
實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用:使用.then()或者.catch()方法之后會返回一個(gè)promise對象,可以繼續(xù)用.then()方法調(diào)用,再次調(diào)用所獲取的參數(shù)是上個(gè)then方法return的內(nèi)容
promise的三種狀態(tài)是?
fulfilled(已成功)/pengding(進(jìn)行中)/rejected(已拒絕)狀態(tài)只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但發(fā)生改變便不可二次修改;
Promise 中使用?
resolve?和?reject?兩個(gè)函數(shù)來更改狀態(tài);then 方法內(nèi)部做的事情就是狀態(tài)判斷:
- 如果狀態(tài)是成功,調(diào)用成功回調(diào)函數(shù)
- 如果狀態(tài)是失敗,調(diào)用失敗回調(diào)函數(shù)
函數(shù)柯里化
柯里化(Currying)?是把接收多個(gè)參數(shù)的原函數(shù)變換成接受一個(gè)單一參數(shù)(原來函數(shù)的第一個(gè)參數(shù)的函數(shù))并返回一個(gè)新的函數(shù),新的函數(shù)能夠接受余下的參數(shù),并返回和原函數(shù)相同的結(jié)果。
- 參數(shù)對復(fù)用
- 提高實(shí)用性
- 延遲執(zhí)行 只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)??吕锘暮瘮?shù)可以延遲接收參數(shù),就是比如一個(gè)函數(shù)需要接收的參數(shù)是兩個(gè),執(zhí)行的時(shí)候必須接收兩個(gè)參數(shù),否則沒法執(zhí)行。但是柯里化后的函數(shù),可以先接收一個(gè)參數(shù)
//?普通的add函數(shù)
function?add(x,?y)?{
????return?x?+?y
}
//?Currying后
function?curryingAdd(x)?{
????return?function?(y)?{
????????return?x?+?y
????}
}
add(1,?2)???????????//?3
curryingAdd(1)(2)???//?3
JS對象深克隆
遞歸遍歷對象,解決循環(huán)引用問題
解決循環(huán)引用問題,我們需要一個(gè)存儲容器存放當(dāng)前對象和拷貝對象的對應(yīng)關(guān)系(適合用key-value的數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲,也就是map),當(dāng)進(jìn)行拷貝當(dāng)前對象的時(shí)候,我們先查找存儲容器是否已經(jīng)拷貝過當(dāng)前對象,如果已經(jīng)拷貝過,那么直接把返回,沒有的話則是繼續(xù)拷貝。
function?deepClone(target)?{
????const?map?=?new?Map()
????function?clone?(target)?{
????????if?(isObject(target))?{
????????????let?cloneTarget?=?isArray(target)???[]?:?{};
????????????if?(map.get(target))?{
????????????????return?map.get(target)
????????????}
????????????map.set(target,cloneTarget)
????????????for?(const?key?in?target)?{
????????????????cloneTarget[key]?=?clone(target[key]);
????????????}
????????????return?cloneTarget;
????????}?else?{
????????????return?target;
????????}
????}
????return?clone(target)
};
JS模塊化
nodeJS里面的模塊是基于commonJS規(guī)范實(shí)現(xiàn)的,原理是文件的讀寫,導(dǎo)出文件要使用exports、module.exports,引入文件用require。每個(gè)文件就是一個(gè)模塊;每個(gè)文件里面的代碼會用默認(rèn)寫在一個(gè)閉包函數(shù)里面AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù),AMD?是?RequireJS?在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
AMD推崇依賴前置,?CMD推崇依賴就近。對于依賴的模塊AMD是提前執(zhí)行,CMD是延遲執(zhí)行。
在ES6中,我們可以使用?import?關(guān)鍵字引入模塊,通過?exprot?關(guān)鍵字導(dǎo)出模塊,但是由于ES6目前無法在瀏覽器中執(zhí)行,所以,我們只能通過babel將不被支持的import編譯為當(dāng)前受到廣泛支持的?require。
CommonJs 和 ES6 模塊化的區(qū)別:
- CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用。
- CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。
前端模塊化:CommonJS,AMD,CMD,ES6
import 和 require 導(dǎo)入的區(qū)別
import 的ES6 標(biāo)準(zhǔn)模塊;require 是 AMD規(guī)范引入方式;
import是編譯時(shí)調(diào)用,所以必須放在文件開頭;是解構(gòu)過程 require是運(yùn)行時(shí)調(diào)用,所以require理論上可以運(yùn)用在代碼的任何地方;是賦值過程。其實(shí)require的結(jié)果就是對象、數(shù)字、字符串、函數(shù)等,再把require的結(jié)果賦值給某個(gè)變量
異步加載JS方式
- 匿名函數(shù)自調(diào)動(dòng)態(tài)創(chuàng)建script標(biāo)簽加載js
(function(){
????var?scriptEle?=?document.createElement("script");
????scriptEle.type?=?"text/javasctipt";
????scriptEle.async?=?true;
????scriptEle.src?=?"http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
????var?x?=?document.getElementsByTagName("head")[0];
????x.insertBefore(scriptEle,?x.firstChild);??
?})();
- async屬性
//?async屬性規(guī)定一旦加載腳本可用,則會異步執(zhí)行
<script?type="text/javascript"?src="xxx.js"?async="async"></script>
- defer屬性
//?defer屬性規(guī)定是否對腳本執(zhí)行進(jìn)行延遲,直到頁面加載為止
<script?type="text/javascript"?src="xxx.js"?defer="defer"></script>
Set、Map、WeakSet、WeakMap
Set對象可以存儲任何類型的數(shù)據(jù)。值是唯一的,沒有重復(fù)的值。
Map對象保存鍵值對,任意值都可以成為它的鍵或值。
WeakSet?結(jié)構(gòu)與?Set?類似,也是不重復(fù)的值的集合 .?WeakMap?對象是一組鍵值對的集合
不同:WeakSet?的成員只能是對象,而不能是其他類型的值。WeakSet 不可遍歷。
WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名。
WeakMap的鍵名所指向的對象,不計(jì)入垃圾回收機(jī)制。
call、apply
call( this,a,b,c )?在第一個(gè)參數(shù)之后的,后續(xù)所有參數(shù)就是傳入該函數(shù)的值。apply( this,[a,b,c] )?只有兩個(gè)參數(shù),第一個(gè)是對象,第二個(gè)是數(shù)組,這個(gè)數(shù)組就是該函數(shù)的參數(shù)。
共同之處:都可以用來代替另一個(gè)對象調(diào)用一個(gè)方法,將一個(gè)函數(shù)的對象上下文從初始的上下文改變?yōu)橛蓆hisObj指定的新對象。
所謂防抖,就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次所謂節(jié)流,就是指連續(xù)觸發(fā)事件但是在 n 秒中只執(zhí)行一次函數(shù)。
addEventListener的第三個(gè)參數(shù)干嘛的,為true時(shí)捕獲,false時(shí)冒泡
Object.prototype.toString.call()?判斷對象類型
//?new?Set是實(shí)現(xiàn)數(shù)組去重,
//?Array.from()把去重之后轉(zhuǎn)換成數(shù)組
let?arr2?=?Array.from(new?Set(arr));
詞法作用域與作用域鏈
作用域規(guī)定了如何查找變量,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。
ES5只有全局作用域沒和函數(shù)作用域,ES6增加塊級作用域
暫時(shí)性死區(qū):在代碼塊內(nèi),使用?let?和?const?命令聲明變量之前,該變量都是不可用的,語法上被稱為暫時(shí)性死區(qū)。
JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。
函數(shù)的作用域在函數(shù)定義的時(shí)候就決定了。
當(dāng)查找變量的時(shí)候,會先從當(dāng)前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個(gè)執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。
new關(guān)鍵字做了4件事:
function?_new(constructor,?...arg)?{
//?創(chuàng)建一個(gè)空對象
??var?obj?=?{};
??//?空對象的`__proto__`指向構(gòu)造函數(shù)的`prototype`,?為這個(gè)新對象添加屬性?
??obj.__proto__?=?constructor.prototype;?
??//?構(gòu)造函數(shù)的作用域賦給新對象
??var?res?=?constructor.apply(obj,?arg);?
??//?返回新對象.如果沒有顯式return語句,則返回this
??return?Object.prototype.toString.call(res)?===?'[object?Object]'???res?:?obj;?
}
不應(yīng)該使用箭頭函數(shù)一些情況:
- 當(dāng)想要函數(shù)被提升時(shí)(箭頭函數(shù)是匿名的)
- 要在函數(shù)中使用
this/arguments時(shí),由于箭頭函數(shù)本身不具有this/arguments,因此它們?nèi)Q于外部上下文 - 使用命名函數(shù)(箭頭函數(shù)是匿名的)
- 使用函數(shù)作為構(gòu)造函數(shù)時(shí)(箭頭函數(shù)沒有構(gòu)造函數(shù))
- 當(dāng)想在對象字面是以將函數(shù)作為屬性添加并在其中使用對象時(shí),因?yàn)樵蹅儫o法訪問?
this?即對象本身。
判斷數(shù)組的四種方法
Array.isArray()?判斷
instanceof?判斷: 檢驗(yàn)構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在對象的原型鏈中,返回一個(gè)布爾值。
let a = []; a instanceof Array; //trueconstructor判斷: 實(shí)例的構(gòu)造函數(shù)屬性constructor指向構(gòu)造函數(shù)
let a = [1,3,4]; a.constructor === Array;//trueObject.prototype.toString.call()?判斷
let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true
TS有什么優(yōu)勢
靜態(tài)輸入:靜態(tài)類型化是一種功能,可以在開發(fā)人員編寫腳本時(shí)檢測錯(cuò)誤。
大型的開發(fā)項(xiàng)目:使用TypeScript工具來進(jìn)行重構(gòu)更變的容易、快捷。
更好的協(xié)作:類型安全是在編碼期間檢測錯(cuò)誤,而不是在編譯項(xiàng)目時(shí)檢測錯(cuò)誤。
更強(qiáng)的生產(chǎn)力:干凈的 ECMAScript 6 代碼,自動(dòng)完成和動(dòng)態(tài)輸入等因素有助于提高開發(fā)人員的工作效率。
interface 和 type的區(qū)別
interface 只能定義對象類型。type聲明可以聲明任何類型。
interface 能夠聲明合并,兩個(gè)相同接口會合并。Type聲明合并會報(bào)錯(cuò)
type可以類型推導(dǎo)
二、框架 Vue | React
Vue3.0 新特性
雙向數(shù)據(jù)綁定 Proxy
代理,可以理解為在對象之前設(shè)置一個(gè)“攔截”,當(dāng)該對象被訪問的時(shí)候,都必須經(jīng)過這層攔截。意味著你可以在這層攔截中進(jìn)行各種操作。比如你可以在這層攔截中對原對象進(jìn)行處理,返回你想返回的數(shù)據(jù)結(jié)構(gòu)。
ES6 原生提供 Proxy 構(gòu)造函數(shù),MDN上的解釋為:Proxy 對象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)。
const?p?=?new?Proxy(target,?handler);
//target:?所要攔截的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)
//handler:一個(gè)對象,定義要攔截的行為
const?p?=?new?Proxy({},?{
????get(target,?propKey)?{
????????return?'哈哈,你被我攔截了';
????}
});
console.log(p.name);
新增的屬性,并不需要重新添加響應(yīng)式處理,因?yàn)?Proxy 是對對象的操作,只要你訪問對象,就會走到 Proxy 的邏輯中。
Vue3 Composition API
Vue3.x?推出了Composition API。setup?是組件內(nèi)使用 Composition API的入口。setup?執(zhí)行時(shí)機(jī)是在?beforeCreate?之前執(zhí)行.
reactive、ref 與 toRefs、isRef
Vue3.x 可以使用reactive和ref來進(jìn)行數(shù)據(jù)定義。
//?props?傳入組件對屬性
//?context?一個(gè)上下文對象,包含了一些有用的屬性:attrs,parent,refs
setup(props,?context)?{
??//?ref?定義數(shù)據(jù)
??const?year?=?ref(0);
??//?reactive?處理對象的雙向綁定
??const?user?=?reactive({?nickname:?"xiaofan",?age:?26,?gender:?"女"?});
??setInterval(()?=>?{
????year.value++;
????user.age++;
??},?1000);
??return?{
????year,
????//?使用toRefs,結(jié)構(gòu)解構(gòu)
????...toRefs(user),
??};
},
//?提供isRef,用于檢查一個(gè)對象是否是ref對象
watchEffect 監(jiān)聽函數(shù)
- watchEffect 不需要手動(dòng)傳入依賴
- watchEffect 會先執(zhí)行一次用來自動(dòng)收集依賴
- watchEffect 無法獲取到變化前的值, 只能獲取變化后的值
computed可傳入get和set
用于定義可更改的計(jì)算屬性
const?plusOne?=?computed({
?get:?()?=>?count.value?+?1,
?set:?val?=>?{?count.value?=?val?-?1?}
});
使用TypeScript和JSX
setup現(xiàn)在支持返回一個(gè)渲染函數(shù),這個(gè)函數(shù)返回一個(gè)JSX,JSX可以直接使用聲明在setup作用域的響應(yīng)式狀態(tài):
export?default?{
?setup()?{
?const?count?=?ref(0);
?return?()?=>?(<div>{count.value}</div>);
?},
};
Vue 跟React 對比?
相同點(diǎn):
- 都有虛擬DOM(Virtual DOM 是一個(gè)映射真實(shí)DOM的JavaScript對象)
- 都提供了響應(yīng)式和組件化的視圖組件。
不同點(diǎn):Vue 是MVVM框架,雙向數(shù)據(jù)綁定,當(dāng)ViewModel對Model進(jìn)行更新時(shí),通過數(shù)據(jù)綁定更新到View。
React是一個(gè)單向數(shù)據(jù)流的庫,狀態(tài)驅(qū)動(dòng)視圖。State --> View --> New State --> New View?ui = render (data)
模板渲染方式不同。React是通過JSX來渲染模板,而Vue是通過擴(kuò)展的HTML來進(jìn)行模板的渲染。
組件形式不同,Vue文件里將HTML,JS,CSS組合在一起。react提供class組件和function組
Vue封裝好了一些v-if,v-for,React什么都是自己實(shí)現(xiàn),自由度更高
Vue 初始化過程,雙向數(shù)據(jù)綁定原理
vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過
Object.defineProperty()來劫持各個(gè)屬性的setter,getter,dep.addSub來收集訂閱的依賴,watcher監(jiān)聽數(shù)據(jù)的變化,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。
監(jiān)聽器Observer,用來劫持并監(jiān)聽所有屬性,如果有變動(dòng)的,就通知訂閱者。訂閱者Watcher,可以收到屬性的變化通知并執(zhí)行相應(yīng)的函數(shù),從而調(diào)用對應(yīng)update更新視圖。
v-model?指令,它能輕松實(shí)現(xiàn)表單輸入和應(yīng)用狀態(tài)之間的雙向綁定。
computed:?支持緩存,只有依賴數(shù)據(jù)結(jié)果發(fā)生改變,才會重新進(jìn)行計(jì)算,不支持異步操作,如果一個(gè)屬性依賴其他屬性,多對一,一般用computed
watch:?數(shù)據(jù)變,直接觸發(fā)相應(yīng)操作,支持異步,監(jiān)聽數(shù)據(jù)必須data中聲明過或者父組件傳遞過來的props中的數(shù)據(jù),當(dāng)數(shù)據(jù)變化時(shí),觸發(fā)其他操作,函數(shù)有兩個(gè)參數(shù)
vue-router實(shí)現(xiàn)原理
端路由簡介以及vue-router實(shí)現(xiàn)原理原理核心就是 更新視圖但不重新請求頁面。路徑之間的切換,也就是組件的切換。vue-router實(shí)現(xiàn)單頁面路由跳轉(zhuǎn)模式:hash模式、history模式。根據(jù)設(shè)置mode參數(shù)
hash模式:通過錨點(diǎn)值的改變,根據(jù)不同的值,渲染指定DOM位置的不同數(shù)據(jù)。每一次改變#后的部分,都會在瀏覽器的訪問歷史中增加一個(gè)記錄,使用”后退”按鈕,就可以回到上一個(gè)位置。history模式:利用?window.history.pushState?API 來完成 URL 跳轉(zhuǎn)而無須重新加載頁面。
vuex實(shí)現(xiàn)原理:
Vue.use(vuex)會調(diào)用vuex的install方法
在beforeCreate鉤子前混入vuexInit方法,vuexInit方法實(shí)現(xiàn)了store注入vue組件實(shí)例,并注冊了vuex?store的引用屬性$store。
Vuex的state狀態(tài)是響應(yīng)式,是借助vue的data是響應(yīng)式,將state存入vue實(shí)例組件的data中;
Vuex的getters則是借助vue的計(jì)算屬性computed實(shí)現(xiàn)數(shù)據(jù)實(shí)時(shí)監(jiān)聽。
nextTick 的原理以及運(yùn)行機(jī)制?
nextTick的源碼分析
vue進(jìn)行DOM更新內(nèi)部也是調(diào)用nextTick來做異步隊(duì)列控制。只要觀察到數(shù)據(jù)變化,Vue 將開啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個(gè) watcher 被多次觸發(fā),只會被推入到隊(duì)列中一次。
DOM至少會在當(dāng)前事件循環(huán)里面的所有數(shù)據(jù)變化完成之后,再統(tǒng)一更新視圖。而當(dāng)我們自己調(diào)用nextTick的時(shí)候,它就在更新DOM的microtask(微任務(wù)隊(duì)列)后追加了我們自己的回調(diào)函數(shù),
從而確保我們的代碼在DOM更新后執(zhí)行,同時(shí)也避免了setTimeout可能存在的多次執(zhí)行問題。確保隊(duì)列中的微任務(wù)在一次事件循環(huán)前被執(zhí)行完畢。
Vue 實(shí)現(xiàn)一個(gè)高階組件
高階組件就是一個(gè)函數(shù),且該函數(shù)接受一個(gè)組件作為參數(shù),并返回一個(gè)新的組件。在不改變對象自身的前提下在程序運(yùn)行期間動(dòng)態(tài)的給對象添加一些額外的屬性或行為。
//?高階組件(HOC)接收到的?props?應(yīng)該透傳給被包裝組件即直接將原組件prop傳給包裝組件
//?高階組件完全可以添加、刪除、修改?props
export?default?function?Console(BaseComponent)?{
??return?{
????props:?BaseComponent.props,
????mounted()?{
??????console.log("高階組件");
????},
????render(h)?{
??????console.log(this);
??????//?將?this.$slots?格式化為數(shù)組,因?yàn)?h?函數(shù)第三個(gè)參數(shù)是子節(jié)點(diǎn),是一個(gè)數(shù)組
??????const?slots?=?Object.keys(this.$slots)
????????.reduce((arr,?key)?=>?arr.concat(this.$slots[key]),?[])
????????.map((vnode)?=>?{
??????????vnode.context?=?this._self;?//?綁定到高階組件上,vm:解決具名插槽被作為默認(rèn)插槽進(jìn)行渲染
??????????return?vnode;
????????});
?
??????//?透傳props、透傳事件、透傳slots
??????return?h(
????????BaseComponent,
????????{
??????????on:?this.$listeners,
??????????attrs:?this.$attrs,?//?attrs?指的是那些沒有被聲明為?props?的屬性
??????????props:?this.$props,
????????},
????????slots
??????);
????},
??};
}
Vue.component()、Vue.use()、this.$xxx()
Vue.component()方法注冊全局組件。
- 第一個(gè)參數(shù)是自定義元素名稱,也就是將來在別的組件中使用這個(gè)組件的標(biāo)簽名稱。
- 第二個(gè)參數(shù)是將要注冊的Vue組件。
import?Vue?from?'vue';
//?引入loading組件?
import?Loading?from?'./loading.vue';
//?將loading注冊為全局組件,在別的組件中通過<loading>標(biāo)簽使用Loading組件
Vue.component('loading',?Loading);
Vue.use注冊插件,這接收一個(gè)參數(shù)。這個(gè)參數(shù)必須具有install方法。Vue.use函數(shù)內(nèi)部會調(diào)用參數(shù)的install方法。
- 如果插件沒有被注冊過,那么注冊成功之后會給插件添加一個(gè)installed的屬性值為true。Vue.use方法內(nèi)部會檢測插件的installed屬性,從而避免重復(fù)注冊插件。
- 插件的install方法將接收兩個(gè)參數(shù),第一個(gè)是參數(shù)是Vue,第二個(gè)參數(shù)是配置項(xiàng)options。
- 在install方法內(nèi)部可以添加全局方法或者屬性
import?Vue?from?'vue';
//?這個(gè)插件必須具有install方法
const?plugin?=?{
??install?(Vue,?options)?{
????//?添加全局方法或者屬性
????Vue.myGlobMethod?=?function?()?{};
????//?添加全局指令
????Vue.directive();
????//?添加混入
????Vue.mixin();
????//?添加實(shí)例方法
????Vue.prototype.$xxx?=?function?()?{};
????//?注冊全局組件
????Vue.component()
??}
}
//?Vue.use內(nèi)部會調(diào)用plugin的install方法
Vue.use(plugin);
將Hello方法掛載到Vue的prototype上.
import?Vue?from?'vue';
import?Hello?from?'./hello.js';
Vue.prototype.$hello?=?Hello;
vue組件中就可以this.$hello('hello world')
Vue父組件傳遞props數(shù)據(jù),子組件修改參數(shù)
- 父子組件傳值時(shí),父組件傳遞的參數(shù),數(shù)組和對象,子組件接受之后可以直接進(jìn)行修改,并且父組件相應(yīng)的值也會修改??刂婆_中發(fā)出警告。
- 如果傳遞的值是字符串,直接修改會報(bào)錯(cuò)。單向數(shù)據(jù)流,每次父級組件發(fā)生更新時(shí),子組件中所有的 prop 都將會刷新為最新的值。
如果子組件想修改prop中數(shù)據(jù):
- 定義一個(gè)局部變量,使用prop的值賦值
- 定義一個(gè)計(jì)算屬性,處理prop的值并返回
Vue父子組件生命周期執(zhí)行順序
加載渲染過程
父beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子組件更新過程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父組件更新過程
父beforeUpdate -> 父updated
銷毀過程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
Vue 自定義指令
自定義指令提供了幾個(gè)鉤子函數(shù):bind:指令第一次綁定到元素時(shí)調(diào)用inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用update:所在組件的 VNode 更新時(shí)調(diào)用
使用slot后可以在子組件內(nèi)顯示插入的新標(biāo)簽
三、webpack 及工程化
webpack的生命周期,及鉤子
compiler(整個(gè)生命周期 [k?m?pa?l?r]) 鉤子 https://webpack.docschina.org/api/compiler-hooks/compilation(編譯 [?kɑ?mp??le??n]) 鉤子
compiler對象包含了Webpack 環(huán)境所有的的配置信息。這個(gè)對象在啟動(dòng) webpack 時(shí)被一次性建立,并配置好所有可操作的設(shè)置,包括 options,loader 和 plugin。當(dāng)在 webpack 環(huán)境中應(yīng)用一個(gè)插件時(shí),插件將收到此 compiler 對象的引用??梢允褂盟鼇碓L問 webpack 的主環(huán)境。
compilation對象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。當(dāng)運(yùn)行webpack 開發(fā)環(huán)境中間件時(shí),每當(dāng)檢測到一個(gè)文件變化,就會創(chuàng)建一個(gè)新的 compilation,從而生成一組新的編譯資源。compilation 對象也提供了很多關(guān)鍵時(shí)機(jī)的回調(diào),以供插件做自定義處理時(shí)選擇使用。
compiler代表了整個(gè)webpack從啟動(dòng)到關(guān)閉的生命周期,而compilation?只是代表了一次新的編譯過程
webpack 編譯過程
Webpack 的編譯流程是一個(gè)串行的過程,從啟動(dòng)到結(jié)束會依次執(zhí)行以下流程:
- 初始化參數(shù):從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù);
- 開始編譯:用上一步得到的參數(shù)初始化?
Compiler?對象,加載所有配置的插件,執(zhí)行對象的?run方法開始執(zhí)行編譯; - 確定入口:根據(jù)配置中的?
entry?找出所有的入口文件; - 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的?
Loader?對模塊進(jìn)行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理; - 完成模塊編譯:在經(jīng)過第4步使用?
Loader?翻譯完所有模塊后,得到了每個(gè)模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系; - 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個(gè)個(gè)包含多個(gè)模塊的
Chunk,再把每個(gè)?Chunk?轉(zhuǎn)換成一個(gè)單獨(dú)的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會; - 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)。
優(yōu)化項(xiàng)目的webpack打包編譯過程
1.構(gòu)建打點(diǎn):構(gòu)建過程中,每一個(gè)Loader?和?Plugin?的執(zhí)行時(shí)長,在編譯 JS、CSS 的 Loader 以及對這兩類代碼執(zhí)行壓縮操作的 Plugin上消耗時(shí)長 。一款工具:speed-measure-webpack-plugin
2.緩存:大部分 Loader 都提供了cache?配置項(xiàng)。cache-loader?,將 loader 的編譯結(jié)果寫入硬盤緩存
3.多核編譯,happypack項(xiàng)目接入多核編譯,理解為happypack?將編譯工作灌滿所有線程
4.抽離,webpack-dll-plugin?將這些靜態(tài)依賴從每一次的構(gòu)建邏輯中抽離出去,靜態(tài)依賴單獨(dú)打包,Externals將不需要打包的靜態(tài)資源從構(gòu)建邏輯中剔除出去,使用CDN 引用
5.tree-shaking,雖然依賴了某個(gè)模塊,但其實(shí)只使用其中的某些功能。通過?tree-shaking,將沒有使用的模塊剔除,來達(dá)到刪除無用代碼的目的。
首屏加載優(yōu)化
路由懶加載:改為用import引用,以函數(shù)的形式動(dòng)態(tài)引入,可以把各自的路由文件分別打包,只有在解析給定的路由時(shí),才會下載路由組件;
element-ui按需加載:引用實(shí)際上用到的組件 ;
組件重復(fù)打包:CommonsChunkPlugin配置來拆包,把使用2次及以上的包抽離出來,放進(jìn)公共依賴文件,首頁也有復(fù)用的組件,也會下載這個(gè)公共依賴文件;
gzip: 拆完包之后,再用gzip做一下壓縮,關(guān)閉sourcemap。
UglifyJsPlugin:??生產(chǎn)環(huán)境,壓縮混淆代碼,移除console代碼
CDN部署靜態(tài)資源:靜態(tài)請求打在nginx時(shí),將獲取靜態(tài)資源的地址進(jìn)行重定向CDN內(nèi)容分發(fā)網(wǎng)絡(luò)
移動(dòng)端首屏加載可以使用骨架屏,自定義loading,首頁單獨(dú)做服務(wù)端渲染。
如何進(jìn)行前端性能優(yōu)化(21種優(yōu)化+7種定位方式)
webpack 熱更新機(jī)制
熱更新流程總結(jié):
啟動(dòng)本地
server,讓瀏覽器可以請求本地的靜態(tài)資源頁面首次打開后,服務(wù)端與客戶端通過 websocket建立通信渠道,把下一次的 hash 返回前端
客戶端獲取到hash,這個(gè)hash將作為下一次請求服務(wù)端 hot-update.js 和 hot-update.json的hash
修改頁面代碼后,Webpack 監(jiān)聽到文件修改后,開始編譯,編譯完成后,發(fā)送 build 消息給客戶端
客戶端獲取到hash,成功后客戶端構(gòu)造hot-update.js script鏈接,然后插入主文檔
hot-update.js 插入成功后,執(zhí)行hotAPI 的 createRecord 和 reload方法,獲取到 Vue 組件的 render方法,重新 render 組件, 繼而實(shí)現(xiàn) UI 無刷新更新。
webpack的 loader和plugin介紹,css-loader,style-loader的區(qū)別
loader?它就是一個(gè)轉(zhuǎn)換器,將A文件進(jìn)行編譯形成B文件,
plugin?,它就是一個(gè)擴(kuò)展器,來操作的是文件,針對是loader結(jié)束后,webpack打包的整個(gè)過程,它并不直接操作文件,會監(jiān)聽webpack打包過程中的某些節(jié)點(diǎn)(run, build-module, program)
Babel?能把ES6/ES7的代碼轉(zhuǎn)化成指定瀏覽器能支持的代碼。
css-loader?的作用是把 css文件進(jìn)行轉(zhuǎn)碼style-loader: 使用<style>將css-loader內(nèi)部樣式注入到我們的HTML頁面
先使用?css-loader轉(zhuǎn)碼,然后再使用?style-loader插入到文件
如何編寫一個(gè)webpack的plugin?
https://segmentfault.com/a/1190000037513682
webpack 插件的組成:
- 一個(gè) JS 命名函數(shù)或一個(gè)類(可以想下我們平時(shí)使用插件就是?
new XXXPlugin()的方式) - 在插件類/函數(shù)的 (prototype) 上定義一個(gè) apply 方法。
- 通過 apply 函數(shù)中傳入 compiler 并插入指定的事件鉤子,在鉤子回調(diào)中取到 compilation 對象
- 通過 compilation 處理 webpack 內(nèi)部特定的實(shí)例數(shù)據(jù)
- 如果是插件是異步的,在插件的邏輯編寫完后調(diào)用 webpack 提供的 callback
為什么 Vite 啟動(dòng)這么快
Webpack 會先打包,然后啟動(dòng)開發(fā)服務(wù)器,請求服務(wù)器時(shí)直接給予打包結(jié)果。
而 Vite 是直接啟動(dòng)開發(fā)服務(wù)器,請求哪個(gè)模塊再對該模塊進(jìn)行實(shí)時(shí)編譯。
Vite 將開發(fā)環(huán)境下的模塊文件,就作為瀏覽器要執(zhí)行的文件,而不是像 Webpack 那樣進(jìn)行打包合并。
由于 Vite 在啟動(dòng)的時(shí)候不需要打包,也就意味著不需要分析模塊的依賴、不需要編譯。因此啟動(dòng)速度非???。當(dāng)瀏覽器請求某個(gè)模塊時(shí),再根據(jù)需要對模塊內(nèi)容進(jìn)行編譯。
你的腳手架是怎么做的
使用?download-git-repo?下載倉庫代碼democommander:完整的?node.js?命令行解決方案。聲明program,使用.option()?方法來定義選項(xiàng)Inquirer.js:命令行用戶界面的集合。
前端監(jiān)控
前端監(jiān)控通常包括行為監(jiān)控(PV/UV,埋點(diǎn)接口統(tǒng)計(jì))、異常監(jiān)控、性能監(jiān)控。
一個(gè)監(jiān)控系統(tǒng),大致可以分為四個(gè)階段:日志采集、日志存儲、統(tǒng)計(jì)與分析、報(bào)告和警告。
錯(cuò)誤監(jiān)控
Vue專門的錯(cuò)誤警告的方法?Vue.config.errorHandler,(Vue提供只能捕獲其頁面生命周期內(nèi)的函數(shù),比如created,mounted)
Vue.config.errorHandler?=?function?(err)?{
console.error(‘Vue.error’,err.stack)
//?邏輯處理
};
框架:betterjs,fundebug(收費(fèi)) 捕獲錯(cuò)誤的腳本要放置在最前面,確??梢允占藉e(cuò)誤信息 方法:
window.onerror()當(dāng)有js運(yùn)行時(shí)錯(cuò)誤觸發(fā)時(shí),onerror可以接受多個(gè)參數(shù)(message, source, lineno, colno, error)。window.addEventListener('error'), function(e) {}, true?會比window.onerror先觸發(fā),不能阻止默認(rèn)事件處理函數(shù)的執(zhí)行,但可以全局捕獲資源加載異常的錯(cuò)誤
前端JS錯(cuò)誤捕獲--sourceMap
如何監(jiān)控網(wǎng)頁崩潰?**崩潰和卡頓有何差別?**監(jiān)控錯(cuò)誤
Service Worker 有自己獨(dú)立的工作線程,與網(wǎng)頁區(qū)分開,網(wǎng)頁崩潰了,Service Worker 一般情況下不會崩潰;
Service Worker 生命周期一般要比網(wǎng)頁還要長,可以用來監(jiān)控網(wǎng)頁的狀態(tài);
卡頓:加載中,渲染遇到阻塞
性能監(jiān)控 && 性能優(yōu)化
性能指標(biāo):
FP(首次繪制)FCP(首次內(nèi)容繪制 First contentful paint)LCP(最大內(nèi)容繪制時(shí)間 Largest contentful paint)FPS(每秒傳輸幀數(shù))TTI(頁面可交互時(shí)間 Time to Interactive)HTTP?請求響應(yīng)時(shí)間DNS?解析時(shí)間TCP?連接時(shí)間
性能數(shù)據(jù)采集需要使用?window.performance API?, ? JS庫?web-vitals:import {getLCP} from 'web-vitals';
????//?重定向耗時(shí)
????redirect:?timing.redirectEnd?-?timing.redirectStart,
????//?DOM?渲染耗時(shí)
????dom:?timing.domComplete?-?timing.domLoading,
????//?頁面加載耗時(shí)
????load:?timing.loadEventEnd?-?timing.navigationStart,
????//?頁面卸載耗時(shí)
????unload:?timing.unloadEventEnd?-?timing.unloadEventStart,
????//?請求耗時(shí)
????request:?timing.responseEnd?-?timing.requestStart,
????//?獲取性能信息時(shí)當(dāng)前時(shí)間
????time:?new?Date().getTime(),
????//?DNS查詢耗時(shí)
????domainLookupEnd?-?domainLookupStart
?//?TCP鏈接耗時(shí)
????connectEnd?-?connectStart
?//?request請求耗時(shí)
????responseEnd?-?responseStart
?//?解析dom樹耗時(shí)
????domComplete?-?domInteractive
?//?白屏?xí)r間
????domloadng?-?fetchStart
?//?onload時(shí)間
????loadEventEnd?-?fetchStart
性能優(yōu)化常用手段:緩存技術(shù)、 ? 預(yù)加載技術(shù)、 ? 渲染方案。
緩存?:主要有 cdn、瀏覽器緩存、本地緩存以及應(yīng)用離線包
預(yù)加載?:資源預(yù)拉?。╬refetch)則是另一種性能優(yōu)化的技術(shù)。通過預(yù)拉取可以告訴瀏覽器用戶在未來可能用到哪些資源。
prefetch支持預(yù)拉取圖片、腳本或者任何可以被瀏覽器緩存的資源。
在head里 添加?
<linkrel="prefetch"href="image.png">prerender是一個(gè)重量級的選項(xiàng),它可以讓瀏覽器提前加載指定頁面的所有資源。
subresource可以用來指定資源是最高優(yōu)先級的。當(dāng)前頁面需要,或者馬上就會用到時(shí)。
- 渲染方案:
- 靜態(tài)渲染(SR)
- 前端渲染(CSR)
- 服務(wù)端渲染(SSR)
- 客戶端渲染(NSR):NSR 數(shù)據(jù)請求,首屏數(shù)據(jù)請求和數(shù)據(jù)線上與 webview 的一個(gè)初始化和框架 JS 初始化并行了起來,大大縮短了首屏?xí)r間。

常見的六種設(shè)計(jì)模式以及應(yīng)用場景
https://www.cnblogs.com/whu-2017/p/9471670.html
觀察者模式的概念
觀察者模式模式,屬于行為型模式的一種,它定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一個(gè)主題對象。這個(gè)主體對象在狀態(tài)變化時(shí),會通知所有的觀察者對象。
發(fā)布訂閱者模式的概念
發(fā)布-訂閱模式,消息的發(fā)送方,叫做發(fā)布者(publishers),消息不會直接發(fā)送給特定的接收者,叫做訂閱者。意思就是發(fā)布者和訂閱者不知道對方的存在。需要一個(gè)第三方組件,叫做信息中介,它將訂閱者和發(fā)布者串聯(lián)起來,它過濾和分配所有輸入的消息。換句話說,發(fā)布-訂閱模式用來處理不同系統(tǒng)組件的信息交流,即使這些組件不知道對方的存在。
需要一個(gè)第三方組件,叫做信息中介,它將訂閱者和發(fā)布者串聯(lián)起來
工廠模式??主要是為創(chuàng)建對象提供了接口。場景:在編碼時(shí)不能預(yù)見需要?jiǎng)?chuàng)建哪種類的實(shí)例。
代理模式 命令模式
單例模式
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。(window)
四、Http 及瀏覽器相關(guān)
七層網(wǎng)絡(luò)模型
應(yīng)用層、表示層、會話層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層、物理層
TCP:面向連接、傳輸可靠(保證數(shù)據(jù)正確性,保證數(shù)據(jù)順序)、用于傳輸大量數(shù)據(jù)(流模式)、速度慢,建立連接需要開銷較多(時(shí)間,系統(tǒng)資源) 。(應(yīng)用場景:HTP,HTTP,郵件)
UDP:面向非連接、傳輸不可靠、用于傳輸少量數(shù)據(jù)(數(shù)據(jù)包模式)、速度快 ,可能丟包(應(yīng)用場景:即時(shí)通訊)
是否連接?????面向連接?????面向非連接
傳輸可靠性???可靠????????不可靠
應(yīng)用場合????少量數(shù)據(jù)????傳輸大量數(shù)據(jù)
https
客戶端先向服務(wù)器端索要公鑰,然后用公鑰加密信息,服務(wù)器收到密文后,用自己的私鑰解密。服務(wù)器公鑰放在數(shù)字證書中。
url到加載渲染全過程
- DNS域名解析。
- TCP三次握手,建立接連。
- 發(fā)送HTTP請求報(bào)文。
- 服務(wù)器處理請求返回響應(yīng)報(bào)文。
- 瀏覽器解析渲染頁面。
- 四次揮手,斷開連接。
DNS 協(xié)議提供通過域名查找 IP地址,或逆向從?IP地址反查域名的服務(wù)。DNS 是一個(gè)網(wǎng)絡(luò)服務(wù)器,我們的域名解析簡單來說就是在 DNS 上記錄一條信息記錄。
TCP 三次握手,四次揮手:握手揮手都是客戶端發(fā)起,客戶端結(jié)束。三次握手與四次揮手詳解
負(fù)載均衡:請求在進(jìn)入到真正的應(yīng)用服務(wù)器前,可能還會先經(jīng)過負(fù)責(zé)負(fù)載均衡的機(jī)器,它的作用是將請求合理地分配到多個(gè)服務(wù)器上,轉(zhuǎn)發(fā)HTTP請求;同時(shí)具備具備防攻擊等功能??煞譃镈NS負(fù)載均衡,HTTP負(fù)載均衡,IP負(fù)載均衡,鏈路層負(fù)載均衡等。
Web Server:請求經(jīng)過前面的負(fù)載均衡后,將進(jìn)入到對應(yīng)服務(wù)器上的 Web Server,比如Apache、Tomcat
反向代理是工作在 HTTP 上的,一般都是?Nginx。全國各地訪問baidu.com就肯定要通過代理訪問,不可能都訪問百度的那臺服務(wù)器。?(VPN正向代理,代理客戶端)
瀏覽器解析渲染過程:返回的html傳遞到瀏覽器后,如果有g(shù)zip會先解壓,找出文件編碼格式,外鏈資源的加載 html從上往下解析,遇到j(luò)s,css停止解析渲染,直到j(luò)s執(zhí)行完成。解析HTML,構(gòu)建DOM樹 解析CSS,生成CSS規(guī)則樹 合并DOM樹和CSS規(guī)則,生成render樹去渲染
不會引起DOM樹變化,頁面布局變化,改變元素樣式的行為叫重繪
引起DOM樹結(jié)構(gòu)變化,頁面布局變化的行為叫回流
GUI渲染線程負(fù)責(zé)渲染瀏覽器界面HTML元素,當(dāng)界面需要?重繪(Repaint)?或由于某種操作引發(fā)?回流(reflow)?時(shí),該線程就會執(zhí)行。在Javascript引擎運(yùn)行腳本期間,GUI渲染線程都是處于掛起狀態(tài)的,也就是說被”凍結(jié)”了. 直到JS程序執(zhí)行完成,才會接著執(zhí)行。因此如果JS執(zhí)行的時(shí)間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞的感覺。JavaScript是可操縱DOM的,如果在修改這些元素屬性同時(shí)渲染界面,渲染前后元素?cái)?shù)據(jù)可能不一致
GPU繪制多進(jìn)程的瀏覽器:主控進(jìn)程,插件進(jìn)程,GPU,tab頁(瀏覽器內(nèi)核)多線程的瀏覽器內(nèi)核:每一個(gè)tab頁面可以看作是瀏覽器內(nèi)核進(jìn)程,然后這個(gè)進(jìn)程是多線程的。
它有幾大類子線程:
- GUI線程
- JS引擎線程
- 事件觸發(fā)線程
- 定時(shí)器線程
- HTTP請求線程
http1 跟HTTP2
http2
多路復(fù)用:相同域名多個(gè)請求,共享同一個(gè)TCP連接,降低了延遲
請求優(yōu)先級:給每個(gè)request設(shè)置優(yōu)先級
二進(jìn)制傳輸;之前是用純文本傳輸
數(shù)據(jù)流:數(shù)據(jù)包不是按順序發(fā)送,對數(shù)據(jù)包做標(biāo)記。每個(gè)請求或回應(yīng)的所有數(shù)據(jù)包成為一個(gè)數(shù)據(jù)流,
服務(wù)端推送:可以主動(dòng)向客戶端發(fā)送消息。
頭部壓縮:減少包的大小跟數(shù)量
HTTP/1.1 中的管道( pipeline)傳輸中如果有一個(gè)請求阻塞了,那么隊(duì)列后請求也統(tǒng)統(tǒng)被阻塞住了 HTTP/2 多請求復(fù)用一個(gè)TCP連接,一旦發(fā)生丟包,就會阻塞住所有的 HTTP 請求。HTTP/3 把 HTTP 下層的 TCP 協(xié)議改成了?UDP!http1 keep alive 串行傳輸
http 中的 keep-alive 有什么作用
響應(yīng)頭中設(shè)置?keep-alive?可以在一個(gè) TCP 連接上發(fā)送多個(gè) http?請求
瀏覽器緩存策略
強(qiáng)緩存:cache-control;no-cache max-age=<10000000>;expires;其中Cache-Conctrol的優(yōu)先級比Expires高;
控制強(qiáng)制緩存的字段分別是Expires和Cache-Control,如果客戶端的時(shí)間小于Expires的值時(shí),直接使用緩存結(jié)果。
協(xié)商緩存:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的優(yōu)先級比Last-Modified / 首次請求,服務(wù)器會在返回的響應(yīng)頭中加上Last-Modified字段,表示資源最后修改的時(shí)間。
瀏覽器再次請求時(shí),請求頭中會帶上If-Modified-Since字段,比較兩個(gè)字段,一樣則證明資源未修改,返回304,否則重新返回資源,狀態(tài)碼為200;
垃圾回收機(jī)制:
標(biāo)記清除:進(jìn)入執(zhí)行環(huán)境的變量都被標(biāo)記,然后執(zhí)行完,清除這些標(biāo)記跟變量。查看變量是否被引用。
引用計(jì)數(shù):會記錄每個(gè)值被引用的次數(shù),當(dāng)引用次數(shù)變成0后,就會被釋放掉。
前端安全
同源策略:如果兩個(gè) URL 的協(xié)議、域名和端口都相同,我們就稱這兩個(gè) URL 同源。因?yàn)闉g覽器有cookies。
XSS:跨站腳本攻擊(Cross Site Scripting)?input, textarea等所有可能輸入文本信息的區(qū)域,輸入
<script src="http://惡意網(wǎng)站"></script>等,提交后信息會存在服務(wù)器中 。CSRF:跨站請求偽造 。引誘用戶打開黑客的網(wǎng)站,在黑客的網(wǎng)站中,利用用戶的登錄狀態(tài)發(fā)起的跨站請求。
A站點(diǎn)img的src=B站點(diǎn)的請求接口,可以訪問;解決:referer攜帶請求來源訪問該頁面后,表單自動(dòng)提交, 模擬完成了一次POST操作,發(fā)送post請求
解決:后端注入一個(gè)
隨機(jī)串到Cookie,前端請求取出隨機(jī)串添加傳給后端。http 劫持:電信運(yùn)營商劫持
SQL注入
點(diǎn)擊劫持:誘使用戶點(diǎn)擊看似無害的按鈕(實(shí)則點(diǎn)擊了透明?
iframe中的按鈕) ,解決后端請求頭加一個(gè)字段?X-Frame-Options文件上傳漏洞?:服務(wù)器未校驗(yàn)上傳的文件
五、CSS 及 HTML
什么是BFC(塊級格式化上下文)、IFC(內(nèi)聯(lián)格式化上下文 )、FFC(彈性盒模型)
BFC(Block formatting context),即塊級格式化上下文,它作為HTML頁面上的一個(gè)獨(dú)立渲染區(qū)域,只有區(qū)域內(nèi)元素參與渲染,且不會影響其外部元素。簡單來說,可以將 BFC 看做是一個(gè)“圍城”,外面的元素進(jìn)不來,里面的元素出不去(互不干擾)。
一個(gè)決定如何渲染元素的容器 ,渲染規(guī)則 :
1、內(nèi)部的塊級元素會在垂直方向,一個(gè)接一個(gè)地放置。
2、塊級元素垂直方向的距離由margin決定。屬于同一個(gè)BFC的兩個(gè)相鄰塊級元素的margin會發(fā)生重疊。
3、對于從左往右的格式化,每個(gè)元素(塊級元素與行內(nèi)元素)的左邊緣,與包含塊的左邊緣相接觸,(對于從右往左的格式化則相反)。即使包含塊中的元素存在浮動(dòng)也是如此,除非其中元素再生成一個(gè)BFC。
4、BFC的區(qū)域不會與浮動(dòng)元素重疊。
5、BFC是一個(gè)隔離的獨(dú)立容器,容器里面的子元素和外面的元素互不影響。
6、計(jì)算BFC容器的高度時(shí),浮動(dòng)元素也參與計(jì)算。
形成BFC的條件:
1、浮動(dòng)元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 為以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
BFC 一般用來解決以下幾個(gè)問題
- 邊距重疊問題
- 消除浮動(dòng)問題
- 自適應(yīng)布局問題
flex: 0 1 auto;?是什么意思?
元素會根據(jù)自身寬高設(shè)置尺寸。它會縮短自身以適應(yīng)?flex?容器,但不會伸長并吸收flex?容器中的額外自由空間來適應(yīng)?flex?容器?。水平的主軸(main axis)和垂直的交叉軸(cross axis)幾個(gè)屬性決定按哪個(gè)軸的排列方向
flex-grow:?0??一個(gè)無單位數(shù)(): 它會被當(dāng)作<flex-grow>的值。flex-shrink:?1??一個(gè)有效的**寬度(width)**值: 它會被當(dāng)作?<flex-basis>的值。flex-basis:?auto??關(guān)鍵字none,auto或initial.
放大比例、縮小比例、分配多余空間之前占據(jù)的主軸空間。
避免CSS全局污染
- scoped 屬性
- css in js
const?styles?=?{
??bar:?{
????backgroundColor:?'#000'
??}
}
const?example?=?(props)=>{
??<div?style={styles.bar}?/>
}
- CSS Modules
- 使用less,盡量少使用全局對選擇器
//?選擇器上>要記得寫,免得污染所有ul下面的li
ul{
??>li{
??? color:red;
??}
}
CSS Modules
阮一峰 CSS Modules
CSS Modules是一種構(gòu)建步驟中的一個(gè)進(jìn)程。通過構(gòu)建工具來使指定class達(dá)到scope的過程。
CSS Modules?允許使用::global(.className)的語法,聲明一個(gè)全局規(guī)則。凡是這樣聲明的class,都不會被編譯成哈希字符串。:local(className): 做 localIdentName 規(guī)則處理,編譯唯一哈希類名。
CSS Modules使用特點(diǎn):
- 不使用選擇器,只使用 class 名來定義樣式
- 不層疊多個(gè) class,只使用一個(gè) class 把所有樣式定義好
- 不嵌套class
盒子模型和?box-sizing?屬性
width: 160px; padding: 20px; border: 8px solid orange;標(biāo)準(zhǔn) box-sizing:?content-box;?元素的總寬度 = 160 + 202 + 82; IE的?border-box:總寬度160
margin/padding取百分比的值時(shí) ,基于父元素的寬度和高度的。
css繪制三角形
- 通過border 處理
//?border?處理
.class?{
????width:?0;
????height:?0;
????border-left:?50px?solid?transparent;
????border-right:?50px?solid?transparent;
????border-bottom:?100px?solid?red;
}
//?寬高+border
div?{
????width:?50px;
????height:?50px;
????border:?2px?solid?orange;
}
- clip-path裁剪獲得
div{
?clip-path:?polygon(0?100%,?50%?0,?100%?100%);
}
- 漸變linear-gradient 實(shí)現(xiàn)
div?{
??width:?200px;
??height:?200px;
??background:linear-gradient(to?bottom?right,?#fff?0%,?#fff?49.9%,?rgba(148,88,255,1)?50%,rgba(185,88,255,1)?100%);
}
CSS實(shí)現(xiàn)了三角形后如何給三角形添加陰影
???
CSS兩列布局的N種實(shí)現(xiàn)
兩列布局分為兩種,一種是左側(cè)定寬、右側(cè)自適應(yīng),另一種是兩列都自適應(yīng)(即左側(cè)寬度由子元素決定,右側(cè)補(bǔ)齊剩余空間)。
- 左側(cè)定寬、右側(cè)自適應(yīng)如何實(shí)現(xiàn)
//?兩個(gè)元素都設(shè)置dislpay:inline-block
.left?{
????display:?inline-block;
????width:?100px;
????height:?200px;
????background-color:?red;
????vertical-align:?top;
}
.right?{
????display:?inline-block;
????width:?calc(100%?-?100px);
????height:?400px;
????background-color:?blue;
????vertical-align:?top;
}
//?兩個(gè)元素設(shè)置浮動(dòng),右側(cè)自適應(yīng)元素寬度使用calc函數(shù)計(jì)算
.left{
????float:?left;
????width:?100px;
????height:?200px;
????background-color:?red;
}
.right{
????float:?left;
????width:?calc(100%?-?100px);
????height:?400px;
????background-color:?blue;
}
//?父元素設(shè)置display:flex,自適應(yīng)元素設(shè)置flex:1
.box{
????height:?600px;
????width:?100%;
????display:?flex;
}
.left{
????width:?100px;
????height:?200px;
????background-color:?red;
}
.right{
????flex:?1;
????height:?400px;
????background-color:?blue;
}
//?父元素相對定位,左側(cè)元素絕對定位,右側(cè)自適應(yīng)元素設(shè)置margin-left的值大于定寬元素的寬度
.left{
????position:?absolute;
????width:?100px;
????height:?200px;
????background-color:?red;
}
.right{
????margin-left:?100px;
????height:?400px;
????background-color:?blue;
}
- 左右兩側(cè)元素都自適應(yīng)
//?flex布局?同上
//?父元素設(shè)置display:grid;?grid-template-columns:auto?1fr;(這個(gè)屬性定義列寬,auto關(guān)鍵字表示由瀏覽器自己決定長度。fr是一個(gè)相對尺寸單位,表示剩余空間做等分)grid-gap:20px(行間距)
.parent{
????display:grid;
????grid-template-columns:auto?1fr;
????grid-gap:20px
}?
.left{
????background-color:?red;
????height:?200px;
}
.right{
????height:300px;
????background-color:?blue;
}
//?浮動(dòng)+BFC???父元素設(shè)置overflow:hidden,左側(cè)定寬元素浮動(dòng),右側(cè)自適應(yīng)元素設(shè)置overflow:auto創(chuàng)建BFC
.box{
????height:?600px;
????width:?100%;
????overflow:?hidden;
}
.left{
????float:?left;
????width:?100px;
????height:?200px;
????background-color:?red;
}
.right{
????overflow:?auto;
????height:?400px;
????background-color:?blue;
}
CSS三列布局
float布局:左邊左浮動(dòng),右邊右浮動(dòng),中間margin:0 100px;
Position布局: 左邊left:0; 右邊right:0; 中間left: 100px; right: 100px;
table布局: 父元素 display: table; 左右 width: 100px; 三個(gè)元素display: table-cell;
彈性(flex)布局:父元素 display: flex; 左右 width: 100px;
網(wǎng)格(gird)布局:
//?gird提供了?gird-template-columns、grid-template-rows屬性讓我們設(shè)置行和列的高、寬
.div{
????width:?100%;
????display:?grid;
????grid-template-rows:?100px;
????grid-template-columns:?300px?auto?300px;
}六、常見場景及問題
app與H5 如何通訊交互的?
//?兼容IOS和安卓
callMobile(parameters,messageHandlerName)?{
??//handlerInterface由iOS addScriptMessageHandler與andorid addJavascriptInterface 代碼注入而來。
??if?(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent))?{
????//?alert('ios')
????window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))
??}?else?{
????//?alert('安卓')
????//安卓傳輸不了js?json對象,只能傳輸string
????window.webkit[messageHandlerName](JSON.stringify(parameters))
??}
}
由app將原生方法注入到window上供js調(diào)用
messageHandlerName?約定的通信方法parameters?需要傳入的參數(shù)
移動(dòng)端適配方案
rem是相對于HTML的根元素em相對于父級元素的字體大小。VW,VH?屏幕寬度高度的高分比
//按照寬度375圖算,?1rem?=?100px;
(function?(win,?doc)?{
???function?changeSize()?{
?????doc.documentElement.style.fontSize?=?doc.documentElement.clientWidth?/?3.75?+?'px';
????console.log(100?*?doc.documentElement.clientWidht?/?3.75)
???}
???changeSize();
???win.addEventListener('resize',?changeSize,?false);
})(window,?document);七、代碼編程相關(guān)
實(shí)現(xiàn)發(fā)布訂閱
/*?Pubsub?*/
function?Pubsub(){
??//存放事件和對應(yīng)的處理方法
??this.handles?=?{};
}
Pubsub.prototype?=?{
??//傳入事件類型type和事件處理handle
??on:?function?(type,?handle)?{
????if(!this.handles[type]){
??????this.handles[type]?=?[];
????}
????this.handles[type].push(handle);
??},
??emit:?function?()?{
????//通過傳入?yún)?shù)獲取事件類型
????//將arguments轉(zhuǎn)為真數(shù)組
????var?type?=?Array.prototype.shift.call(arguments);
????if(!this.handles[type]){
??????return?false;
????}
????for?(var?i?=?0;?i?<?this.handles[type].length;?i++)?{
??????var?handle?=?this.handles[type][i];
??????//執(zhí)行事件
??????handle.apply(this,?arguments);
????}
??},
??off:?function?(type,?handle)?{
????handles?=?this.handles[type];
????if(handles){
??????if(!handle){
????????handles.length?=?0;//清空數(shù)組
??????}else{
??????for?(var?i?=?0;?i?<?handles.length;?i++)?{
????????var?_handle?=?handles[i];
????????if(_handle?===?handle){
??????????//從數(shù)組中刪除
??????????handles.splice(i,1);
????????}
??????}
????}
??}??
}
promise怎么實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用跟返回不同的狀態(tài)
//?MyPromise.js
//?先定義三個(gè)常量表示狀態(tài)
const?PENDING?=?'pending';
const?FULFILLED?=?'fulfilled';
const?REJECTED?=?'rejected';
//?新建?MyPromise?類
class?MyPromise?{
??constructor(executor){
????//?executor?是一個(gè)執(zhí)行器,進(jìn)入會立即執(zhí)行
????//?并傳入resolve和reject方法
????executor(this.resolve,?this.reject)
??}
??//?儲存狀態(tài)的變量,初始值是?pending
??status?=?PENDING;
??// resolve和reject為什么要用箭頭函數(shù)?
??//?如果直接調(diào)用的話,普通函數(shù)this指向的是window或者undefined
??//?用箭頭函數(shù)就可以讓this指向當(dāng)前實(shí)例對象
??//?成功之后的值
??value?=?null;
??//?失敗之后的原因
??reason?=?null;
??//?更改成功后的狀態(tài)
??resolve?=?(value)?=>?{
????//?只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
????if?(this.status?===?PENDING)?{
??????//?狀態(tài)修改為成功
??????this.status?=?FULFILLED;
??????//?保存成功之后的值
??????this.value?=?value;
????}
??}
??//?更改失敗后的狀態(tài)
??reject?=?(reason)?=>?{
????//?只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
????if?(this.status?===?PENDING)?{
??????//?狀態(tài)成功為失敗
??????this.status?=?REJECTED;
??????//?保存失敗后的原因
??????this.reason?=?reason;
????}
??}
????then(onFulfilled,?onRejected)?{
????//?判斷狀態(tài)
????if?(this.status?===?FULFILLED)?{
??????//?調(diào)用成功回調(diào),并且把值返回
??????onFulfilled(this.value);
????}?else?if?(this.status?===?REJECTED)?{
??????//?調(diào)用失敗回調(diào),并且把原因返回
??????onRejected(this.reason);
????}
??}
}
實(shí)現(xiàn)Promise.all
//?Promise.all
function?all(promises)?{
??let?len?=?promises.length,?res?=?[]
??if?(len)?{
????return?new?Promise(function?(resolve,?reject)?{
????????for(let?i=0;?i?<?len;?i++){
????????????let?promise?=?promises[i];
????????????promise.then(response?=>?{
????????????????res[i]?=?response
????????????????//?當(dāng)返回結(jié)果為最后一個(gè)時(shí)
????????????????if?(res.length?===?len)?{
????????????????????resolve(res)
????????????????}
????????????},?error?=>?{
????????????????reject(error)
????????????})
????????}
????})
}
對象數(shù)組轉(zhuǎn)換成tree數(shù)組
>?將entries?按照?level?轉(zhuǎn)換成?result?數(shù)據(jù)結(jié)構(gòu)
const?entries?=?[
????{
????????"province":?"浙江",?"city":?"杭州",?"name":?"西湖"
????},?{
????????"province":?"四川",?"city":?"成都",?"name":?"錦里"
????},?{
????????"province":?"四川",?"city":?"成都",?"name":?"方所"
????},?{
????????"province":?"四川",?"city":?"阿壩",?"name":?"九寨溝"
????}
];
?
const?level?=?["province",?"city",?"name"];
const??result?=?[
?{
??value:'浙江',
??children:[
???{
????value:'杭州',
????children:[
?????{
??????value:'西湖'
?????}
????]
???}
??]
?},
?{
??value:'四川',
??children:[
???{
????value:'成都',
????children:[
?????{
??????value:'錦里'
?????},
?????{
??????value:'方所'
?????}
????]
???},
???{
????value:'阿壩',
????children:[
?????{
??????value:'九寨溝'
?????}
????]
???}
??]
?},
]
思路:涉及到樹形數(shù)組,采用遞歸遍歷的方式
function?transfrom(list,?level)?{
??const?res?=?[];
??list.forEach(item?=>?{
????pushItem(res,?item,?0);
??});
??function?pushItem(arr,?obj,?i)?{
????const?o?=?{
??????value:?obj[level[i]],
??????children:?[],
????};
????//?判斷傳入數(shù)組里是否有value等于要傳入的項(xiàng)
????const?hasItem?=?arr.find(el?=>?el.value?===?obj[level[i]]);
????let?nowArr;
????if(hasItem)?{
??????//?存在,則下一次遍歷傳入存在項(xiàng)的children
??????nowArr?=?hasItem.children;
????}else{
??????//?不存在?壓入arr,下一次遍歷傳入此項(xiàng)的children
??????arr.push(o);
??????nowArr?=?o.children;
????}
????if(i?===?level.length?-?1)?delete?o.children;
????i++;
????if(i?<?level.length)?{
??????//?遞歸進(jìn)行層級的遍歷
??????pushItem(nowArr,?obj,?i);
????}
??}
}
transfrom(entries,?level);
JS instanceof 方法原生實(shí)現(xiàn)
簡單用法
function?Fn?()?{}
const?fn?=?new?Fn()
fn?instanceof?Fn??//?true
實(shí)現(xiàn)如下:
//?left?instanceof?right
function?_instanceof(left,?right)?{
??//?構(gòu)造函數(shù)原型
??const?prototype?=?right.prototype
??//?實(shí)列對象屬性,指向其構(gòu)造函數(shù)原型
??left?=?left.__proto__
??//?查實(shí)原型鏈
??while?(true)?{
????//?如果為null,說明原型鏈已經(jīng)查找到最頂層了,真接返回false
????if?(left?===?null)?{
??????return?false
????}
????//?查找到原型
????if?(prototype?===?left){
??????return?true
????}
????//?繼續(xù)向上查找
????left?=?left.__proto__
??}
}
const?str?=?"abc"
_instanceof(str,?String)?//?true
編程題
將有同樣元素的數(shù)組進(jìn)行合并
//?例如:
const?arr?=?[
????['a',?'b',?'c'],
????['a',?'d'],
????['d',?'e'],
????['f',?'g'],
????['h',?'g'],
????['i']
]
//?運(yùn)行后的返回結(jié)果是:
[
????['a',?'b',?'c',?'d',?'e'],
????['f',?'g',?'h'],
????['i']
]
//?思路一:
const?arr?=?[['a',?'b',?'c'],?['a',?'d'],?['d',?'e'],?['f',?'g'],?['h',?'g'],?['i']]
function?transform(arr){
????let?res?=?[]
????arr?=?arr.map(el?=>?el.sort()).sort()
????const?item?=?arr.reduce((pre,?cur)?=>?{
??????if?(cur.some(el?=>?pre?&&?pre.includes(el)))?{
????????pre?=?pre.concat(cur)
??????}?else?{
????????res.push(pre)
????????pre?=?cur
??????}
??????return?[...new?Set(pre)]
????})
????res.push(item)
????return?res;
}
transform(arr)
//?console.log(transform(arr));
//?思路二:?
function?r?(arr)?{
??const?map?=?new?Map()
??arr.forEach((array,?index)?=>?{
????const?findAlp?=?array.find((v)?=>?map.get(v))
????if?(findAlp)?{
??????const?set?=?map.get(findAlp)
??????array.forEach((alp)?=>?{
????????set.add(alp)
????????const?findAlp2?=?map.get(alp)
????????if?(findAlp2?&&?findAlp2?!==?set)?{
??????????for(const?v?of?findAlp2.values()){
????????????set.add(v)
????????????map.set(v,?set)
??????????}
????????}
????????map.set(alp,?set)
??????})
????}?else?{
??????const?set?=?new?Set(arr[index])
??????array.forEach((alp)?=>?map.set(alp,?set))
????}
??})
??const?set?=?new?Set()
??const?ret?=?[]
??for?(const?[key,?value]?of?map.entries())?{
????if?(set.has(value))?continue
????set.add(value)
????ret.push([...value])
??}
??return?ret
}
本文來自作者@幾米陽光
https://juejin.cn/post/6991724298197008421
八、總結(jié)
在我們閱讀完官方文檔后,我們一定會進(jìn)行更深層次的學(xué)習(xí),比如看下框架底層是如何運(yùn)行的,以及源碼的閱讀。? ? 這里廣東靚仔給下一些小建議:- 在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計(jì)理念、源碼分層設(shè)計(jì)
- 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
- 借助框架的調(diào)用棧來進(jìn)行源碼的閱讀,通過這個(gè)執(zhí)行流程,我們就完整的對源碼進(jìn)行了一個(gè)初步的了解
- 接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍
關(guān)注我,一起攜手進(jìn)階
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
