面試圖譜:前端基礎技術知識講解

前言

當你老了,回顧一輩子,就會發覺:何時出國讀書,何時決定作第一份職業,什麼時候選定對象而戀愛,何時結婚,其實都是命運的鉅變。只是當時站在三岔路口,眼見風雲千檣,你作出選擇的那一日,在日記上,至關沉悶和平凡,當時還覺得是生命中普通的一天。html

一個改變面試的項目 -- 面試圖譜前端

金九銀十的秋招季近在眼前,想必你們也都心癢難耐,準備挑戰更好的工做機會。那麼,面試確定是最大的挑戰。react

對於面試來講,平時的積累確定是必須的,可是在面試前的準備也是相當重要的。git

在幾月前我我的組建了一個小團隊,花了將近半年的時間尋找大廠的面試題,篩選出了近百個知識點而後成文,並所有翻譯爲英文。今天,終於開源出了第一個版本,目前總字數已高達 10 餘萬字。團隊的每一個成員都是來自一線的工程師,其中還有一個來自谷歌的大佬。github

能夠這樣說,咱們有自信,把這個項目打形成關於面試相關中的 NO 1. 由於全部內容都沒有照搬任何的書籍,每個知識點都有詳細的查閱過資料才成文。後期更會加入更多的內容,儘量地覆蓋大部分知識點。面試

咱們認爲,一味的背面試題是沒多大做用的。只有熟悉了各個知識點並融會貫通,才能在面試中披荊斬棘。本圖譜目前包含了近百個高頻知識點,不管是面試前的準備仍是平時學習中的查漏補缺,咱們相信確定能幫助到你們。目前內容包含了 JS、網絡、瀏覽器相關、性能優化、安全、框架、Git、數據結構、算法等內容,不管是基礎仍是進階,亦或是源碼解讀,你都能在本圖譜中獲得滿意的答案,但願這個面試圖譜可以幫助到你們更好的準備面試。算法

該倉庫內容會持續更新,後期將會包含更多的內容,好比:系統設計、區塊鏈、運維、後端等等,固然這些不是個人強項,我會邀請這方面有不錯經驗的朋友來書寫內容。小程序

大綱

求職

最近本人在尋找工做機會,若是有杭州的不錯崗位的話,歡迎聯繫我 zx597813039@gmail.com後端

部份內容預覽

MVVM

MVVM 由如下三個內容組成數組

  • View:界面
  • Model:數據模型
  • ViewModel:做爲橋樑負責溝通 View 和 Model

在 JQuery 時期,若是須要刷新 UI 時,須要先取到對應的 DOM 再更新 UI,這樣數據和業務的邏輯就和頁面有強耦合。

在 MVVM 中,UI 是經過數據驅動的,數據一旦改變就會相應的刷新對應的 UI,UI 若是改變,也會改變對應的數據。這種方式就能夠在業務處理中只關心數據的流轉,而無需直接和頁面打交道。ViewModel 只關心數據和業務的處理,不關心 View 如何處理數據,在這種狀況下,View 和 Model 均可以獨立出來,任何一方改變了也不必定須要改變另外一方,而且能夠將一些可複用的邏輯放在一個 ViewModel 中,讓多個 View 複用這個 ViewModel。

在 MVVM 中,最核心的也就是數據雙向綁定,例如 Angluar 的髒數據檢測,Vue 中的數據劫持。

髒數據檢測

當觸發了指定事件後會進入髒數據檢測,這時會調用 $digest 循環遍歷全部的數據觀察者,判斷當前值是否和先前的值有區別,若是檢測到變化的話,會調用 $watch 函數,而後再次調用 $digest 循環直到發現沒有變化。循環至少爲二次 ,至多爲十次。

髒數據檢測雖然存在低效的問題,可是不關心數據是經過什麼方式改變的,均可以完成任務,可是這在 Vue 中的雙向綁定是存在問題的。而且髒數據檢測能夠實現批量檢測出更新的值,再去統一更新 UI,大大減小了操做 DOM 的次數。因此低效也是相對的,這就仁者見仁智者見智了。

數據劫持

Vue 內部使用了 Obeject.defineProperty() 來實現雙向綁定,經過這個函數能夠監聽到 setget 的事件。

var data = { name: 'yck' }
observe(data)
let name = data.name // -> get value
data.name = 'yyy' // -> change value

function observe(obj) {
  // 判斷類型
  if (!obj || typeof obj !== 'object') {
    return
  }
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key])
  })
}

function defineReactive(obj, key, val) {
  // 遞歸子屬性
  observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      console.log('get value')
      return val
    },
    set: function reactiveSetter(newVal) {
      console.log('change value')
      val = newVal
    }
  })
}
複製代碼

以上代碼簡單的實現瞭如何監聽數據的 setget 的事件,可是僅僅如此是不夠的,還須要在適當的時候給屬性添加發布訂閱

<div>
    {{name}}
</div>
複製代碼

在解析如上模板代碼時,遇到 {{name}} 就會給屬性 name 添加發布訂閱。

// 經過 Dep 解耦
class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    // sub 是 Watcher 實例
    this.subs.push(sub)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}
// 全局屬性,經過該屬性配置 Watcher
Dep.target = null

function update(value) {
  document.querySelector('div').innerText = value
}

class Watcher {
  constructor(obj, key, cb) {
    // 將 Dep.target 指向本身
    // 而後觸發屬性的 getter 添加監聽
    // 最後將 Dep.target 置空
    Dep.target = this
    this.cb = cb
    this.obj = obj
    this.key = key
    this.value = obj[key]
    Dep.target = null
  }
  update() {
    // 得到新值
    this.value = this.obj[this.key]
    // 調用 update 方法更新 Dom
    this.cb(this.value)
  }
}
var data = { name: 'yck' }
observe(data)
// 模擬解析到 `{{name}}` 觸發的操做
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy' 
複製代碼

接下來,對 defineReactive 函數進行改造

function defineReactive(obj, key, val) {
  // 遞歸子屬性
  observe(val)
  let dp = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      console.log('get value')
      // 將 Watcher 添加到訂閱
      if (Dep.target) {
        dp.addSub(Dep.target)
      }
      return val
    },
    set: function reactiveSetter(newVal) {
      console.log('change value')
      val = newVal
      // 執行 watcher 的 update 方法
      dp.notify()
    }
  })
}
複製代碼

以上實現了一個簡易的雙向綁定,核心思路就是手動觸發一次屬性的 getter 來實現發佈訂閱的添加。

Proxy 與 Obeject.defineProperty 對比

Obeject.defineProperty 雖然已經可以實現雙向綁定了,可是他仍是有缺陷的。

  1. 只能對屬性進行數據劫持,因此須要深度遍歷整個對象
  2. 對於數組不能監聽到數據的變化

雖然 Vue 中確實能檢測到數組數據的變化,可是實際上是使用了 hack 的辦法,而且也是有缺陷的。

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// hack 如下幾個函數
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  // 得到原生函數
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    // 調用原生函數
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // 觸發更新
    ob.dep.notify()
    return result
  })
})
複製代碼

反觀 Proxy 就沒以上的問題,原生支持監聽數組變化,而且能夠直接對整個對象進行攔截,因此 Vue 也將在下個大版本中使用 Proxy 替換 Obeject.defineProperty

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);
      return Reflect.set(target, property, value);
    }
  };
  return new Proxy(obj, handler);
};

let obj = { a: 1 }
let value
let p = onWatch(obj, (v) => {
  value = v
}, (target, property) => {
  console.log(`Get '${property}' = ${target[property]}`);
})
p.a = 2 // bind `value` to `2`
p.a // -> Get 'a' = 2
複製代碼

路由原理

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

  • hash 模式
  • history 模式

www.test.com/#/ 就是 Hash URL,當 # 後面的哈希值發生變化時,不會向服務器請求數據,能夠經過 hashchange 事件來監聽到 URL 的變化,從而進行跳轉頁面。

History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美觀

內置類型

JS 中分爲七種內置類型,七種內置類型又分爲兩大類型:基本類型和對象(Object)。

基本類型有六種: nullundefinedbooleannumberstringsymbol

其中 JS 的數字類型是浮點類型的,沒有整型。而且浮點類型基於 IEEE 754標準實現,在使用中會遇到某些 BugNaN 也屬於 number 類型,而且 NaN 不等於自身。

對於基本類型來講,若是使用字面量的方式,那麼這個變量只是個字面量,只有在必要的時候纔會轉換爲對應的類型

let a = 111 // 這只是字面量,不是 number 類型
a.toString() // 使用時候纔會轉換爲對象類型
複製代碼

對象(Object)是引用類型,在使用過程當中會遇到淺拷貝和深拷貝的問題。

let a = { name: 'FE' }
let b = a
b.name = 'EF'
console.log(a.name) // EF
複製代碼

Typeof

typeof 對於基本類型,除了 null 均可以顯示正確的類型

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 沒有聲明,可是還會顯示 undefined
複製代碼

typeof 對於對象,除了函數都會顯示 object

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
複製代碼

對於 null 來講,雖然它是基本類型,可是會顯示 object,這是一個存在好久了的 Bug

typeof null // 'object'
複製代碼

PS:爲何會出現這種狀況呢?由於在 JS 的最第一版本中,使用的是 32 位系統,爲了性能考慮使用低位存儲了變量的類型信息,000 開頭表明是對象,然而 null 表示爲全零,因此將它錯誤的判斷爲 object 。雖然如今的內部類型判斷代碼已經改變了,可是對於這個 Bug 倒是一直流傳下來。

若是咱們想得到一個變量的正確類型,能夠經過 Object.prototype.toString.call(xx)。這樣咱們就能夠得到相似 [Object Type] 的字符串。

let a
// 咱們也能夠這樣判斷 undefined
a === undefined
// 可是 undefined 不是保留字,可以在低版本瀏覽器被賦值
let undefined = 1
// 這樣判斷就會出錯
// 因此能夠用下面的方式來判斷,而且代碼量更少
// 由於 void 後面隨便跟上一個組成表達式
// 返回就是 undefined
a === void 0
複製代碼

類型轉換

轉Boolean

在條件判斷時,除了 undefinednullfalseNaN''0-0,其餘全部值都轉爲 true,包括全部對象。

對象轉基本類型

對象在轉換基本類型時,首先會調用 valueOf 而後調用 toString。而且這兩個方法你是能夠重寫的。

let a = {
    valueOf() {
    	return 0
    }
}
複製代碼

四則運算符

只有當加法運算時,其中一方是字符串類型,就會把另外一個也轉爲字符串類型。其餘運算只要其中一方是數字,那麼另外一方就轉爲數字。而且加法運算會觸發三種類型轉換:將值轉換爲原始值,轉換爲數字,轉換爲字符串。

1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
複製代碼

對於加號須要注意這個表達式 'a' + + 'b'

'a' + + 'b' // -> "aNaN"
// 由於 + 'b' -> NaN
// 你也許在一些代碼中看到過 + '1' -> 1
複製代碼

== 操做符

上圖中的 toPrimitive 就是對象轉基本類型。

通常推薦使用 === 判斷兩個值,可是你若是想知道一個值是否是 null ,你能夠經過 xx == null 來比較。

這裏來解析一道題目 [] == ![] // -> true ,下面是這個表達式爲什麼爲 true 的步驟

// [] 轉成 true,而後取反變成 false
[] == false
// 根據第 8 條得出
[] == ToNumber(false)
[] == 0
// 根據第 10 條得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根據第 6 條得出
0 == 0 // -> true
複製代碼

比較運算符

  1. 若是是對象,就經過 toPrimitive 轉換對象
  2. 若是是字符串,就經過 unicode 字符索引來比較

公衆號

Todo

  • 完成 CSS 內容
  • 完成 Webapck 內容
  • 完成小程序相關內容
  • 完善關於框架的內容

以上內容預計將於 9 月份更新完畢,歡迎你一塊兒參與本圖譜的建設。

面試圖譜 項目地址,若是你以爲項目還不錯,你能夠點個小星星支持咱們一下,你的支持是咱們更新的源動力。

相關文章
相關標籤/搜索