知識點漏缺總結

mylittleZ發表於2019-04-03

模組化

使用模組化可以給我們帶來以下好處

解決命名衝突

提供複用性

提高程式碼可維護性

Proxy

Proxy 來替換原本的 Object.defineProperty 來實現資料響應式。 Proxy 是 ES6 中新增的功能,它可以用來自定義物件中的操作。

let p = new Proxy(target, handler)
複製程式碼

target 代表需要新增代理的物件 ,handler 用來自定義物件中的操作,比如可以用來自定義 set 或者 get 函式。

接下來我們通過 Proxy 來實現一個資料響應式

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(`監聽到屬性${property}改變為${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 監聽到屬性a改變
p.a // 'a' = 2
複製程式碼

在上述程式碼中,我們通過自定義 set 和 get 函式的方式,在原本的邏輯中插入了我們的函式邏輯,實現了在對物件任何屬性進行讀寫時發出通知。

當然這是簡單版的響應式實現,如果需要實現一個 Vue 中的響應式,需要我們在 get 中收集依賴,在 set 派發更新,之所以 Vue3.0 要使用 Proxy 替換原本的 API 原因在於 Proxy 無需一層層遞迴為每個屬性新增代理,一次即可完成以上操作,效能上更好,並且原本的實現有一些資料更新不能監聽到,但是 Proxy 可以完美監聽到任何方式的資料改變,唯一缺陷可能就是瀏覽器的相容性不好了。

reduce

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)
複製程式碼

對於 reduce 來說,它接受兩個引數,分別是回撥函式和初始值,接下來我們來分解上述程式碼中 reduce 的過程

首先初始值為 0,該值會在執行第一次回撥函式時作為第一個引數傳入 回撥函式接受四個引數,分別為累計值、當前元素、當前索引、原陣列

在一次執行回撥函式時,當前值和初始值相加得出結果1,

該結果會在第二次執行回撥函式時當做第一個引數傳入。

所以在第二次執行回撥函式時,相加的值就分別是 1 和2,以此類推,迴圈結束後得到結果 6

就通過 reduce 來實現 map和filter 函式

const numbers = [10, 20, 30, 40];
const go = numbers.reduce((finalList, num) => {
  //實現map方法,對映的作用
    num = num * 2;
    if (num > 50) {
  //實現filter方法過濾
        finalList.push(num);
    }
    return finalList;
}, []);

alert(go); // [60, 80]

複製程式碼

setInterval

其實這個函式作用和 setTimeout 基本一致,只是該函式是每隔一段時間執行一次回撥函式

通常來說不建議使用 setInterval。第一,它和 setTimeout 一樣,不能保證在預期的時間執行任務。第二,它存在執行累積的問題,請看以下虛擬碼

function demo() {
  setInterval(function(){
    console.log(2)
  },1000)
  sleep(2000)
}
demo()
複製程式碼

以上程式碼在瀏覽器環境中,如果定時器執行過程中出現了耗時操作,多個回撥函式會在耗時操作結束以後同時執行,這樣可能就會帶來效能上的問題。

如果你有迴圈定時器的需求,其實完全可以通過 requestAnimationFrame 來實現

特點

【1】requestAnimationFrame會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中就完成,並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的重新整理頻率

  【2】在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或迴流,這當然就意味著更少的CPU、GPU和記憶體使用量

  【3】requestAnimationFrame是由瀏覽器專門為動畫提供的API,在執行時瀏覽器會自動優化方法的呼叫,並且如果頁面不是啟用狀態下的話,動畫會自動暫停,有效節省了CPU開銷

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const 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)
複製程式碼

首先 requestAnimationFrame 自帶函式節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),並且該函式的延時效果是精確的,沒有其他定時器時間不準的問題,當然你也可以通過該函式來實現 setTimeout。

程式與執行緒

程式和執行緒都是一個時間段的描述,是CPU工作時間段的描述。放在應用上來說就代表了一個程式。執行緒是程式中的更小單位,描述了執行一段指令所需的時間。

把這些概念拿到瀏覽器中來說,當你開啟一個 Tab 頁時,其實就是建立了一個程式,一個程式中可以有多個執行緒,比如渲染執行緒、JS 引擎執行緒、HTTP 請求執行緒等等。當你發起一個請求時,其實就是建立了一個執行緒,當請求結束後,該執行緒可能就會被銷燬。

bind

和call很相似,第一個引數是this的指向,從第二個引數開始是接收的引數列表。區別在於bind方法返回值是函式以及bind接收的引數列表的使用。

bind返回值是函式

var obj = {
    name: 'Dot'
}

function printName() {
    console.log(this.name)
}

var dot = printName.bind(obj)
console.log(dot) // function () { … }
dot()  // Dot
複製程式碼

bind 方法不會立即執行,而是 返回一個改變了上下文 this 後的函式。 而原函式 printName 中的 this 並沒有被改變,依舊指向全域性物件 window。

引數的使用

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
複製程式碼

call 是把第二個及以後的引數作為 fn 方法的實參傳進去,而 fn1 方法的實參實則是在 bind 中引數的基礎上再往後排。

有時候我們也用bind方法實現函式珂里化,以下是一個簡單的示例:

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12
複製程式碼

在低版本瀏覽器沒有 bind 方法,我們也可以自己實現一個。

if (!Function.prototype.bind) {
    Function.prototype.bind = function () {
        var self = this,                        // 儲存原函式
            context = [].shift.call(arguments), // 儲存需要繫結的this上下文
            args = [].slice.call(arguments);    // 剩餘的引數轉為陣列
        return function () {                    // 返回一個新函式
            self.apply(context, [].concat.call(args, [].slice.call(arguments)));
        }
    }
}
複製程式碼

call apply bind應用場景

求陣列中的最大和最小值

var arr = [1,2,3,89,46]

var max = Math.max.apply(null,arr)//89

var min = Math.min.apply(null,arr)//1
複製程式碼

將類陣列轉化為陣列

var trueArr = Array.prototype.slice.call(arrayLike)
複製程式碼

陣列追加

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]
複製程式碼

判斷變數型別

function isArray(obj){
    return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('dot') // false

複製程式碼

利用call和apply做繼承

function Person(name,age){
    // 這裡的this都指向例項
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this,arguments)//將父元素所有方法在這裡執行一遍就繼承了
}
var dot = new Female('Dot',2)
複製程式碼

使用 log 代理 console.log

function log(){
  console.log.apply(console, arguments);
}
// 當然也有更方便的 var log = console.log()
複製程式碼

總結

call、apply和bind函式存在的區別:

bind返回對應函式, 便於稍後呼叫; apply, call則是立即呼叫。 除此外, 在 ES6 的箭頭函式下, call 和 apply 將失效, 對於箭頭函式來說:

箭頭函式體內的 this 物件, 就是定義時所在的物件,而不是使用時所在的物件;所以不需要類似於var _this = this這種醜陋的寫法

箭頭函式不可以當作建構函式,也就是說不可以使用 new 命令, 否則會丟擲一個錯誤

箭頭函式不可以使用 arguments 物件,,該物件在函式體內不存在. 如果要用, 可以用 Rest 引數代替

不可以使用 yield 命令, 因此箭頭函式不能用作 Generator 函式

為什麼 0.1 + 0.2 != 0.3

因為 JS 採用 IEEE 754 雙精度版本(64位),0.1 在二進位制中是無限迴圈的一些數字,其實不只是 0.1,其實很多十進位制小數用二進位制表示都是無限迴圈的。 JS 採用的浮點數標準卻會裁剪掉我們的數字。 那麼這些迴圈的數字被裁剪了,就會出現精度丟失的問題,也就造成了 0.1 不再是 0.1 了,而是變成了 0.100000000000000002

解決的辦法有很多,這裡我們選用原生提供的方式來最簡單的解決問題

parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
複製程式碼

儲存

cookie,localStorage,sessionStorage,indexDB

知識點漏缺總結
對於 cookie 來說,我們還需要注意安全性。

知識點漏缺總結
Service Worker

Service Worker 是執行在瀏覽器背後的獨立執行緒,一般可以用來實現快取功能。 使用 Service Worker的話,傳輸協議必須為 HTTPS。因為 Service Worker 中涉及到請求攔截,所以必須使用 HTTPS 協議來保障安全。

Service Worker 實現快取功能一般分為三個步驟:首先需要先註冊 Service Worker,然後監聽到 install 事件以後就可以快取需要的檔案,那麼在下次使用者訪問的時候就可以通過攔截請求的方式查詢是否存在快取,存在快取的話就可以直接讀取快取檔案,否則就去請求資料。

瀏覽器快取機制

快取可以說是效能優化中簡單高效的一種優化方式了,它可以顯著減少網路傳輸所帶來的損耗。

對於一個資料請求來說,可以分為發起網路請求、後端處理、瀏覽器響應三個步驟瀏覽器快取可以幫助我們在第一和第三步驟中優化效能。比如說直接使用快取而不發起請求,或者發起了請求但後端儲存的資料和前端一致,那麼就沒有必要再將資料回傳回來,這樣就減少了響應資料。

快取位置

Service Worker

Memory Cache

Disk Cache

Push Cache

網路請求

快取策略

通常瀏覽器快取策略分為兩種:強快取和協商快取,並且 快取策略都是通過設定 HTTP Header 來實現的。

強快取

強快取可以通過設定兩種 HTTP Header 實現:Expires 和 Cache-Control 。

強快取表示在快取期間不需要請求,state code 為 200。

Expires

Expires: Wed, 22 Oct 2018 08:41:00 GMT Expires 是 HTTP/1 的產物,表示資源會在 Wed, 22 Oct 2018 08:41:00 GMT 後過期,需要再次請求。並且 Expires 受限於本地時間,如果修改了本地時間,可能會造成快取失效。

Cache-control

Cache-control: max-age=30 Cache-Control 出現於 HTTP/1.1,優先順序高於 Expires 。該屬性值表示資源會在 30 秒後過期,需要再次請求。

協商快取

如果快取過期了,就需要發起請求驗證資源是否有更新。協商快取可以通過設定兩種 HTTP Header 實現 :Last-Modified 和 ETag 。

當瀏覽器發起請求驗證資源時,如果資源沒有做改變,那麼服務端就會返回 304 狀態碼,並且更新瀏覽器快取有效期。

Last-Modified 和 If-Modified-Since

Last-Modified 表示本地檔案最後修改日期

If-Modified-Since 會將 Last-Modified 的值傳送給伺服器,詢問伺服器在該日期後資源是否有更新,有更新的話就會將新的資源傳送回來,否則返回 304 狀態碼。

但是 Last-Modified 存在一些弊端:

如果本地開啟快取檔案,即使沒有對檔案進行修改,但還是會造成 Last-Modified 被修改,服務端不能命中快取導致傳送相同的資源 因為 Last-Modified 只能以秒計時,如果在不可感知的時間內修改完成檔案,那麼服務端會認為資源還是命中了,不會返回正確的資源

因為以上這些弊端,所以在 HTTP / 1.1 出現了 ETag 。

ETag 和 If-None-Match

ETag 類似於檔案指紋,If-None-Match 會將當前 ETag 傳送給伺服器,詢問該資源 ETag 是否變動,有變動的話就將新的資源傳送回來。並且 ETag 優先順序比 Last-Modified 高。

以上就是快取策略的所有內容了,看到這裡,不知道你是否存在這樣一個疑問。如果什麼快取策略都沒設定,那麼瀏覽器會怎麼處理?

對於這種情況,瀏覽器會採用一個啟發式的演算法,通常會取響應頭中的 Date 減去 Last-Modified 值的 10% 作為快取時間。

實際場景應用快取策略

頻繁變動的資源

對於頻繁變動的資源,首先需要使用 Cache-Control: no-cache

使瀏覽器每次都請求伺服器,然後配合 ETag 或者 Last-Modified 來驗證資源是否有效。這樣的做法雖然不能節省請求數量,但是能顯著減少響應資料大小。

程式碼檔案

這裡特指除了 HTML 外的程式碼檔案,因為 HTML 檔案一般不快取或者快取時間很短。

一般來說,現在都會使用工具來打包程式碼,那麼我們就可以對檔名進行雜湊處理,只有當程式碼修改後才會生成新的檔名。基於此,我們就可以給程式碼檔案設定快取有效期一年 Cache-Control: max-age=31536000,這樣只有當 HTML 檔案中引入的檔名發生了改變才會去下載最新的程式碼檔案,否則就一直使用快取。

瀏覽器渲染原理

瀏覽器從網路中接收到 HTML 檔案然後一系列的轉換過程。

知識點漏缺總結
接下來就是css樹與dom樹合併成渲染樹。渲染樹只會包括需要顯示的節點和這些節點的樣式資訊,如果某個節點是 display: none 的,那麼就不會在渲染樹中顯示。

為什麼操作 DOM 慢

因為 DOM 是屬於渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們通過 JS 操作 DOM 的時候,其實 這個操作涉及到了兩個執行緒之間的通訊,那麼勢必會帶來一些效能上的損耗。 操作 DOM 次數一多,也就等同於一直在進行執行緒之間的通訊,並且操作 DOM 可能還會帶來重繪迴流的情況,所以也就導致了效能上的問題。

經典面試題:插入幾萬個 DOM,如何實現頁面不卡頓?

大部分人應該可以想到通過 requestAnimationFrame (requestAnimationFrame會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中就完成,並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的重新整理頻率)的方式去迴圈的插入 DOM。

其實還有種方式去解決這個問題:虛擬滾動(virtualized scroller)。

這種技術的原理就是只渲染可視區域內的內容,非可見區域的那就完全不渲染了,當使用者在滾動的時候就實時去替換渲染的內容。

什麼情況阻塞渲染

首先渲染的前提是生成渲染樹,所以 HTML 和 CSS 肯定會阻塞渲染。如果你想渲染的越快,你越應該降低一開始需要渲染的檔案大小,並且扁平層級,優化選擇器。

然後當瀏覽器在解析到 script 標籤時,會暫停構建 DOM,完成後才會從暫停的地方重新開始。也就是說,如果你想首屏渲染的越快,就越不應該在首屏就載入 JS 檔案,這也是都建議將 script 標籤放在 body 標籤底部的原因。

當然在當下,並不是說 script 標籤必須放在底部,因為你可以給 script 標籤新增 defer 或者 async 屬性。

當 script 標籤加上 defer 屬性以後,表示該 JS 檔案會並行下載,但是會放到 HTML 解析完成後順序執行,所以對於這種情況你可以把 script 標籤放在任意位置。

對於沒有任何依賴的 JS 檔案可以加上 async 屬性,表示 JS 檔案下載和解析不會阻塞渲染。

減少重繪和迴流

使用 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>
複製程式碼

使用 visibility 替換 display: none ,因為前者只會引起重繪,後者會引發迴流(改變了佈局)

不要把節點的屬性值放在一個迴圈裡當成迴圈裡的變數

for(let i = 0; i < 1000; i++) {
    // 獲取 offsetTop 會導致迴流,因為需要去獲取正確的值
    console.log(document.querySelector('.test').style.offsetTop)
}
複製程式碼

不要使用 table 佈局,可能很小的一個小改動會造成整個 table 的重新佈局

動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也可以選擇使用 requestAnimationFrame

CSS 選擇符從右往左匹配查詢,所以要避免節點層級過多

將頻繁重繪或者回流的節點設定為圖層,圖層能夠阻止該節點的渲染行為影響別的節點。比如對於 video 標籤來說,瀏覽器會自動將該節點變為圖層。

設定節點為圖層的方式有很多,我們可以通過以下幾個常用屬性可以生成新圖層

will-change

video、iframe 標籤

在不考慮快取和優化網路協議的前提下,考慮可以通過哪些方式來最快的渲染頁面,也就是常說的關鍵渲染路徑,這部分也是效能優化中的一塊內容。

知識點漏缺總結
當發生 DOMContentLoaded 事件後,就會生成渲染樹,生成渲染樹就可以進行渲染了,這一過程更大程度上和硬體有關係了。

提示如何加速:

從檔案大小考慮

從 script 標籤使用上來考慮

從 CSS、HTML 的程式碼書寫上來考慮

從需要下載的內容是否需要在首屏使用上來考慮

效能優化

圖片優化

計算圖片大小

對於一張 100 * 100 畫素的圖片來說,影象上有 10000 個畫素點,如果每個畫素的值是 RGBA 儲存的話,那麼也就是說每個畫素有 4 個通道,每個通道 1 個位元組(8 位 = 1個位元組),所以該圖片大小大概為 39KB(10000 * 1 * 4 / 1024)。

減少畫素點

減少每個畫素點能夠顯示的顏色

圖片載入優化

1、修飾圖片完全可以用 CSS 去代替。

2、沒有必要去載入原圖浪費頻寬。一般圖片都用 CDN載入,可以計算出適配螢幕的寬度,然後去請求相應裁剪好的圖片。

3、小圖使用 base64 格式

4、將多個圖示檔案整合到一張圖片中(雪碧圖)

5.選擇正確的圖片格式:

對於能夠顯示 WebP 格式的瀏覽器儘量使用 WebP 格式。因為 WebP 格式具有更好的影象資料壓縮演算法,能帶來更小的圖片體積, 而且擁有肉眼識別無差異的影象質量,缺點就是相容性並不好 小圖使用 PNG,其實對於大部分圖示這類圖片,完全可以使用 SVG 代替 照片使用 JPEG

DNS 預解析

DNS 解析也是需要時間的,可以通過預解析的方式來預先獲得域名所對應的 IP。

<link rel="dns-prefetch" href="//yuchengkai.cn">
複製程式碼

防抖節流也是效能優化

預載入

有些資源不需要馬上用到,但是希望儘早獲取,這時候就可以使用預載入。

預載入其實 是宣告式的 fetch強制瀏覽器請求資源,並且不會阻塞 onload 事件,可以使用以下程式碼開啟預載入

<link rel="preload" href="http://example.com">
複製程式碼

預載入可以一定程度上降低首屏的載入時間,因為可以將一些不影響首屏但重要的檔案延後載入,唯一缺點就是相容性不好。

預渲染

可以通過預渲染將下載的檔案預先在後臺渲染,可以使用以下程式碼開啟預渲染

<link rel="prerender" href="http://example.com"> 
複製程式碼

預渲染雖然可以提高頁面的載入速度,但是要確保該頁面大概率會被使用者在之後開啟,否則就是白白浪費資源去渲染。

懶載入

懶載入的原理就是隻載入可視區域,但也可以是即將進入可視區域)內需要載入的東西。對於圖片來說,先設定圖片標籤的 src 屬性為一張佔點陣圖,將真實的圖片資源放入一個自定義屬性中,當進入可視區域時,就將自定義屬性替換為 src 屬性,這樣圖片就會去下載資源,實現了圖片懶載入。

CDN

CDN 的原理是儘可能的在各個地方分佈機房快取資料,這樣即使我們的根伺服器遠在國外,在國內的使用者也可以通過國內的機房迅速載入資源。

因此,我們可以將靜態資源儘量使用 CDN 載入,由於瀏覽器對於單個域名有併發請求上限,可以考慮使用多個 CDN 域名。並且對於 CDN 載入靜態資源需要注意 CDN 域名要與主站不同,否則每次請求都會帶上主站的 Cookie,平白消耗流量。

Webpack 效能優化

減少 Webpack 打包時間

優化 Loader

對於 Loader 來說,影響打包效率首當其衝必屬 Babel 了。因為 Babel 會將程式碼轉為字串生成 AST,然後對 AST 繼續進行轉變最後再生成新的程式碼,專案越大,轉換程式碼越多,效率就越低。

優化方法:

1、首先我們可以優化 Loader 的檔案搜尋範圍,只作用在 JS 程式碼上的,然後 node_modules 中使用的程式碼都是編譯過的,所以我們也完全沒有必要再去處理一遍

2、將 Babel 編譯過的檔案快取起來(下次只需要編譯更改過的程式碼檔案即可,這樣可以大幅度加快打包時間)

MVVM

首先先申明一點,不管是 React 還是 Vue,它們都不是 MVVM 框架,只是有借鑑 MVVM 的思路。文中拿 Vue 舉例也是為了更好地理解 MVVM 的概念。

傳統的 MVC 架構通常是使用控制器更新模型,檢視從模型中獲取資料去渲染。當使用者有輸入時,會通過控制器去更新模型,並且通知檢視進行更新。

知識點漏缺總結

但是MVC 有一個巨大的缺陷就是控制器承擔的責任太大了,隨著專案愈加複雜,控制器中的程式碼會越來越臃腫,導致出現不利於維護的情況。

在 MVVM 架構中,引入了 ViewModel 的概念。ViewModel 只關心資料和業務的處理,不關心 View 如何處理資料,在這種情況下,View 和 Model 都可以獨立出來,任何一方改變了也不一定需要改變另一方,並且可以將一些可複用的邏輯放在一個 ViewModel 中,讓多個 View 複用這個 ViewModel。

知識點漏缺總結
以 Vue 框架來舉例,ViewModel 就是元件的例項。View 就是模板,Model 的話在引入 Vuex 的情況下是完全可以和元件分離的。

除了以上三個部分,其實在 MVVM 中還引入了一個隱式的 Binder 層,實現了 View 和 ViewModel 的繫結。

知識點漏缺總結
同樣以 Vue 框架來舉例,這個隱式的 Binder 層就是 Vue 通過解析模板中的插值和指令從而實現 View 與 ViewModel 的繫結。

對於 MVVM 來說,其實最重要的並不是通過雙向繫結或者其他的方式將 View 與 ViewModel 繫結起來,而是通過 ViewModel 將檢視中的狀態和使用者的行為分離出一個抽象,這才是 MVVM 的精髓。

路由原理

前端路由實現起來其實很簡單,本質就是監聽 URL 的變化,然後匹配路由規則,顯示相應的頁面,並且無須重新整理頁面。目前前端使用的路由就只有兩種實現方式

Hash 模式

History 模式

Hash 模式

www.test.com/#/ 就是 Hash URL,當 # 後面的雜湊值發生變化時,可以通過 hashchange 事件來監聽到 URL 的變化,從而進行跳轉頁面,並且無論雜湊值如何變化,服務端接收到的 URL 請求永遠是 www.test.com。

window.addEventListener('hashchange', () => {
  // ... 具體邏輯
})
複製程式碼

Hash 模式相對來說更簡單,並且相容性也更好。

History 模式

History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改變 URL。

通過 History 模式改變 URL 同樣不會引起頁面的重新整理,只會更新瀏覽器的歷史記錄。

// 新增歷史記錄
history.pushState(stateObject, title, URL)
// 替換當前歷史記錄
history.replaceState(stateObject, title, URL)
複製程式碼

當使用者做出瀏覽器動作時,比如點選後退按鈕時會觸發 popState 事件

window.addEventListener('popstate', e => {
  // e.state 就是 pushState(stateObject) 中的 stateObject
  console.log(e.state)
})
複製程式碼

兩種模式對比

Hash 模式只可以更改 # 後面的內容,History 模式可以通過 API 設定任意的同源 URL

History 模式可以通過 API 新增任意型別的資料到歷史記錄中,Hash 模式只能更改雜湊值,也就是字串

Hash 模式無需後端配置,並且相容性好。History 模式在使用者手動輸入地址或者重新整理頁面的時候會發起 URL 請求,後端需要配置 index.html 頁面用於匹配不到靜態資源的時候

computed 和 watch 區別

computed 是計算屬性,依賴其他屬性計算值,來動態獲得值並且 computed 的值有快取,只有當計算值變化才會返回內容。

watch 監聽到值的變化就會執行回撥,在回撥中可以進行一些複雜業務邏輯操作。

keep-alive 元件有什麼作用

如果你需要在元件切換的時候,儲存一些元件的狀態防止多次渲染,就可以使用 keep-alive 元件包裹需要儲存的元件。

對於 keep-alive 元件來說,它擁有兩個獨有的生命週期鉤子函式,分別為 activated 和 deactivated 。用 keep-alive 包裹的元件在切換時不會進行銷燬,而是快取到記憶體中並執行 deactivated 鉤子函式,命中快取渲染後會執行 actived 鉤子函式。

Vue進階知識

Object.defineProperty 的缺陷

如果通過下標方式修改陣列資料或者給物件新增屬性並不會觸發元件的重新渲染,因為 Object.defineProperty 不能攔截到這些操作,更精確的來說,對於陣列而言,大部分操作都是攔截不到的

所以 Vue 內部通過重寫函式的方式解決了這個問題。

編譯過程

將模板解析為 AST

優化 AST

將 AST 轉換為 render 函式

NextTick 原理分析

nextTick 可以讓我們在下次 DOM 更新迴圈結束之後執行延遲迴調,用於獲得更新後的 DOM。

持續補充

關於function a()與var a = function()

 mili();
    function mili() {
        console.log('mili');
    }

    mogu();
    var mogu = function () {
        console.log('mogu');
    };
複製程式碼

列印結果是:mili Typeerror:mogu is not a function

原因 :因為通過function a ()這種方式是函式宣告和賦值都提前了。而通過var a = function 則只是函式宣告提前了,而賦值要到執行到var a = function這步才會賦值。

瀏覽器快取圖解

知識點漏缺總結