前端面試-複習篇上

一、CSS

#1. 盒模型

頁面渲染時,dom 元素所採用的 佈局模型。可通過box-sizing進行設置。根據計算寬高的區域可分爲

  • content-box (W3C 標準盒模型)
  • border-box (IE 盒模型)
  • padding-box
  • margin-box (瀏覽器未實現)

#2. BFC

塊級格式化上下文,是一個獨立的渲染區域,讓處於 BFC 內部的元素與外部的元素相互隔離,使內外元素的定位不會相互影響。

IE下爲 Layout,可通過 zoom:1 觸發

觸發條件:

  • 根元素
  • position: absolute/fixed
  • display: inline-block / table
  • float 元素
  • ovevflow !== visible

規則:

  • 屬於同一個 BFC 的兩個相鄰 Box 垂直排列
  • 屬於同一個 BFC 的兩個相鄰 Box 的 margin 會發生重疊
  • BFC 中子元素的 margin box 的左邊, 與包含塊 (BFC) border box的左邊相接觸 (子元素 absolute 除外)
  • BFC 的區域不會與 float 的元素區域重疊
  • 計算 BFC 的高度時,浮動子元素也參與計算
  • 文字層不會被浮動層覆蓋,環繞於周圍

應用:

  • 阻止margin重疊
  • 可以包含浮動元素 —— 清除內部浮動(清除浮動的原理是兩個div都位於同一個 BFC 區域之中)
  • 自適應兩欄佈局
  • 可以阻止元素被浮動元素覆蓋

#3.層疊上下文

元素提升爲一個比較特殊的圖層,在三維空間中 (z軸) 高出普通元素一等。

觸發條件

  • 根層疊上下文(html)
  • position
  • css3屬性
    • flex
    • transform
    • opacity
    • filter
    • will-change
    • webkit-overflow-scrolling

層疊等級:層疊上下文在z軸上的排序

  • 在同一層疊上下文中,層疊等級纔有意義
  • z-index的優先級最高

#4. 居中佈局

水平居中

  • 行內元素: text-align: center
  • 塊級元素: margin: 0 auto
  • absolute + transform
  • flex + justify-content: center

垂直居中

  • line-height: height
  • absolute + transform
  • flex + align-items: center
  • table

水平垂直居中

  • absolute + transform
  • flex + justify-content + align-items

#5. 選擇器優先級

  • !important > 行內樣式 > #id > .class > tag > * > 繼承 > 默認
  • 選擇器 從右往左 解析

#6.去除浮動影響,防止父級高度塌陷

  • 通過增加尾元素清除浮動
  • :after / <br> : clear: both
  • 創建父級 BFC
  • 父級設置高度
  • link功能較多,可以定義 RSS,定義 Rel 等作用,而@import只能用於加載 css
  • 當解析到link時,頁面會同步加載所引的 css,而@import所引用的 css 會等到頁面加載完才被加載
  • @import需要 IE5 以上才能使用
  • link可以使用 js 動態引入,@import不行

#8. CSS預處理器(Sass/Less/Postcss)

CSS預處理器的原理: 是將類 CSS語言通過 Webpack 編譯 轉成瀏覽器可讀的真正 CSS。在這層編譯之上,便可以賦予 CSS 更多更強大的功能,常用功能:

  • 嵌套
  • 變量
  • 循環語句
  • 條件語句
  • 自動前綴
  • 單位轉換
  • mixin複用

面試中一般不會重點考察該點,一般介紹下自己在實戰項目中的經驗即可~

#9.CSS動畫

transition: 過渡動畫

  • transition-property: 屬性
  • transition-duration: 間隔
  • transition-timing-function: 曲線
  • transition-delay: 延遲
  • 常用鉤子: transitionend

animation / keyframes

  • animation-name: 動畫名稱,對應@keyframes
  • animation-duration: 間隔
  • animation-timing-function: 曲線
  • animation-delay: 延遲
  • animation-iteration-count: 次數
    • infinite: 循環動畫
  • animation-direction: 方向
    • alternate: 反向播放
  • animation-fill-mode: 靜止模式
    • forwards: 停止時,保留最後一幀
    • backwards: 停止時,回到第一幀
    • both: 同時運用 forwards / backwards
  • 常用鉤子: animationend

動畫屬性: 儘量使用動畫屬性進行動畫,能擁有較好的性能表現

  • translate
  • scale
  • rotate
  • skew
  • opacity
  • color

#二、JavaScript

#1. 原型 / 構造函數 / 實例

  • 原型(prototype): 一個簡單的對象,用於實現對象的 屬性繼承。可以簡單的理解成對象的爹。在 Firefox 和 Chrome 中,每個JavaScript對象中都包含一個__proto__(非標準)的屬性指向它爹(該對象的原型),可obj.__proto__進行訪問。
  • 構造函數: 可以通過new來 新建一個對象 的函數。
  • 實例: 通過構造函數和new創建出來的對象,便是實例。 實例通過__proto__指向原型,通過constructor指向構造函數。

Object爲例,我們常用的Object便是一個構造函數,因此我們可以通過它構建實例。

// 實例
const instance = new Object()

則此時, 實例爲instance, 構造函數爲Object,我們知道,構造函數擁有一個prototype的屬性指向原型,因此原型爲:

// 原型
const prototype = Object.prototype

這裏我們可以來看出三者的關係:

  • 實例.__proto__ === 原型
  • 原型.constructor === 構造函數
  • 構造函數.prototype === 原型
// 這條線其實是是基於原型進行獲取的,可以理解成一條基於原型的映射線
// 例如: 
// const o = new Object()
// o.constructor === Object   --> true
// o.__proto__ = null;
// o.constructor === Object   --> false
實例.constructor === 構造函數

#2.原型鏈:

原型鏈是由原型對象組成,每個對象都有 __proto__ 屬性,指向了創建該對象的構造函數的原型,__proto__ 將對象連接起來組成了原型鏈。是一個用來實現繼承和共享屬性的有限的對象鏈

  • 屬性查找機制: 當查找對象的屬性時,如果實例對象自身不存在該屬性,則沿着原型鏈往上一級查找,找到時則輸出,不存在時,則繼續沿着原型鏈往上一級查找,直至最頂級的原型對象Object.prototype,如還是沒找到,則輸出undefined
  • 屬性修改機制: 只會修改實例對象本身的屬性,如果不存在,則進行添加該屬性,如果需要修改原型的屬性時,則可以用: b.prototype.x = 2;但是這樣會造成所有繼承於該對象的實例的屬性發生改變。

#3. 執行上下文(EC)

執行上下文可以簡單理解爲一個對象:

它包含三個部分:

  • 變量對象(VO)
  • 作用域鏈(詞法作用域)
  • this指向

它的類型:

  • 全局執行上下文
  • 函數執行上下文
  • eval執行上下文

代碼執行過程:

  • 創建 全局上下文 (global EC)
  • 全局執行上下文 (caller) 逐行 自上而下 執行。遇到函數時,函數執行上下文 (callee) 被push到執行棧頂層
  • 函數執行上下文被激活,成爲 active EC, 開始執行函數中的代碼,caller 被掛起
  • 函數執行完後,callee 被pop移除出執行棧,控制權交還全局上下文 (caller),繼續執行

#4.變量對象

  • 變量對象,是執行上下文中的一部分,可以抽象爲一種 數據作用域,其實也可以理解爲就是一個簡單的對象,它存儲着該執行上下文中的所有 變量和函數聲明(不包含函數表達式)。
  • 活動對象 (AO): 當變量對象所處的上下文爲 active EC 時,稱爲活動對象。

#5. 作用域

執行上下文中還包含作用域鏈。理解作用域之前,先介紹下作用域。作用域其實可理解爲該上下文中聲明的 變量和聲明的作用範圍。可分爲 塊級作用域 和 函數作用域

特性:

  • 聲明提前: 一個聲明在函數體內都是可見的, 函數優先於變量
  • 非匿名自執行函數,函數變量爲 只讀 狀態,無法修改
let foo = function() { console.log(1) }
(function foo() {
    foo = 10  // 由於foo在函數中只爲可讀,因此賦值無效
    console.log(foo)
}()) 

// 結果打印:  ƒ foo() { foo = 10 ; console.log(foo) }

#6.作用域鏈

我們知道,我們可以在執行上下文中訪問到父級甚至全局的變量,這便是作用域鏈的功勞。作用域鏈可以理解爲一組對象列表,包含 父級和自身的變量對象,因此我們便能通過作用域鏈訪問到父級裏聲明的變量或者函數。

由兩部分組成:

  • [[scope]]屬性: 指向父級變量對象和作用域鏈,也就是包含了父級的[[scope]]AO
  • AO: 自身活動對象

如此 [[scopr]]包含[[scope]],便自上而下形成一條 鏈式作用域。

#7. 閉包

閉包屬於一種特殊的作用域,稱爲 靜態作用域。它的定義可以理解爲: 父函數被銷燬 的情況下,返回出的子函數的[[scope]]中仍然保留着父級的單變量對象和作用域鏈,因此可以繼續訪問到父級的變量對象,這樣的函數稱爲閉包。

閉包會產生一個很經典的問題:

多個子函數的[[scope]]都是同時指向父級,是完全共享的。因此當父級的變量對象被修改時,所有子函數都受到影響。

••解決:**

  • 變量可以通過 函數參數的形式 傳入,避免使用默認的[[scope]]向上查找
  • 使用setTimeout包裹,通過第三個參數傳入
  • 使用 塊級作用域,讓變量成爲自己上下文的屬性,避免共享

#8. script 引入方式:

  • html 靜態<script>引入
  • js 動態插入<script>
  • <script defer>: 異步加載,元素解析完成後執行
  • <script async>: 異步加載,但執行時會阻塞元素渲染

#9. 對象的拷貝

淺拷貝: 以賦值的形式拷貝引用對象,仍指向同一個地址,修改時原對象也會受到影響

  • Object.assign
  • 展開運算符(...)

深拷貝: 完全拷貝一個新對象,修改時原對象不再受到任何影響

  • JSON.parse(JSON.stringify(obj)): 性能最快
  • 具有循環引用的對象時,報錯
  • 當值爲函數、undefined、或symbol時,無法拷貝
  • 遞歸進行逐一賦值

#10. new運算符的執行過程

  • 新生成一個對象
  • 鏈接到原型: obj.__proto__ = Con.prototype
  • 綁定this: apply
  • 返回新對象(如果構造函數有自己 retrun 時,則返回該值)

#11. instanceof原理

能在實例的 原型對象鏈 中找到該構造函數的prototype屬性所指向的 原型對象,就返回true。即:

// __proto__: 代表原型對象鏈
instance.[__proto__...] === instance.constructor.prototype

// return true

#12. 代碼的複用

當你發現任何代碼開始寫第二遍時,就要開始考慮如何複用。一般有以下的方式:

  • 函數封裝
  • 繼承
  • 複製extend
  • 混入mixin
  • 借用apply/call

#13. 繼承

在 JS 中,繼承通常指的便是 原型鏈繼承,也就是通過指定原型,並可以通過原型鏈繼承原型上的屬性或者方法。

最優化: 聖盃模式

var inherit = (function(c,p){
	var F = function(){};
	return function(c,p){
		F.prototype = p.prototype;
		c.prototype = new F();
		c.uber = p.prototype;
		c.prototype.constructor = c;
	}
})();

使用 ES6 的語法糖 class / extends

#14. 類型轉換

大家都知道 JS 中在使用運算符號或者對比符時,會自帶隱式轉換,規則如下:

  • -、*、/、%:一律轉換成數值後計算
  • +:
    • 數字 + 字符串 = 字符串, 運算順序是從左到右
    • 數字 + 對象, 優先調用對象的valueOf -> toString
    • 數字 + boolean/null -> 數字
    • 數字 + undefined -> NaN
  • [1].toString() === '1'
  • {}.toString() === '[object object]'
  • NaN !== NaN 、+undefined 爲 NaN

#15. 類型判斷

判斷 Target 的類型,單單用 typeof 並無法完全滿足,這其實並不是 bug,本質原因是 JS 的萬物皆對象的理論。因此要真正完美判斷時,我們需要區分對待:

  • 基本類型(null): 使用 String(null)
  • 基本類型(string / number / boolean / undefined) + function: - 直接使用 typeof即可
  • 其餘引用類型(Array / Date / RegExp Error): 調用toString後根據[object XXX]進行判斷

很穩的判斷封裝:

let class2type = {}
'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) 

function type(obj) {
    if (obj == null) return String(obj)
    return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}

#16. 模塊化

模塊化開發在現代開發中已是必不可少的一部分,它大大提高了項目的可維護、可拓展和可協作性。通常,我們 在瀏覽器中使用 ES6 的模塊化支持,在 Node 中使用 commonjs 的模塊化支持。

分類:

  • es6: import / export
  • commonjs: require / module.exports / exports
  • amd: require / defined

require與import的區別

  • require支持 動態導入,import不支持,正在提案 (babel 下可支持)
  • require是 同步 導入,import屬於 異步 導入
  • require是 值拷貝,導出值變化不會影響導入值;import指向 內存地址,導入值會隨導出值而變化

#17. 防抖與節流

防抖與節流函數是一種最常用的 高頻觸發優化方式,能對性能有較大的幫助。

  • 防抖 (debounce): 將多次高頻操作優化爲只在最後一次執行,通常使用的場景是:用戶輸入,只需再輸入完成後做一次輸入校驗即可。
function debounce(fn, wait, immediate) {
    let timer = null

    return function() {
        let args = arguments
        let context = this

        if (immediate && !timer) {
            fn.apply(context, args)
        }

        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}
  • 節流(throttle): 每隔一段時間後執行一次,也就是降低頻率,將高頻操作優化成低頻操作,通常使用場景: 滾動條事件 或者 resize 事件,通常每隔 100~500 ms執行一次即可。
function throttle(fn, wait, immediate) {
    let timer = null
    let callNow = immediate
    
    return function() {
        let context = this,
            args = arguments

        if (callNow) {
            fn.apply(context, args)
            callNow = false
        }

        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null
            }, wait)
        }
    }
}

#18. 函數執行改變this

  • 由於 JS 的設計原理: 在函數中,可以引用運行環境中的變量。因此就需要一個機制來讓我們可以在函數體內部獲取當前的運行環境,這便是this

因此要明白 this 指向,其實就是要搞清楚 函數的運行環境,說人話就是,誰調用了函數。例如

  • obj.fn(),便是 obj 調用了函數,既函數中的 this === obj
  • fn(),這裏可以看成 window.fn(),因此 this === window

但這種機制並不完全能滿足我們的業務需求,因此提供了三種方式可以手動修改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

#19. ES6/ES7

由於 Babel的強大和普及,現在 ES6/ES7 基本上已經是現代化開發的必備了。通過新的語法糖,能讓代碼整體更爲簡潔和易讀。

聲明

  • let / const: 塊級作用域、不存在變量提升、暫時性死區、不允許重複聲明
  • const: 聲明常量,無法修改

解構賦值

class / extend: 類聲明與繼承

Set / Map: 新的數據結構

異步解決方案:

  • Promise的使用與實現
  • generator:
    • yield: 暫停代碼
    • next(): 繼續執行代碼
function* helloWorld() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

const generator = helloWorld();

generator.next()  // { value: 'hello', done: false }

generator.next()  // { value: 'world', done: false }

generator.next()  // { value: 'ending', done: true }

generator.next()  // { value: undefined, done: true }

await / async: 是generator的語法糖, babel中是基於promise實現。

async function getUserByAsync(){
   let user = await fetchUser();
   return user;
}

const user = await getUserByAsync()
console.log(user)

#20. AST

抽象語法樹 (Abstract Syntax Tree),是將代碼逐字母解析成 樹狀對象 的形式。這是語言之間的轉換、代碼語法檢查,代碼風格檢查,代碼格式化,代碼高亮,代碼錯誤提示,代碼自動補全等等的基礎。例如:

function square(n){
	return n * n
}

通過解析轉化成的AST如下圖:

#21. babel編譯原理

  • babylon 將 ES6/ES7 代碼解析成 AST
  • babel-traverse 對 AST 進行遍歷轉譯,得到新的 AST
  • 新 AST 通過 babel-generator 轉換成 ES5

#22. 函數柯里化

在一個函數中,首先填充幾個參數,然後再返回一個新的函數的技術,稱爲函數的柯里化。通常可用於在不侵入函數的前提下,爲函數 預置通用參數,供多次重複調用。

const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

#23. 數組(array)

  • map: 遍歷數組,返回回調返回值組成的新數組
  • forEach: 無法break,可以用try/catchthrow new Error來停止
  • filter: 過濾
  • some: 有一項返回true,則整體爲true
  • every: 有一項返回false,則整體爲false
  • join: 通過指定連接符生成字符串
  • push / pop: 末尾推入和彈出,改變原數組, 返回推入/彈出項
  • unshift / shift: 頭部推入和彈出,改變原數組,返回操作項
  • sort(fn) / reverse: 排序與反轉,改變原數組
  • concat: 連接數組,不影響原數組, 淺拷貝
  • slice(start, end): 返回截斷後的新數組,不改變原數組
  • splice(start, number, value...): 返回刪除元素組成的數組,value爲插入項,改變原數組
  • indexOf / lastIndexOf(value, fromIndex): 查找數組項,返回對應的下標
  • reduce / reduceRight(fn(prev, cur), defaultPrev): 兩兩執行,prev 爲上次化簡函數的return值,cur爲當前值(從第二項開始)

數組亂序:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});

數組拆解: flat: [1,[2,3]] --> [1, 2, 3]

Array.prototype.flat = function() {
    this.toString().split(',').map(item => +item )
}

#三、瀏覽器

#1. 跨標籤頁通訊

不同標籤頁間的通訊,本質原理就是去運用一些可以 共享的中間介質,因此比較常用的有以下方法:

  • 通過父頁面window.open()和子頁面postMessage
    • 異步下,通過 window.open('about: blank') 和 tab.location.href = '*'
  • 設置同域下共享的localStorage與監聽window.onstorage
    • 重複寫入相同的值無法觸發
    • 會受到瀏覽器隱身模式等的限制
  • 設置共享cookie與不斷輪詢髒檢查(setInterval)
  • 藉助服務端或者中間層實現

#2. 瀏覽器架構

  • 用戶界面
  • 主進程
  • 內核
    • 渲染引擎
    • JS 引擎
      • 執行棧
  • 事件觸發線程
    • 消息隊列
      • 微任務
      • 宏任務
  • 網絡異步線程
  • 定時器線程

#3. 瀏覽器下事件循環(Event Loop)

事件循環是指: 執行一個宏任務,然後執行清空微任務列表,循環再執行宏任務,再清微任務列表

  • 微任務 microtask(jobs): promise / ajax / Object.observe(該方法已廢棄)
  • 宏任務 macrotask(task): setTimout / script / IO / UI Rendering

#4. 從輸入 url 到展示的過程

  • DNS 解析
  • TCP 三次握手
  • 發送請求,分析 url,設置請求報文(頭,主體)
  • 服務器返回請求的文件 (html)
  • 瀏覽器渲染
    • HTML parser --> DOM Tree
      • 標記化算法,進行元素狀態的標記
      • dom 樹構建
  • CSS parser --> Style Tree
    • 解析 css 代碼,生成樣式樹
  • attachment --> Render Tree
    • 結合 dom樹 與 style樹,生成渲染樹
  • layout: 佈局
  • GPU painting: 像素繪製頁面

#5. 重繪與迴流

當元素的樣式發生變化時,瀏覽器需要觸發更新,重新繪製元素。這個過程中,有兩種類型的操作,即重繪與迴流。

  • 重繪(repaint): 當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時由於只需要UI層面的重新像素繪製,因此 損耗較少
  • 迴流(reflow): 當元素的尺寸、結構或觸發某些屬性時,瀏覽器會重新渲染頁面,稱爲迴流。此時,瀏覽器需要重新經過計算,計算後還需要重新頁面佈局,因此是較重的操作。會觸發迴流的操作:
  • 頁面初次渲染
  • 瀏覽器窗口大小改變
  • 元素尺寸、位置、內容發生改變
  • 元素字體大小變化
  • 添加或者刪除可見的 dom 元素
  • 激活 CSS 僞類(例如::hover
  • 查詢某些屬性或調用某些方法
    • clientWidth、clientHeight、clientTop、clientLeft
    • offsetWidth、offsetHeight、offsetTop、offsetLeft
    • scrollWidth、scrollHeight、scrollTop、scrollLeft
    • getComputedStyle()
    • getBoundingClientRect()
    • scrollTo()

迴流必定觸發重繪,重繪不一定觸發迴流。重繪的開銷較小,迴流的代價較高。

最佳實踐:

css

  • 避免使用table佈局
  • 將動畫效果應用到position屬性爲absolutefixed的元素上

javascript

  • 避免頻繁操作樣式,可彙總後統一 一次修改
  • 儘量使用class進行樣式修改
  • 減少dom的增刪次數,可使用 字符串 或者 documentFragment 一次性插入
  • 極限優化時,修改樣式可將其display: none後修改
  • 避免多次觸發上面提到的那些會觸發迴流的方法,可以的話儘量用 變量存住

#6. 存儲

我們經常需要對業務中的一些數據進行存儲,通常可以分爲 短暫性存儲 和 持久性儲存。

  • 短暫性的時候,我們只需要將數據存在內存中,只在運行時可用
  • 持久性存儲,可以分爲 瀏覽器端 與 服務器端
    • 瀏覽器:
      • cookie: 通常用於存儲用戶身份,登錄狀態等
        • http 中自動攜帶, 體積上限爲 4K, 可自行設置過期時間
      • localStorage / sessionStorage: 長久儲存/窗口關閉刪除, 體積限制爲 4~5M
      • indexDB
    • 服務器:
      • 分佈式緩存 redis
      • 數據庫

#7. Web Worker

現代瀏覽器爲JavaScript創造的 多線程環境。可以新建並將部分任務分配到worker線程並行運行,兩個線程可 獨立運行,互不干擾,可通過自帶的 消息機制 相互通信。

基本用法:

// 創建 worker
const worker = new Worker('work.js');

// 向主進程推送消息
worker.postMessage('Hello World');

// 監聽主進程來的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}

限制:

  • 同源限制
  • 無法使用 document / window / alert / confirm
  • 無法加載本地資源

#8. 內存泄露

  • 意外的全局變量: 無法被回收
  • 定時器: 未被正確關閉,導致所引用的外部變量無法被釋放
  • 事件監聽: 沒有正確銷燬 (低版本瀏覽器可能出現)
  • 閉包: 會導致父級中的變量無法被釋放
  • dom 引用: dom 元素被刪除時,內存中的引用未被正確清空

可用 chrome 中的 timeline 進行內存標記,可視化查看內存的變化情況,找出異常點。

#四、服務端與網絡

#1. http/https 協議

1.0 協議缺陷:

  • 無法複用鏈接,完成即斷開,重新慢啓動和 TCP 3次握手
  • head of line blocking: 線頭阻塞,導致請求之間互相影響

1.1 改進:

  • 長連接(默認 keep-alive),複用
  • host 字段指定對應的虛擬站點
  • 新增功能:
    • 斷點續傳
    • 身份認證
    • 狀態管理
    • cache 緩存
      • Cache-Control
      • Expires
      • Last-Modified
      • Etag

2.0:

  • 多路複用
  • 二進制分幀層: 應用層和傳輸層之間
  • 首部壓縮
  • 服務端推送

https: 較爲安全的網絡傳輸協議

  • 證書(公鑰)
  • SSL 加密
  • 端口 443

TCP:

  • 三次握手
  • 四次揮手
  • 滑動窗口: 流量控制
  • 擁塞處理
    • 慢開始
    • 擁塞避免
    • 快速重傳
    • 快速恢復

緩存策略: 可分爲 強緩存 和 協商緩存

  • Cache-Control/Expires: 瀏覽器判斷緩存是否過期,未過期時,直接使用強緩存,Cache-Control的 max-age 優先級高於 Expires
  • 當緩存已經過期時,使用協商緩存
    • 唯一標識方案: Etag(response 攜帶) & If-None-Match(request攜帶,上一次返回的 Etag): 服務器判斷資源是否被修改
    • 最後一次修改時間: Last-Modified(response) & If-Modified-Since(request,上一次返回的Last-Modified)
      • 如果一致,則直接返回 304 通知瀏覽器使用緩存
      • 如不一致,則服務端返回新的資源
  • Last-Modified 缺點:
    • 週期性修改,但內容未變時,會導致緩存失效
    • 最小粒度只到 s, s 以內的改動無法檢測到
  • Etag 的優先級高於Last-Modified

#2. 常見狀態碼

  • 1xx: 接受,繼續處理
  • 200: 成功,並返回數據
  • 201: 已創建
  • 202: 已接受
  • 203: 成爲,但未授權
  • 204: 成功,無內容
  • 205: 成功,重置內容
  • 206: 成功,部分內容
  • 301: 永久移動,重定向
  • 302: 臨時移動,可使用原有URI
  • 304: 資源未修改,可使用緩存
  • 305: 需代理訪問
  • 400: 請求語法錯誤
  • 401: 要求身份認證
  • 403: 拒絕請求
  • 404: 資源不存在
  • 500: 服務器錯誤

#3. get / post

  • get: 緩存、請求長度受限、會被歷史保存記錄
    • 無副作用(不修改資源),冪等(請求次數與資源無關)的場景
  • post: 安全、大數據、更多編碼類型

#4. Websocket

Websocket 是一個 持久化的協議, 基於 http , 服務端可以 主動 push

兼容:

  • FLASH Socket
  • 長輪詢: 定時發送 ajax
  • long poll: 發送 --> 有消息時再 response
  • new WebSocket(url)
  • ws.onerror = fn
  • ws.onclose = fn
  • ws.onopen = fn
  • ws.onmessage = fn
  • ws.send()

#5. TCP三次握手

建立連接前,客戶端和服務端需要通過握手來確認對方:

  • 客戶端發送 syn(同步序列編號) 請求,進入 syn_send 狀態,等待確認
  • 服務端接收並確認 syn 包後發送 syn+ack 包,進入 syn_recv 狀態
  • 客戶端接收 syn+ack 包後,發送 ack 包,雙方進入 established 狀態

#6. TCP四次揮手

  • 客戶端 -- FIN --> 服務端, FIN—WAIT
  • 服務端 -- ACK --> 客戶端, CLOSE-WAIT
  • 服務端 -- ACK,FIN --> 客戶端, LAST-ACK
  • 客戶端 -- ACK --> 服務端,CLOSED

#7. Node 的 Event Loop: 6個階段

  • timer 階段: 執行到期的setTimeout / setInterval隊列回調
  • I/O 階段: 執行上輪循環殘流的callback
  • idleprepare
  • poll: 等待回調
      1. 執行回調
      1. 執行定時器
      • 如有到期的setTimeout / setInterval, 則返回 timer 階段
      • 如有setImmediate,則前往 check 階段
  • check
    • 執行setImmediate
  • close callbacks

#8. 跨域

  • JSONP: 利用<script>標籤不受跨域限制的特點,缺點是隻能支持 get 請求
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
  • 設置 CORS: Access-Control-Allow-Origin:*
  • postMessage

#9. 安全

  • XSS攻擊: 注入惡意代碼
    • cookie 設置 httpOnly
    • 轉義頁面上的輸入內容和輸出內容
  • CSRF: 跨站請求僞造,防護:
    • get不修改數據
    • 不被第三方網站訪問到用戶的 cookie
    • 設置白名單,不被第三方網站請求
    • 請求校驗

#五、框架:Vue

#1. nextTick

在下次dom更新循環結束之後執行延遲迴調,可用於獲取更新後的dom狀態

  • 新版本中默認是mincrotasksv-on中會使用macrotasks
  • macrotasks任務的實現:
    • setImmediate / MessageChannel / setTimeout

#2. 生命週期

init

  • initLifecycle/Event,往vm上掛載各種屬性
  • callHook: beforeCreated: 實例剛創建
  • initInjection/initState: 初始化注入和 data 響應性
  • created: 創建完成,屬性已經綁定, 但還未生成真實dom`
  • 進行元素的掛載: $el / vm.$mount()
  • 是否有template: 解析成 render function
    • *.vue文件: vue-loader會將<template>編譯成render function
  • beforeMount: 模板編譯/掛載之前
  • 執行render function,生成真實的dom,並替換到dom tree
  • mounted: 組件已掛載

update

  • 執行diff算法,比對改變是否需要觸發UI更新
  • flushScheduleQueue
  • watcher.before: 觸發beforeUpdate鉤子 - watcher.run(): 執行watcher中的 notify,通知所有依賴項更新UI
  • 觸發updated鉤子: 組件已更新
  • actived / deactivated(keep-alive): 不銷燬,緩存,組件激活與失活
  • destroy
    • beforeDestroy: 銷燬開始
    • 銷燬自身且遞歸銷燬子組件以及事件監聽
      • remove(): 刪除節點
      • watcher.teardown(): 清空依賴
      • vm.$off(): 解綁監聽
    • destroyed: 完成後觸發鉤子

上面是vue的聲明週期的簡單梳理,接下來我們直接以代碼的形式來完成vue的初始化


new Vue({})

// 初始化Vue實例
function _init() {
	 // 掛載屬性
    initLifeCycle(vm) 
    // 初始化事件系統,鉤子函數等
    initEvent(vm) 
    // 編譯slot、vnode
    initRender(vm) 
    // 觸發鉤子
    callHook(vm, 'beforeCreate')
    // 添加inject功能
    initInjection(vm)
    // 完成數據響應性 props/data/watch/computed/methods
    initState(vm)
    // 添加 provide 功能
    initProvide(vm)
    // 觸發鉤子
    callHook(vm, 'created')
		
	 // 掛載節點
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }
}

// 掛載節點實現
function mountComponent(vm) {
	 // 獲取 render function
    if (!this.options.render) {
        // template to render
        // Vue.compile = compileToFunctions
        let { render } = compileToFunctions() 
        this.options.render = render
    }
    // 觸發鉤子
    callHook('beforeMounte')
    // 初始化觀察者
    // render 渲染 vdom, 
    vdom = vm.render()
    // update: 根據 diff 出的 patchs 掛載成真實的 dom 
    vm._update(vdom)
    // 觸發鉤子  
    callHook(vm, 'mounted')
}

// 更新節點實現
funtion queueWatcher(watcher) {
	nextTick(flushScheduleQueue)
}

// 清空隊列
function flushScheduleQueue() {
	 // 遍歷隊列中所有修改
    for(){
	    // beforeUpdate
        watcher.before()
         
        // 依賴局部更新節點
        watcher.update() 
        callHook('updated')
    }
}

// 銷燬實例實現
Vue.prototype.$destory = function() {
	 // 觸發鉤子
    callHook(vm, 'beforeDestory')
    // 自身及子節點
    remove() 
    // 刪除依賴
    watcher.teardown() 
    // 刪除監聽
    vm.$off() 
    // 觸發鉤子
    callHook(vm, 'destoryed')
}

#3. Proxy 相比於 defineProperty 的優勢

  • 數組變化也能監聽到
  • 不需要深度遍歷監聽
let data = { a: 1 }
let reactiveData = new Proxy(data, {
	get: function(target, name){
		// ...
	},
	// ...
})

#4. vue-router

mode

  • hash
  • history

跳轉

  • this.$router.push()
  • <router-link to=""></router-link>

佔位

<router-view></router-view>

#5. vuex

  • state: 狀態中心
  • mutations: 更改狀態
  • actions: 異步更改狀態
  • getters: 獲取狀態
  • modules: 將state分成多個modules,便於管理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章