中高級前端大廠面試祕籍,爲你保駕護航金三銀四,直通大廠(上)

引言

當下,正面臨着近幾年來的最嚴重的互聯網寒冬,聽得最多的一句話即是:相見於江湖~🤣。縮減HC、裁人不絕於耳,你們都是人心惶惶,年前如此,年後想必確定又是一場更爲慘烈的江湖廝殺。但博主始終相信,寒冬之中,人才更是尤其珍貴。只要有過硬的操做和裝備,在逆風局下,一樣也能來一波收割翻盤。javascript

博主也是年前經歷了一番廝殺,最終拿到多家大廠的 offer。在閉關修煉的過程當中,本身整理出了一套面試祕籍供本身反覆研究,後來給了多位有須要的兄臺,均表示至關靠譜,理應在這寒冬之中回報於社會。因而決定花點精力整理成文,讓你們能比較系統的反覆學習,快速提高本身。css

面試當然有技巧,但毫不是僞造與吹流弊,經過一段短期沉下心來閉關修煉,出山收割,步入大廠,薪資翻番,豈不爽哉?🤓html

修煉原則

想必你們很厭煩筆試和考察知識點。由於其實在平時實戰中,講究的是開發效率,不多會去刻意記下一些細節和深挖知識點,腦海中都是一些分散的知識點,沒法系統性地關聯成網,一直處於時曾相識的狀態。不知道多少人和博主同樣,至今每次寫阻止冒泡都須要谷歌一番如何拼寫。🤪。前端

以如此的狀態,定然是沒法在面試的戰場上縱橫的。其實面試就猶如考試,你們回想下高考以前所作的事,無非就是 理解系統性關聯記憶。本祕籍的知識點較多,花點時間一個個理解並記憶後,天然也就融會貫通,無所畏懼。vue

因爲本祕籍爲了便於記憶,快速達到應試狀態,相似於複習知識大綱。知識點會盡可能的精簡與提煉知識脈絡,並不去展開深刻細節,面面俱到。有興趣或者有疑問的童鞋能夠自行谷歌下對應知識點的詳細內容。😋java

CSS

1. 盒模型

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

  • content-box (W3C 標準盒模型)
  • border-box (IE 盒模型)
  • padding-box
  • margin-box

2. BFC

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

IE下爲 Layout,可經過 zoom:1 觸發
  • 觸發條件:webpack

    • 根元素
    • positon: absolute/fixed
    • display: inline-block / table
    • float 元素
    • ovevflow !== visible
  • 規則:css3

    • 屬於同一個 BFC 的兩個相鄰 Box 垂直排列
    • 屬於同一個 BFC 的兩個相鄰 Box 的 margin 會發生重疊
    • BFC 中子元素不會超出他的包含塊
    • 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
  • 父級設置高度

7.link 與 @import 的區別

  • 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

經驗

一般,CSS 並非重點的考察領域,但這實際上是因爲如今國內業界對 CSS 的專一不夠致使的,真正精通並專一於 CSS 的團隊和人才並很少。所以若是能在 CSS 領域有本身的看法和經驗,反而會爲至關的加分和脫穎而出。

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 === 原型

實例.constructorr === 構造函數

放大來看,我畫了張圖供你們完全理解:

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),繼續執行

2.變量對象

變量對象,是執行上下文中的一部分,能夠抽象爲一種 數據做用域,其實也能夠理解爲就是一個簡單的對象,它存儲着該執行上下文中的全部 變量和函數聲明(不包含函數表達式)

活動對象 (AO): 當變量對象所處的上下文爲 active EC 時,稱爲活動對象。

3. 做用域

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

特性:

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

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

4.做用域鏈

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

  • 由兩部分組成:

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

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

5. 閉包

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

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

    • 多個子函數的[[scope]]都是同時指向父級,是徹底共享的。所以當父級的變量對象被修改時,全部子函數都受到影響。
  • 解決:

    • 變量能夠經過 函數參數的形式 傳入,避免使用默認的[[scope]]向上查找
    • 使用setTimeout包裹,經過第三個參數傳入
    • 使用 塊級做用域,讓變量成爲本身上下文的屬性,避免共享

6. script 引入方式:

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

7. 對象的拷貝

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

    • Object.assign
    • 展開運算符(...)
  • 深拷貝: 徹底拷貝一個新對象,修改時原對象再也不受到任何影響

    • JSON.parse(JSON.stringify(obj)): 性能最快

      • 具備循環引用的對象時,報錯
      • 當值爲函數或undefined時,沒法拷貝
    • 遞歸進行逐一賦值

8. new運算符的執行過程

  • 新生成一個對象
  • 連接到原型: obj.__proto__ = Con.prototype
  • 綁定this: apply
  • 返回新對象

9. instanceof原理

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

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

// return true

10. 代碼的複用

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

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

11. 繼承

在 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

12. 類型轉換

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

  • -、*、/、% :一概轉換成數值後計算
  • +:

    • 數字 + 字符串 = 字符串, 運算順序是從左到右
    • 數字 + 對象, 優先調用對象的valueOf -> toString
    • 數字 + boolean/null = 數字
    • 數字 + undefined == NaN
  • [1].toString() === '1'
  • {}.toString() === '[object object]'
  • NaN !== NaN+undefined === NaN

13. 類型判斷

判斷 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
}

14. 模塊化

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

  • 分類:

    • es6: import / exports
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • requireimport的區別

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

15. 防抖與節流

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

  • 防抖 (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 = true
    
    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)
        }
    }
}

16. 函數執行改變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)

17. 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`實現。

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

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

18. AST

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

function square(n){
    return n * n
}

經過解析轉化成的AST以下圖:

19. babel編譯原理

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

20. 函數柯里化

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

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

const add1 = add(1)

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

21. 數組(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]
arr.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. V8垃圾回收機制

垃圾回收: 將內存中再也不使用的數據進行清理,釋放出內存空間。V8 將內存分紅 新生代空間老生代空間

  • 新生代空間: 用於存活較短的對象

    • 又分紅兩個空間: from 空間 與 to 空間
    • Scavenge GC算法: 當 from 空間被佔滿時,啓動 GC 算法

      • 存活的對象從 from space 轉移到 to space
      • 清空 from space
      • from space 與 to space 互換
      • 完成一次新生代GC
  • 老生代空間: 用於存活時間較長的對象

    • 從 新生代空間 轉移到 老生代空間 的條件

      • 經歷過一次以上 Scavenge GC 的對象
      • 當 to space 體積超過25%
    • 標記清除算法: 標記存活的對象,未被標記的則被釋放

      • 增量標記: 小模塊標記,在代碼執行間隙執,GC 會影響性能
      • 併發標記(最新技術): 不阻塞 js 執行
    • 壓縮算法: 將內存中清除後致使的碎片化對象往內存堆的一端移動,解決 內存的碎片化

9. 內存泄露

  • 意外的全局變量: 沒法被回收
  • 定時器: 未被正確關閉,致使所引用的外部變量沒法被釋放
  • 事件監聽: 沒有正確銷燬 (低版本瀏覽器可能出現)
  • 閉包: 會致使父級中的變量沒法被釋放
  • 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
  • idle, prepare
  • poll: 等待回調

      1. 執行回調
      1. 執行定時器
      • 若有到期的setTimeout / setInterval, 則返回 timer 階段
      • 若有setImmediate,則前往 check 階段
  • check

    • 執行setImmediate
  • close callbacks

跨域

  • 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

安全

  • XSS攻擊: 注入惡意代碼

    • cookie 設置 httpOnly
    • 轉義頁面上的輸入內容和輸出內容
  • CSPF: 跨站請求僞造,防禦:

    • get 不修改數據
    • 不被第三方網站訪問到用戶的 cookie
    • 設置白名單,不被第三方網站請求
    • 請求校驗

框架:Vue

1. nextTick

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

  • 新版本中默認是mincrotasks, v-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. 數據響應(數據劫持)

看完生命週期後,裏面的watcher等內容實際上是數據響應中的一部分。數據響應的實現由兩部分構成: 觀察者( watcher )依賴收集器( Dep ),其核心是 defineProperty這個方法,它能夠 重寫屬性的 get 與 set 方法,從而完成監聽數據的改變。

  • Observe (觀察者)觀察 props 與 state

    • 遍歷 props 與 state,對每一個屬性建立獨立的監聽器( watcher )
  • 使用 defineProperty 重寫每一個屬性的 get/set(defineReactive

    • get: 收集依賴

      • Dep.depend()

        • watcher.addDep()
    • set: 派發更新

      • Dep.notify()
      • watcher.update()
      • queenWatcher()
      • nextTick
      • flushScheduleQueue
      • watcher.run()
      • updateComponent()

你們能夠先看下面的數據相應的代碼實現後,理解後就比較容易看懂上面的簡單脈絡了。

let data = {a: 1}
// 數據響應性
observe(data)

// 初始化觀察者
new Watcher(data, 'name', updateComponent)
data.a = 2

// 簡單表示用於數據更新後的操做
function updateComponent() {
    vm._update() // patchs
}

// 監視對象
function observe(obj) {
     // 遍歷對象,使用 get/set 從新定義對象的每一個屬性值
    Object.keys(obj).map(key => {
        defineReactive(obj, key, obj[key])
    })
}

function defineReactive(obj, k, v) {
    // 遞歸子屬性
    if (type(v) == 'object') observe(v)
    
    // 新建依賴收集器
    let dep = new Dep()
    // 定義get/set
    Object.defineProperty(obj, k, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
              // 當有獲取該屬性時,證實依賴於該對象,所以被添加進收集器中
            if (Dep.target) {
                dep.addSub(Dep.target)
            }
            return v
        },
        // 從新設置值時,觸發收集器的通知機制
        set: function reactiveSetter(nV) {
            v = nV
            dep.nofify()
        },
    })
}

// 依賴收集器
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    notify() {
        this.subs.map(sub => {
            sub.update()
        })
    }
}

Dep.target = null

// 觀察者
class Watcher {
    constructor(obj, key, cb) {
        Dep.target = this
        this.cb = cb
        this.obj = obj
        this.key = key
        this.value = obj[key]
        Dep.target = null
    }
    addDep(Dep) {
        Dep.addSub(this)
    }
    update() {
        this.value = this.obj[this.key]
        this.cb(this.value)
    }
    before() {
        callHook('beforeUpdate')
    }
}

4. virtual dom 原理實現

  • 建立 dom 樹
  • 樹的diff,同層對比,輸出patchs(listDiff/diffChildren/diffProps)

    • 沒有新的節點,返回
    • 新的節點tagNamekey不變, 對比props,繼續遞歸遍歷子樹

      • 對比屬性(對比新舊屬性列表):

        • 舊屬性是否存在與新屬性列表中
        • 都存在的是否有變化
        • 是否出現舊列表中沒有的新屬性
    • tagNamekey值變化了,則直接替換成新節點
  • 渲染差別

    • 遍歷patchs, 把須要更改的節點取出來
    • 局部更新dom
// diff算法的實現
function diff(oldTree, newTree) {
     // 差別收集
    let pathchs = {}
    dfs(oldTree, newTree, 0, pathchs)
    return pathchs
}

function dfs(oldNode, newNode, index, pathchs) {
    let curPathchs = []
    if (newNode) {
        // 當新舊節點的 tagName 和 key 值徹底一致時
        if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
              // 繼續比對屬性差別
            let props = diffProps(oldNode.props, newNode.props)
            curPathchs.push({ type: 'changeProps', props })
            // 遞歸進入下一層級的比較
            diffChildrens(oldNode.children, newNode.children, index, pathchs)
        } else {
              // 當 tagName 或者 key 修改了後,表示已是全新節點,無需再比
            curPathchs.push({ type: 'replaceNode', node: newNode })
        }
    }

     // 構建出整顆差別樹
    if (curPathchs.length) {
            if(pathchs[index]){
                pathchs[index] = pathchs[index].concat(curPathchs)
            } else {
                pathchs[index] = curPathchs
            }
    }
}

// 屬性對比實現
function diffProps(oldProps, newProps) {
    let propsPathchs = []
    // 遍歷新舊屬性列表
    // 查找刪除項
    // 查找修改項
    // 查找新增項
    forin(olaProps, (k, v) => {
        if (!newProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'remove', prop: k })
        } else {
            if (v !== newProps[k]) {
                propsPathchs.push({ type: 'change', prop: k , value: newProps[k] })
            }
        }
    })
    forin(newProps, (k, v) => {
        if (!oldProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'add', prop: k, value: v })
        }
    })
    return propsPathchs
}

// 對比子級差別
function diffChildrens(oldChild, newChild, index, pathchs) {
        // 標記子級的刪除/新增/移動
    let { change, list } = diffList(oldChild, newChild, index, pathchs)
    if (change.length) {
        if (pathchs[index]) {
            pathchs[index] = pathchs[index].concat(change)
        } else {
            pathchs[index] = change
        }
    }

     // 根據 key 獲取本來匹配的節點,進一步遞歸從頭開始對比
    oldChild.map((item, i) => {
        let keyIndex = list.indexOf(item.key)
        if (keyIndex) {
            let node = newChild[keyIndex]
            // 進一步遞歸對比
            dfs(item, node, index, pathchs)
        }
    })
}

// 列表對比,主要也是根據 key 值查找匹配項
// 對比出新舊列表的新增/刪除/移動
function diffList(oldList, newList, index, pathchs) {
    let change = []
    let list = []
    const newKeys = getKey(newList)
    oldList.map(v => {
        if (newKeys.indexOf(v.key) > -1) {
            list.push(v.key)
        } else {
            list.push(null)
        }
    })

    // 標記刪除
    for (let i = list.length - 1; i>= 0; i--) {
        if (!list[i]) {
            list.splice(i, 1)
            change.push({ type: 'remove', index: i })
        }
    }

    // 標記新增和移動
    newList.map((item, i) => {
        const key = item.key
        const index = list.indexOf(key)
        if (index === -1 || key == null) {
            // 新增
            change.push({ type: 'add', node: item, index: i })
            list.splice(i, 0, key)
        } else {
            // 移動
            if (index !== i) {
                change.push({
                    type: 'move',
                    form: index,
                    to: i,
                })
                move(list, index, i)
            }
        }
    })

    return { change, list }
}

5. Proxy 相比於 defineProperty 的優點

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

6. vue-router

  • mode

    • hash
    • history
  • 跳轉

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

    • <router-view></router-view>

7. vuex

  • state: 狀態中心
  • mutations: 更改狀態
  • actions: 異步更改狀態
  • getters: 獲取狀態
  • modules: 將state分紅多個modules,便於管理

算法

其實算法方面在前端的實際項目中涉及得並很少,但仍是須要精通一些基礎性的算法,一些公司仍是會有這方面的需求和考覈,建議你們仍是須要稍微準備下,這屬於加分題。

1. 五大算法

  • 貪心算法: 局部最優解法
  • 分治算法: 分紅多個小模塊,與原問題性質相同
  • 動態規劃: 每一個狀態都是過去歷史的一個總結
  • 回溯法: 發現原先選擇不優時,退回從新選擇
  • 分支限界法

2. 基礎排序算法

  • 冒泡排序: 兩兩比較
function bubleSort(arr) {
        var len = arr.length;
        for (let outer = len ; outer >= 2; outer--) {
            for(let inner = 0; inner <=outer - 1; inner++) {
                if(arr[inner] > arr[inner + 1]) {
                    [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
                }
            }
        }
        return arr;
    }
  • 選擇排序: 遍歷自身之後的元素,最小的元素跟本身調換位置
function selectSort(arr) {
    var len = arr.length;
    for(let i = 0 ;i < len - 1; i++) {
        for(let j = i ; j<len; j++) {
            if(arr[j] < arr[i]) {
                [arr[i],arr[j]] = [arr[j],arr[i]];
            }
        }
    }
    return arr
}
  • 插入排序: 即將元素插入到已排序好的數組中
function insertSort(arr) {
    for(let i = 1; i < arr.length; i++) {  //外循環從1開始,默認arr[0]是有序段
        for(let j = i; j > 0; j--) {  //j = i,將arr[j]依次插入有序段中
            if(arr[j] < arr[j-1]) {
                [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
            } else {
                break;
            }
        }
    }
    return arr;
}

3. 高級排序算法

  • 快速排序

    • 選擇基準值(base),原數組長度減一(基準值),使用 splice
    • 循環原數組,小的放左邊(left數組),大的放右邊(right數組);
    • concat(left, base, right)
    • 遞歸繼續排序 left 與 right
function quickSort(arr) {
    if(arr.length <= 1) {
        return arr;  //遞歸出口
    }
    var left = [],
        right = [],
        current = arr.splice(0,1); 
    for(let i = 0; i < arr.length; i++) {
        if(arr[i] < current) {
            left.push(arr[i])  //放在左邊
        } else {
            right.push(arr[i]) //放在右邊
        }
    }
    return quickSort(left).concat(current,quickSort(right));
}
  • 希爾排序:不定步數的插入排序,插入排序
  • 口訣: 插冒歸基穩定,快選堆希不穩定

穩定性: 同大小狀況下是否可能會被交換位置, 虛擬dom的diff,不穩定性會致使從新渲染;

4. 遞歸運用(斐波那契數列): 爬樓梯問題

初始在第一級,到第一級有1種方法(s(1) = 1),到第二級也只有一種方法(s(2) = 1), 第三級(s(3) = s(1) + s(2))

function cStairs(n) {
    if(n === 1 || n === 2) {
        return 1;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}

5. 數據樹

  • 二叉樹: 最多隻有兩個子節點

    • 徹底二叉樹
    • 滿二叉樹

      • 深度爲 h, 有 n 個節點,且知足 n = 2^h - 1
  • 二叉查找樹: 是一種特殊的二叉樹,能有效地提升查找效率

    • 小值在左,大值在右
    • 節點 n 的全部左子樹值小於 n,全部右子樹值大於 n

  • 遍歷節點

    • 前序遍歷

        1. 根節點
        1. 訪問左子節點,回到 1
        1. 訪問右子節點,回到 1
    • 中序遍歷

        1. 先訪問到最左的子節點
        1. 訪問該節點的父節點
        1. 訪問該父節點的右子節點, 回到 1
    • 後序遍歷

        1. 先訪問到最左的子節點
        1. 訪問相鄰的右節點
        1. 訪問父節點, 回到 1
  • 插入與刪除節點

6. 天平找次品

有n個硬幣,其中1個爲假幣,假幣重量較輕,你有一把天平,請問,至少須要稱多少次能保證必定找到假幣?

  • 三等分算法:

      1. 將硬幣分紅3組,隨便取其中兩組天平稱量
      • 平衡,假幣在未上稱的一組,取其回到 1 繼續循環
      • 不平衡,假幣在天平上較輕的一組, 取其回到 1 繼續循環

結語

因爲精力時間及篇幅有限,這篇就先寫到這。你們慢慢來不急。。🤪。下篇打算準備如下內容,我也得補補課先:

  • Webpack相關

    • 原理
    • Loader
    • Plugin
  • 項目性能優化

    • 首屏渲染優化
    • 用戶體驗優化
    • webpack 性能優化
  • Hybrid 與 Webview

    • webview 加載過程
    • bridge 原理
    • hybrid app 經驗
  • 框架: React

在面試中,不少領域並無真正的答案,能回答到什麼樣的深度,仍是得靠本身真正的去使用和研究。知識面的廣度與深度應該並行,儘可能的拓張本身的領域,至少都有些基礎性的瞭解,在被問到的時候能夠同面試官嘮嗑兩句,而後在本身喜歡的領域,又有着足夠深刻的研究,讓面試官以爲你是這方面的專家。

知識大綱還在不斷的完善和修正,因爲也是精力時間有限,我會慢慢補充後面列出來的部分。固然,我也是在整理中不斷的學習,也但願你們能一塊兒參與進來,要補充或修正的地方麻煩趕忙提出。另外,剛新建了個公衆號,想做爲你們交流和分享的地方,有興趣想法的童鞋聯繫我哈~~😉

Tips:
頭條招前端,內推的童鞋趕忙死命找我!
博主寫得很辛苦,感恩 github。😚

相關文章
相關標籤/搜索