知识点漏缺总结
模塊化
使用模塊化可以給我們帶來(lái)以下好處
解決命名沖突
提供復(fù)用性
提高代碼可維護(hù)性
Proxy
Proxy 來(lái)替換原本的 Object.defineProperty 來(lái)實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式。 Proxy 是 ES6 中新增的功能,它可以用來(lái)自定義對(duì)象中的操作。
let p = new Proxy(target, handler) 復(fù)制代碼target 代表需要添加代理的對(duì)象 ,handler 用來(lái)自定義對(duì)象中的操作,比如可以用來(lái)自定義 set 或者 get 函數(shù)。
接下來(lái)我們通過(guò) Proxy 來(lái)實(shí)現(xiàn)一個(gè)數(shù)據(jù)響應(yīng)式
let onWatch = (obj, setBind, getLogger) => {let handler = {get(target, property, receiver) {getLogger(target, property)return Reflect.get(target, property, receiver)},set(target, property, value, receiver) {setBind(value, property)return Reflect.set(target, property, value)}}return new Proxy(obj, handler) }let obj = { a: 1 } let p = onWatch(obj,(v, property) => {console.log(`監(jiān)聽(tīng)到屬性${property}改變?yōu)?span id="ozvdkddzhkzd" class="hljs-variable">${v}`)},(target, property) => {console.log(`'${property}' = ${target[property]}`)} ) p.a = 2 // 監(jiān)聽(tīng)到屬性a改變 p.a // 'a' = 2 復(fù)制代碼在上述代碼中,我們通過(guò)自定義 set 和 get 函數(shù)的方式,在原本的邏輯中插入了我們的函數(shù)邏輯,實(shí)現(xiàn)了在對(duì)對(duì)象任何屬性進(jìn)行讀寫時(shí)發(fā)出通知。
當(dāng)然這是簡(jiǎn)單版的響應(yīng)式實(shí)現(xiàn),如果需要實(shí)現(xiàn)一個(gè) Vue 中的響應(yīng)式,需要我們?cè)?get 中收集依賴,在 set 派發(fā)更新,之所以 Vue3.0 要使用 Proxy 替換原本的 API 原因在于 Proxy 無(wú)需一層層遞歸為每個(gè)屬性添加代理,一次即可完成以上操作,性能上更好,并且原本的實(shí)現(xiàn)有一些數(shù)據(jù)更新不能監(jiān)聽(tīng)到,但是 Proxy 可以完美監(jiān)聽(tīng)到任何方式的數(shù)據(jù)改變,唯一缺陷可能就是瀏覽器的兼容性不好了。
reduce
const arr = [1, 2, 3] const sum = arr.reduce((acc, current) => acc + current, 0) console.log(sum) 復(fù)制代碼對(duì)于 reduce 來(lái)說(shuō),它接受兩個(gè)參數(shù),分別是回調(diào)函數(shù)和初始值,接下來(lái)我們來(lái)分解上述代碼中 reduce 的過(guò)程
首先初始值為 0,該值會(huì)在執(zhí)行第一次回調(diào)函數(shù)時(shí)作為第一個(gè)參數(shù)傳入 回調(diào)函數(shù)接受四個(gè)參數(shù),分別為累計(jì)值、當(dāng)前元素、當(dāng)前索引、原數(shù)組
在一次執(zhí)行回調(diào)函數(shù)時(shí),當(dāng)前值和初始值相加得出結(jié)果1,
該結(jié)果會(huì)在第二次執(zhí)行回調(diào)函數(shù)時(shí)當(dāng)做第一個(gè)參數(shù)傳入。
所以在第二次執(zhí)行回調(diào)函數(shù)時(shí),相加的值就分別是 1 和2,以此類推,循環(huán)結(jié)束后得到結(jié)果 6
就通過(guò) reduce 來(lái)實(shí)現(xiàn) map和filter 函數(shù)
const numbers = [10, 20, 30, 40]; const go = numbers.reduce((finalList, num) => {//實(shí)現(xiàn)map方法,映射的作用num = num * 2;if (num > 50) {//實(shí)現(xiàn)filter方法過(guò)濾finalList.push(num);}return finalList; }, []);alert(go); // [60, 80]復(fù)制代碼setInterval
其實(shí)這個(gè)函數(shù)作用和 setTimeout 基本一致,只是該函數(shù)是每隔一段時(shí)間執(zhí)行一次回調(diào)函數(shù)。
通常來(lái)說(shuō)不建議使用 setInterval。第一,它和 setTimeout 一樣,不能保證在預(yù)期的時(shí)間執(zhí)行任務(wù)。第二,它存在執(zhí)行累積的問(wèn)題,請(qǐng)看以下偽代碼
function demo() {setInterval(function(){console.log(2)},1000)sleep(2000) } demo() 復(fù)制代碼以上代碼在瀏覽器環(huán)境中,如果定時(shí)器執(zhí)行過(guò)程中出現(xiàn)了耗時(shí)操作,多個(gè)回調(diào)函數(shù)會(huì)在耗時(shí)操作結(jié)束以后同時(shí)執(zhí)行,這樣可能就會(huì)帶來(lái)性能上的問(wèn)題。
如果你有循環(huán)定時(shí)器的需求,其實(shí)完全可以通過(guò) requestAnimationFrame 來(lái)實(shí)現(xiàn)
特點(diǎn)
【1】requestAnimationFrame會(huì)把每一幀中的所有DOM操作集中起來(lái),在一次重繪或回流中就完成,并且重繪或回流的時(shí)間間隔緊緊跟隨瀏覽器的刷新頻率
??【2】在隱藏或不可見(jiàn)的元素中,requestAnimationFrame將不會(huì)進(jìn)行重繪或回流,這當(dāng)然就意味著更少的CPU、GPU和內(nèi)存使用量
??【3】requestAnimationFrame是由瀏覽器專門為動(dòng)畫提供的API,在運(yùn)行時(shí)瀏覽器會(huì)自動(dòng)優(yōu)化方法的調(diào)用,并且如果頁(yè)面不是激活狀態(tài)下的話,動(dòng)畫會(huì)自動(dòng)暫停,有效節(jié)省了CPU開(kāi)銷
function setInterval(callback, interval) {let timerconst now = Date.nowlet startTime = now()let endTime = startTimeconst loop = () => {timer = window.requestAnimationFrame(loop)endTime = now()if (endTime - startTime >= interval) {startTime = endTime = now()callback(timer)}}timer = window.requestAnimationFrame(loop)return timer }let a = 0 setInterval(timer => {console.log(1)a++if (a === 3) cancelAnimationFrame(timer) }, 1000) 復(fù)制代碼首先 requestAnimationFrame 自帶函數(shù)節(jié)流功能,基本可以保證在 16.6 毫秒內(nèi)只執(zhí)行一次(不掉幀的情況下),并且該函數(shù)的延時(shí)效果是精確的,沒(méi)有其他定時(shí)器時(shí)間不準(zhǔn)的問(wèn)題,當(dāng)然你也可以通過(guò)該函數(shù)來(lái)實(shí)現(xiàn) setTimeout。
進(jìn)程與線程
進(jìn)程和線程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述。放在應(yīng)用上來(lái)說(shuō)就代表了一個(gè)程序。線程是進(jìn)程中的更小單位,描述了執(zhí)行一段指令所需的時(shí)間。
把這些概念拿到瀏覽器中來(lái)說(shuō),當(dāng)你打開(kāi)一個(gè) Tab 頁(yè)時(shí),其實(shí)就是創(chuàng)建了一個(gè)進(jìn)程,一個(gè)進(jìn)程中可以有多個(gè)線程,比如渲染線程、JS 引擎線程、HTTP 請(qǐng)求線程等等。當(dāng)你發(fā)起一個(gè)請(qǐng)求時(shí),其實(shí)就是創(chuàng)建了一個(gè)線程,當(dāng)請(qǐng)求結(jié)束后,該線程可能就會(huì)被銷毀。
bind
和call很相似,第一個(gè)參數(shù)是this的指向,從第二個(gè)參數(shù)開(kāi)始是接收的參數(shù)列表。區(qū)別在于bind方法返回值是函數(shù)以及bind接收的參數(shù)列表的使用。
bind返回值是函數(shù)
var obj = {name: 'Dot' }function printName() {console.log(this.name) }var dot = printName.bind(obj) console.log(dot) // function () { … } dot() // Dot 復(fù)制代碼bind 方法不會(huì)立即執(zhí)行,而是 返回一個(gè)改變了上下文 this 后的函數(shù)。 而原函數(shù) printName 中的 this 并沒(méi)有被改變,依舊指向全局對(duì)象 window。
參數(shù)的使用
function fn(a, b, c) {console.log(a, b, c); } var fn1 = fn.bind(null, 'Dot');fn('A', 'B', 'C'); // A B C fn1('A', 'B', 'C'); // Dot A B fn1('B', 'C'); // Dot B C fn.call(null, 'Dot'); // Dot undefined undefined 復(fù)制代碼call 是把第二個(gè)及以后的參數(shù)作為 fn 方法的實(shí)參傳進(jìn)去,而 fn1 方法的實(shí)參實(shí)則是在 bind 中參數(shù)的基礎(chǔ)上再往后排。
有時(shí)候我們也用bind方法實(shí)現(xiàn)函數(shù)珂里化,以下是一個(gè)簡(jiǎn)單的示例:
var add = function(x) {return function(y) {return x + y;}; };var increment = add(1); var addTen = add(10);increment(2); // 3addTen(2); // 12 復(fù)制代碼在低版本瀏覽器沒(méi)有 bind 方法,我們也可以自己實(shí)現(xiàn)一個(gè)。
if (!Function.prototype.bind) {Function.prototype.bind = function () {var self = this, // 保存原函數(shù)context = [].shift.call(arguments), // 保存需要綁定的this上下文args = [].slice.call(arguments); // 剩余的參數(shù)轉(zhuǎn)為數(shù)組return function () { // 返回一個(gè)新函數(shù)self.apply(context, [].concat.call(args, [].slice.call(arguments)));}} } 復(fù)制代碼call apply bind應(yīng)用場(chǎng)景
求數(shù)組中的最大和最小值
var arr = [1,2,3,89,46]var max = Math.max.apply(null,arr)//89var min = Math.min.apply(null,arr)//1 復(fù)制代碼將類數(shù)組轉(zhuǎn)化為數(shù)組
var trueArr = Array.prototype.slice.call(arrayLike) 復(fù)制代碼數(shù)組追加
var arr1 = [1,2,3]; var arr2 = [4,5,6]; var total = [].push.apply(arr1, arr2);//6 // arr1 [1, 2, 3, 4, 5, 6] // arr2 [4,5,6] 復(fù)制代碼判斷變量類型
function isArray(obj){return Object.prototype.toString.call(obj) == '[object Array]'; } isArray([]) // true isArray('dot') // false復(fù)制代碼利用call和apply做繼承
function Person(name,age){// 這里的this都指向?qū)嵗齮his.name = namethis.age = agethis.sayAge = function(){console.log(this.age)} } function Female(){Person.apply(this,arguments)//將父元素所有方法在這里執(zhí)行一遍就繼承了 } var dot = new Female('Dot',2) 復(fù)制代碼使用 log 代理 console.log
function log(){console.log.apply(console, arguments); } // 當(dāng)然也有更方便的 var log = console.log() 復(fù)制代碼總結(jié)
call、apply和bind函數(shù)存在的區(qū)別:
bind返回對(duì)應(yīng)函數(shù), 便于稍后調(diào)用; apply, call則是立即調(diào)用。 除此外, 在 ES6 的箭頭函數(shù)下, call 和 apply 將失效, 對(duì)于箭頭函數(shù)來(lái)說(shuō):
箭頭函數(shù)體內(nèi)的 this 對(duì)象, 就是定義時(shí)所在的對(duì)象,而不是使用時(shí)所在的對(duì)象;所以不需要類似于var _this = this這種丑陋的寫法
箭頭函數(shù)不可以當(dāng)作構(gòu)造函數(shù),也就是說(shuō)不可以使用 new 命令, 否則會(huì)拋出一個(gè)錯(cuò)誤
箭頭函數(shù)不可以使用 arguments 對(duì)象,,該對(duì)象在函數(shù)體內(nèi)不存在. 如果要用, 可以用 Rest 參數(shù)代替
不可以使用 yield 命令, 因此箭頭函數(shù)不能用作 Generator 函數(shù)
為什么 0.1 + 0.2 != 0.3
因?yàn)?JS 采用 IEEE 754 雙精度版本(64位),0.1 在二進(jìn)制中是無(wú)限循環(huán)的一些數(shù)字,其實(shí)不只是 0.1,其實(shí)很多十進(jìn)制小數(shù)用二進(jìn)制表示都是無(wú)限循環(huán)的。 JS 采用的浮點(diǎn)數(shù)標(biāo)準(zhǔn)卻會(huì)裁剪掉我們的數(shù)字。 那么這些循環(huán)的數(shù)字被裁剪了,就會(huì)出現(xiàn)精度丟失的問(wèn)題,也就造成了 0.1 不再是 0.1 了,而是變成了 0.100000000000000002
解決的辦法有很多,這里我們選用原生提供的方式來(lái)最簡(jiǎn)單的解決問(wèn)題
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true 復(fù)制代碼存儲(chǔ)
cookie,localStorage,sessionStorage,indexDB
對(duì)于 cookie 來(lái)說(shuō),我們還需要注意安全性。 Service WorkerService Worker 是運(yùn)行在瀏覽器背后的獨(dú)立線程,一般可以用來(lái)實(shí)現(xiàn)緩存功能。 使用 Service Worker的話,傳輸協(xié)議必須為 HTTPS。因?yàn)?Service Worker 中涉及到請(qǐng)求攔截,所以必須使用 HTTPS 協(xié)議來(lái)保障安全。
Service Worker 實(shí)現(xiàn)緩存功能一般分為三個(gè)步驟:首先需要先注冊(cè) Service Worker,然后監(jiān)聽(tīng)到 install 事件以后就可以緩存需要的文件,那么在下次用戶訪問(wèn)的時(shí)候就可以通過(guò)攔截請(qǐng)求的方式查詢是否存在緩存,存在緩存的話就可以直接讀取緩存文件,否則就去請(qǐng)求數(shù)據(jù)。
瀏覽器緩存機(jī)制
緩存可以說(shuō)是性能優(yōu)化中簡(jiǎn)單高效的一種優(yōu)化方式了,它可以顯著減少網(wǎng)絡(luò)傳輸所帶來(lái)的損耗。
對(duì)于一個(gè)數(shù)據(jù)請(qǐng)求來(lái)說(shuō),可以分為發(fā)起網(wǎng)絡(luò)請(qǐng)求、后端處理、瀏覽器響應(yīng)三個(gè)步驟。瀏覽器緩存可以幫助我們?cè)诘谝缓偷谌襟E中優(yōu)化性能。比如說(shuō)直接使用緩存而不發(fā)起請(qǐng)求,或者發(fā)起了請(qǐng)求但后端存儲(chǔ)的數(shù)據(jù)和前端一致,那么就沒(méi)有必要再將數(shù)據(jù)回傳回來(lái),這樣就減少了響應(yīng)數(shù)據(jù)。
緩存位置
Service Worker
Memory Cache
Disk Cache
Push Cache
網(wǎng)絡(luò)請(qǐng)求
緩存策略
通常瀏覽器緩存策略分為兩種:強(qiáng)緩存和協(xié)商緩存,并且 緩存策略都是通過(guò)設(shè)置 HTTP Header 來(lái)實(shí)現(xiàn)的。
強(qiáng)緩存
強(qiáng)緩存可以通過(guò)設(shè)置兩種 HTTP Header 實(shí)現(xiàn):Expires 和 Cache-Control 。
強(qiáng)緩存表示在緩存期間不需要請(qǐng)求,state code 為 200。
Expires
Expires: Wed, 22 Oct 2018 08:41:00 GMT Expires 是 HTTP/1 的產(chǎn)物,表示資源會(huì)在 Wed, 22 Oct 2018 08:41:00 GMT 后過(guò)期,需要再次請(qǐng)求。并且 Expires 受限于本地時(shí)間,如果修改了本地時(shí)間,可能會(huì)造成緩存失效。
Cache-control
Cache-control: max-age=30 Cache-Control 出現(xiàn)于 HTTP/1.1,優(yōu)先級(jí)高于 Expires 。該屬性值表示資源會(huì)在 30 秒后過(guò)期,需要再次請(qǐng)求。
協(xié)商緩存
如果緩存過(guò)期了,就需要發(fā)起請(qǐng)求驗(yàn)證資源是否有更新。協(xié)商緩存可以通過(guò)設(shè)置兩種 HTTP Header 實(shí)現(xiàn) :Last-Modified 和 ETag 。
當(dāng)瀏覽器發(fā)起請(qǐng)求驗(yàn)證資源時(shí),如果資源沒(méi)有做改變,那么服務(wù)端就會(huì)返回 304 狀態(tài)碼,并且更新瀏覽器緩存有效期。
Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件最后修改日期,
If-Modified-Since 會(huì)將 Last-Modified 的值發(fā)送給服務(wù)器,詢問(wèn)服務(wù)器在該日期后資源是否有更新,有更新的話就會(huì)將新的資源發(fā)送回來(lái),否則返回 304 狀態(tài)碼。
但是 Last-Modified 存在一些弊端:
如果本地打開(kāi)緩存文件,即使沒(méi)有對(duì)文件進(jìn)行修改,但還是會(huì)造成 Last-Modified 被修改,服務(wù)端不能命中緩存導(dǎo)致發(fā)送相同的資源 因?yàn)?Last-Modified 只能以秒計(jì)時(shí),如果在不可感知的時(shí)間內(nèi)修改完成文件,那么服務(wù)端會(huì)認(rèn)為資源還是命中了,不會(huì)返回正確的資源
因?yàn)橐陨线@些弊端,所以在 HTTP / 1.1 出現(xiàn)了 ETag 。
ETag 和 If-None-Match
ETag 類似于文件指紋,If-None-Match 會(huì)將當(dāng)前 ETag 發(fā)送給服務(wù)器,詢問(wèn)該資源 ETag 是否變動(dòng),有變動(dòng)的話就將新的資源發(fā)送回來(lái)。并且 ETag 優(yōu)先級(jí)比 Last-Modified 高。
以上就是緩存策略的所有內(nèi)容了,看到這里,不知道你是否存在這樣一個(gè)疑問(wèn)。如果什么緩存策略都沒(méi)設(shè)置,那么瀏覽器會(huì)怎么處理?
對(duì)于這種情況,瀏覽器會(huì)采用一個(gè)啟發(fā)式的算法,通常會(huì)取響應(yīng)頭中的 Date 減去 Last-Modified 值的 10% 作為緩存時(shí)間。
實(shí)際場(chǎng)景應(yīng)用緩存策略
頻繁變動(dòng)的資源
對(duì)于頻繁變動(dòng)的資源,首先需要使用 Cache-Control: no-cache
使瀏覽器每次都請(qǐng)求服務(wù)器,然后配合 ETag 或者 Last-Modified 來(lái)驗(yàn)證資源是否有效。這樣的做法雖然不能節(jié)省請(qǐng)求數(shù)量,但是能顯著減少響應(yīng)數(shù)據(jù)大小。
代碼文件
這里特指除了 HTML 外的代碼文件,因?yàn)?HTML 文件一般不緩存或者緩存時(shí)間很短。
一般來(lái)說(shuō),現(xiàn)在都會(huì)使用工具來(lái)打包代碼,那么我們就可以對(duì)文件名進(jìn)行哈希處理,只有當(dāng)代碼修改后才會(huì)生成新的文件名。基于此,我們就可以給代碼文件設(shè)置緩存有效期一年 Cache-Control: max-age=31536000,這樣只有當(dāng) HTML 文件中引入的文件名發(fā)生了改變才會(huì)去下載最新的代碼文件,否則就一直使用緩存。
瀏覽器渲染原理
瀏覽器從網(wǎng)絡(luò)中接收到 HTML 文件然后一系列的轉(zhuǎn)換過(guò)程。
接下來(lái)就是css樹(shù)與dom樹(shù)合并成渲染樹(shù)。渲染樹(shù)只會(huì)包括需要顯示的節(jié)點(diǎn)和這些節(jié)點(diǎn)的樣式信息,如果某個(gè)節(jié)點(diǎn)是 display: none 的,那么就不會(huì)在渲染樹(shù)中顯示。為什么操作 DOM 慢
因?yàn)?DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當(dāng)我們通過(guò) JS 操作 DOM 的時(shí)候,其實(shí) 這個(gè)操作涉及到了兩個(gè)線程之間的通信,那么勢(shì)必會(huì)帶來(lái)一些性能上的損耗。 操作 DOM 次數(shù)一多,也就等同于一直在進(jìn)行線程之間的通信,并且操作 DOM 可能還會(huì)帶來(lái)重繪回流的情況,所以也就導(dǎo)致了性能上的問(wèn)題。
經(jīng)典面試題:插入幾萬(wàn)個(gè) DOM,如何實(shí)現(xiàn)頁(yè)面不卡頓?
大部分人應(yīng)該可以想到通過(guò) requestAnimationFrame (requestAnimationFrame會(huì)把每一幀中的所有DOM操作集中起來(lái),在一次重繪或回流中就完成,并且重繪或回流的時(shí)間間隔緊緊跟隨瀏覽器的刷新頻率)的方式去循環(huán)的插入 DOM。
其實(shí)還有種方式去解決這個(gè)問(wèn)題:虛擬滾動(dòng)(virtualized scroller)。
這種技術(shù)的原理就是只渲染可視區(qū)域內(nèi)的內(nèi)容,非可見(jiàn)區(qū)域的那就完全不渲染了,當(dāng)用戶在滾動(dòng)的時(shí)候就實(shí)時(shí)去替換渲染的內(nèi)容。
什么情況阻塞渲染
首先渲染的前提是生成渲染樹(shù),所以 HTML 和 CSS 肯定會(huì)阻塞渲染。如果你想渲染的越快,你越應(yīng)該降低一開(kāi)始需要渲染的文件大小,并且扁平層級(jí),優(yōu)化選擇器。
然后當(dāng)瀏覽器在解析到 script 標(biāo)簽時(shí),會(huì)暫停構(gòu)建 DOM,完成后才會(huì)從暫停的地方重新開(kāi)始。也就是說(shuō),如果你想首屏渲染的越快,就越不應(yīng)該在首屏就加載 JS 文件,這也是都建議將 script 標(biāo)簽放在 body 標(biāo)簽底部的原因。
當(dāng)然在當(dāng)下,并不是說(shuō) script 標(biāo)簽必須放在底部,因?yàn)槟憧梢越o script 標(biāo)簽添加 defer 或者 async 屬性。
當(dāng) script 標(biāo)簽加上 defer 屬性以后,表示該 JS 文件會(huì)并行下載,但是會(huì)放到 HTML 解析完成后順序執(zhí)行,所以對(duì)于這種情況你可以把 script 標(biāo)簽放在任意位置。
對(duì)于沒(méi)有任何依賴的 JS 文件可以加上 async 屬性,表示 JS 文件下載和解析不會(huì)阻塞渲染。
減少重繪和回流
使用 transform 替代 top
<div class="test"></div> <style>.test {position: absolute;top: 10px;width: 100px;height: 100px;background: red;} </style> <script>setTimeout(() => {// 引起回流document.querySelector('.test').style.top = '100px'}, 1000) </script> 復(fù)制代碼使用 visibility 替換 display: none ,因?yàn)榍罢咧粫?huì)引起重繪,后者會(huì)引發(fā)回流(改變了布局)
不要把節(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量
for(let i = 0; i < 1000; i++) {// 獲取 offsetTop 會(huì)導(dǎo)致回流,因?yàn)樾枰カ@取正確的值console.log(document.querySelector('.test').style.offsetTop) } 復(fù)制代碼不要使用 table 布局,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局
動(dòng)畫實(shí)現(xiàn)的速度的選擇,動(dòng)畫速度越快,回流次數(shù)越多,也可以選擇使用 requestAnimationFrame
CSS 選擇符從右往左匹配查找,所以要避免節(jié)點(diǎn)層級(jí)過(guò)多
將頻繁重繪或者回流的節(jié)點(diǎn)設(shè)置為圖層,圖層能夠阻止該節(jié)點(diǎn)的渲染行為影響別的節(jié)點(diǎn)。比如對(duì)于 video 標(biāo)簽來(lái)說(shuō),瀏覽器會(huì)自動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層。
設(shè)置節(jié)點(diǎn)為圖層的方式有很多,我們可以通過(guò)以下幾個(gè)常用屬性可以生成新圖層
will-change
video、iframe 標(biāo)簽
在不考慮緩存和優(yōu)化網(wǎng)絡(luò)協(xié)議的前提下,考慮可以通過(guò)哪些方式來(lái)最快的渲染頁(yè)面,也就是常說(shuō)的關(guān)鍵渲染路徑,這部分也是性能優(yōu)化中的一塊內(nèi)容。
當(dāng)發(fā)生 DOMContentLoaded 事件后,就會(huì)生成渲染樹(shù),生成渲染樹(shù)就可以進(jìn)行渲染了,這一過(guò)程更大程度上和硬件有關(guān)系了。提示如何加速:
從文件大小考慮
從 script 標(biāo)簽使用上來(lái)考慮
從 CSS、HTML 的代碼書寫上來(lái)考慮
從需要下載的內(nèi)容是否需要在首屏使用上來(lái)考慮
性能優(yōu)化
圖片優(yōu)化
計(jì)算圖片大小
對(duì)于一張 100 * 100 像素的圖片來(lái)說(shuō),圖像上有 10000 個(gè)像素點(diǎn),如果每個(gè)像素的值是 RGBA 存儲(chǔ)的話,那么也就是說(shuō)每個(gè)像素有 4 個(gè)通道,每個(gè)通道 1 個(gè)字節(jié)(8 位 = 1個(gè)字節(jié)),所以該圖片大小大概為 39KB(10000 * 1 * 4 / 1024)。
減少像素點(diǎn)
減少每個(gè)像素點(diǎn)能夠顯示的顏色
圖片加載優(yōu)化
1、修飾圖片完全可以用 CSS 去代替。
2、沒(méi)有必要去加載原圖浪費(fèi)帶寬。一般圖片都用 CDN加載,可以計(jì)算出適配屏幕的寬度,然后去請(qǐng)求相應(yīng)裁剪好的圖片。
3、小圖使用 base64 格式
4、將多個(gè)圖標(biāo)文件整合到一張圖片中(雪碧圖)
5.選擇正確的圖片格式:
對(duì)于能夠顯示 WebP 格式的瀏覽器盡量使用 WebP 格式。因?yàn)?WebP 格式具有更好的圖像數(shù)據(jù)壓縮算法,能帶來(lái)更小的圖片體積, 而且擁有肉眼識(shí)別無(wú)差異的圖像質(zhì)量,缺點(diǎn)就是兼容性并不好 小圖使用 PNG,其實(shí)對(duì)于大部分圖標(biāo)這類圖片,完全可以使用 SVG 代替 照片使用 JPEG
DNS 預(yù)解析
DNS 解析也是需要時(shí)間的,可以通過(guò)預(yù)解析的方式來(lái)預(yù)先獲得域名所對(duì)應(yīng)的 IP。
<link rel="dns-prefetch" href="//yuchengkai.cn"> 復(fù)制代碼防抖節(jié)流也是性能優(yōu)化
預(yù)加載
有些資源不需要馬上用到,但是希望盡早獲取,這時(shí)候就可以使用預(yù)加載。
預(yù)加載其實(shí) 是聲明式的 fetch ,強(qiáng)制瀏覽器請(qǐng)求資源,并且不會(huì)阻塞 onload 事件,可以使用以下代碼開(kāi)啟預(yù)加載
<link rel="preload" href="http://example.com"> 復(fù)制代碼預(yù)加載可以一定程度上降低首屏的加載時(shí)間,因?yàn)榭梢詫⒁恍┎挥绊懯灼恋匾奈募雍蠹虞d,唯一缺點(diǎn)就是兼容性不好。
預(yù)渲染
可以通過(guò)預(yù)渲染將下載的文件預(yù)先在后臺(tái)渲染,可以使用以下代碼開(kāi)啟預(yù)渲染
<link rel="prerender" href="http://example.com"> 復(fù)制代碼預(yù)渲染雖然可以提高頁(yè)面的加載速度,但是要確保該頁(yè)面大概率會(huì)被用戶在之后打開(kāi),否則就是白白浪費(fèi)資源去渲染。
懶加載
懶加載的原理就是只加載可視區(qū)域,但也可以是即將進(jìn)入可視區(qū)域)內(nèi)需要加載的東西。對(duì)于圖片來(lái)說(shuō),先設(shè)置圖片標(biāo)簽的 src 屬性為一張占位圖,將真實(shí)的圖片資源放入一個(gè)自定義屬性中,當(dāng)進(jìn)入可視區(qū)域時(shí),就將自定義屬性替換為 src 屬性,這樣圖片就會(huì)去下載資源,實(shí)現(xiàn)了圖片懶加載。
CDN
CDN 的原理是盡可能的在各個(gè)地方分布機(jī)房緩存數(shù)據(jù),這樣即使我們的根服務(wù)器遠(yuǎn)在國(guó)外,在國(guó)內(nèi)的用戶也可以通過(guò)國(guó)內(nèi)的機(jī)房迅速加載資源。
因此,我們可以將靜態(tài)資源盡量使用 CDN 加載,由于瀏覽器對(duì)于單個(gè)域名有并發(fā)請(qǐng)求上限,可以考慮使用多個(gè) CDN 域名。并且對(duì)于 CDN 加載靜態(tài)資源需要注意 CDN 域名要與主站不同,否則每次請(qǐng)求都會(huì)帶上主站的 Cookie,平白消耗流量。
Webpack 性能優(yōu)化
減少 Webpack 打包時(shí)間
優(yōu)化 Loader
對(duì)于 Loader 來(lái)說(shuō),影響打包效率首當(dāng)其沖必屬 Babel 了。因?yàn)?Babel 會(huì)將代碼轉(zhuǎn)為字符串生成 AST,然后對(duì) AST 繼續(xù)進(jìn)行轉(zhuǎn)變最后再生成新的代碼,項(xiàng)目越大,轉(zhuǎn)換代碼越多,效率就越低。
優(yōu)化方法:
1、首先我們可以優(yōu)化 Loader 的文件搜索范圍,只作用在 JS 代碼上的,然后 node_modules 中使用的代碼都是編譯過(guò)的,所以我們也完全沒(méi)有必要再去處理一遍
2、將 Babel 編譯過(guò)的文件緩存起來(lái)(下次只需要編譯更改過(guò)的代碼文件即可,這樣可以大幅度加快打包時(shí)間)
MVVM
首先先申明一點(diǎn),不管是 React 還是 Vue,它們都不是 MVVM 框架,只是有借鑒 MVVM 的思路。文中拿 Vue 舉例也是為了更好地理解 MVVM 的概念。
傳統(tǒng)的 MVC 架構(gòu)通常是使用控制器更新模型,視圖從模型中獲取數(shù)據(jù)去渲染。當(dāng)用戶有輸入時(shí),會(huì)通過(guò)控制器去更新模型,并且通知視圖進(jìn)行更新。
但是MVC 有一個(gè)巨大的缺陷就是控制器承擔(dān)的責(zé)任太大了,隨著項(xiàng)目愈加復(fù)雜,控制器中的代碼會(huì)越來(lái)越臃腫,導(dǎo)致出現(xiàn)不利于維護(hù)的情況。
在 MVVM 架構(gòu)中,引入了 ViewModel 的概念。ViewModel 只關(guān)心數(shù)據(jù)和業(yè)務(wù)的處理,不關(guān)心 View 如何處理數(shù)據(jù),在這種情況下,View 和 Model 都可以獨(dú)立出來(lái),任何一方改變了也不一定需要改變另一方,并且可以將一些可復(fù)用的邏輯放在一個(gè) ViewModel 中,讓多個(gè) View 復(fù)用這個(gè) ViewModel。
以 Vue 框架來(lái)舉例,ViewModel 就是組件的實(shí)例。View 就是模板,Model 的話在引入 Vuex 的情況下是完全可以和組件分離的。除了以上三個(gè)部分,其實(shí)在 MVVM 中還引入了一個(gè)隱式的 Binder 層,實(shí)現(xiàn)了 View 和 ViewModel 的綁定。
同樣以 Vue 框架來(lái)舉例,這個(gè)隱式的 Binder 層就是 Vue 通過(guò)解析模板中的插值和指令從而實(shí)現(xiàn) View 與 ViewModel 的綁定。對(duì)于 MVVM 來(lái)說(shuō),其實(shí)最重要的并不是通過(guò)雙向綁定或者其他的方式將 View 與 ViewModel 綁定起來(lái),而是通過(guò) ViewModel 將視圖中的狀態(tài)和用戶的行為分離出一個(gè)抽象,這才是 MVVM 的精髓。
路由原理
前端路由實(shí)現(xiàn)起來(lái)其實(shí)很簡(jiǎn)單,本質(zhì)就是監(jiān)聽(tīng) URL 的變化,然后匹配路由規(guī)則,顯示相應(yīng)的頁(yè)面,并且無(wú)須刷新頁(yè)面。目前前端使用的路由就只有兩種實(shí)現(xiàn)方式
Hash 模式
History 模式
Hash 模式
www.test.com/#/ 就是 Hash URL,當(dāng) # 后面的哈希值發(fā)生變化時(shí),可以通過(guò) hashchange 事件來(lái)監(jiān)聽(tīng)到 URL 的變化,從而進(jìn)行跳轉(zhuǎn)頁(yè)面,并且無(wú)論哈希值如何變化,服務(wù)端接收到的 URL 請(qǐng)求永遠(yuǎn)是 www.test.com。
window.addEventListener('hashchange', () => {// ... 具體邏輯 }) 復(fù)制代碼Hash 模式相對(duì)來(lái)說(shuō)更簡(jiǎn)單,并且兼容性也更好。
History 模式
History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改變 URL。
通過(guò) History 模式改變 URL 同樣不會(huì)引起頁(yè)面的刷新,只會(huì)更新瀏覽器的歷史記錄。
// 新增歷史記錄 history.pushState(stateObject, title, URL) // 替換當(dāng)前歷史記錄 history.replaceState(stateObject, title, URL) 復(fù)制代碼當(dāng)用戶做出瀏覽器動(dòng)作時(shí),比如點(diǎn)擊后退按鈕時(shí)會(huì)觸發(fā) popState 事件
window.addEventListener('popstate', e => {// e.state 就是 pushState(stateObject) 中的 stateObjectconsole.log(e.state) }) 復(fù)制代碼兩種模式對(duì)比
Hash 模式只可以更改 # 后面的內(nèi)容,History 模式可以通過(guò) API 設(shè)置任意的同源 URL
History 模式可以通過(guò) API 添加任意類型的數(shù)據(jù)到歷史記錄中,Hash 模式只能更改哈希值,也就是字符串
Hash 模式無(wú)需后端配置,并且兼容性好。History 模式在用戶手動(dòng)輸入地址或者刷新頁(yè)面的時(shí)候會(huì)發(fā)起 URL 請(qǐng)求,后端需要配置 index.html 頁(yè)面用于匹配不到靜態(tài)資源的時(shí)候
computed 和 watch 區(qū)別
computed 是計(jì)算屬性,依賴其他屬性計(jì)算值,來(lái)動(dòng)態(tài)獲得值并且 computed 的值有緩存,只有當(dāng)計(jì)算值變化才會(huì)返回內(nèi)容。
watch 監(jiān)聽(tīng)到值的變化就會(huì)執(zhí)行回調(diào),在回調(diào)中可以進(jìn)行一些復(fù)雜業(yè)務(wù)邏輯操作。
keep-alive 組件有什么作用
如果你需要在組件切換的時(shí)候,保存一些組件的狀態(tài)防止多次渲染,就可以使用 keep-alive 組件包裹需要保存的組件。
對(duì)于 keep-alive 組件來(lái)說(shuō),它擁有兩個(gè)獨(dú)有的生命周期鉤子函數(shù),分別為 activated 和 deactivated 。用 keep-alive 包裹的組件在切換時(shí)不會(huì)進(jìn)行銷毀,而是緩存到內(nèi)存中并執(zhí)行 deactivated 鉤子函數(shù),命中緩存渲染后會(huì)執(zhí)行 actived 鉤子函數(shù)。
Vue進(jìn)階知識(shí)
Object.defineProperty 的缺陷
如果通過(guò)下標(biāo)方式修改數(shù)組數(shù)據(jù)或者給對(duì)象新增屬性并不會(huì)觸發(fā)組件的重新渲染,因?yàn)?Object.defineProperty 不能攔截到這些操作,更精確的來(lái)說(shuō),對(duì)于數(shù)組而言,大部分操作都是攔截不到的
所以 Vue 內(nèi)部通過(guò)重寫函數(shù)的方式解決了這個(gè)問(wèn)題。
編譯過(guò)程
將模板解析為 AST
優(yōu)化 AST
將 AST 轉(zhuǎn)換為 render 函數(shù)
NextTick 原理分析
nextTick 可以讓我們?cè)谙麓?DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),用于獲得更新后的 DOM。
持續(xù)補(bǔ)充
關(guān)于function a()與var a = function()
mili();function mili() {console.log('mili');}mogu();var mogu = function () {console.log('mogu');}; 復(fù)制代碼打印結(jié)果是:mili Typeerror:mogu is not a function
原因 :因?yàn)橥ㄟ^(guò)function a ()這種方式是函數(shù)聲明和賦值都提前了。而通過(guò)var a = function 則只是函數(shù)聲明提前了,而賦值要到執(zhí)行到var a = function這步才會(huì)賦值。
瀏覽器緩存圖解
window.onload和document.ready的區(qū)別
最基本區(qū)別
1.執(zhí)行時(shí)間
window.onload必須等到頁(yè)面內(nèi)包括圖片的所有元素加載完畢后再去執(zhí)行。
$(document).ready()時(shí)DOM結(jié)構(gòu)回執(zhí)完畢后就執(zhí)行,不必等到加載完畢。
2.編寫個(gè)數(shù)不同
window.onload不同同時(shí)編寫多個(gè),如果有多個(gè)window.onload方法,只會(huì)執(zhí)行一個(gè)
$(document).ready()可以同時(shí)編寫多個(gè),并且可以得到執(zhí)行
document.onDOMContentLoaded在頁(yè)面中觸發(fā)[DOMContentLoaded]事件時(shí)觸發(fā)。此時(shí),文檔被加載和解析,并且DOM被完全構(gòu)造,但鏈接的資源(例如圖像,樣式表和子幀)可能尚未被加載。
轉(zhuǎn)載于:https://juejin.im/post/5ca36b7fe51d4501f311a1a6
總結(jié)
- 上一篇: autocad .net开发指南_就业指
- 下一篇: 告诉你银行在年底为存储做的小动作