挑戰一輪大廠後的面試總結 (含六個方向) - 框架(vue)和工程相關

在去年末開始換工做,直到如今算是告了一個段落,斷斷續續的也面試了很多公司,如今回想起來,那段時間經歷了被面試官手撕,被筆試題狂懟,悲傷的時候差點留下沒技術的淚水。javascript

這篇文章我打算把我找工做遇到的各類面試題(每次面試完我都會總結)和我本身複習遇到比較有意思的題目,作一份彙總,年後是跳槽高峯期,也許能幫到一些小夥伴。css

先說下這些題目難度,大部分都是基礎題,由於這段經歷給個人感受就是,無論你面試的是高級仍是初級,基礎的知識必定會問到,甚至會有必定的深度,因此基礎仍是很是重要的。html

我將根據類型分爲幾篇文章來寫:前端

面試總結:javascript 面試點彙總(萬字長文)(已完成) 強烈你們看看這篇,面試中 js 是大頭vue

面試總結:nodejs 面試點彙總(已完成)java

面試總結:瀏覽器相關 面試點彙總(已完成)node

面試總結:css 面試點彙總(已完成)react

面試總結:框架 vue 和工程相關的面試點彙總(已完成)jquery

面試總結:非技術問題彙總(已完成)webpack

我會抓緊時間把未完成的總結補全的~

這篇文章是對 框架 vue 和工程相關 相關的題目作總結,歡迎朋友們先收藏在看。

先看看目錄

目錄

VUE

這部分是 vue 相關的整理。

響應式原理

響應式原理是 vue 的核心思想之一,後續打算單獨整理一篇,這裏簡單介紹響應式的三大件

Observer

觀察者,使用 Object.defineProperty 方法對對象的每個子屬性進行數據劫持/監聽,在 get 方法中進行依賴收集,添加訂閱者 watcher 到訂閱中心。 在 set 方法中,對新的值進行收集,同時訂閱中心通知訂閱者們。

/*對象的子對象遞歸進行observe並返回子節點的Observer對象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      /*若是本來對象擁有getter方法則執行*/
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        /*進行依賴收集*/
        dep.depend()
        if (childOb) {
          /*子對象進行依賴收集,其實就是將同一個watcher觀察者實例放進了兩個depend中,一個是正在自己閉包中的depend,另外一個是子元素的depend*/
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          /*是數組則須要對每個成員都進行依賴收集,若是數組的成員仍是數組,則遞歸。*/
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      /*經過getter方法獲取當前值,與新值進行比較,一致則不須要執行下面的操做*/
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        /*若是本來對象擁有setter方法則執行setter*/
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      /*新的值須要從新進行observe,保證數據響應式*/
      childOb = observe(newVal)
      /*dep對象通知全部的觀察者*/
      dep.notify()
    }
  })
複製代碼

在setter中通知觀察者更新,在getter中向Dep(調度中心)添加觀察者。

watcher

扮演的角色是訂閱者,他的主要做用是爲觀察屬性提供通知函數,當被觀察的值發生變化時,會接收到來自訂閱中心 dep 的通知,從而觸發依賴更新。

核心方法有: get() 得到getter的值而且從新進行依賴收集 addDep(dep: Dep) 添加一個依賴關係到訂閱中心 Dep 集合中 update() 提供給訂閱中心的通知接口,若是不是同步的(sync),那麼會放到隊列中,異步執行,在下一個事件循環中執行(採用 Promise、MutationObserver以及setTimeout來異步執行)

Dep

扮演的角色是調度中心,主要的做用就是收集觀察者 Watcher 和通知觀察者目標更新。 每個屬性都有一個 Dep 對象,用於存放全部訂閱了該屬性的觀察者對象,當數據發生改變時,會遍歷觀察者列表(dep.subs),通知全部的 watch,讓訂閱者執行本身的 update 邏輯。

computed 爲何比 watch method 性能要好

從編碼上 computed 實現的功能也能夠經過普通 method 實現,但與函數相比,計算屬性是基於響應式依賴進行緩存的,只有在依賴的數據發生改變是,才從新進行計算,只要依賴項沒有發生變化,屢次訪問都只是從緩存中獲取。

計算屬性是基於 watcher 實現,看看源碼

/*初始化computed*/
// 核心是爲每一個計算屬性建立一個 watcher 對象
function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)

  for (const key in computed) {
    const userDef = computed[key]
    /* 計算屬性多是一個function,也有可能設置了get以及set的對象。 */
    let getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production') {
      /*getter不存在的時候拋出warning而且給getter賦空函數*/
      if (getter === undefined) {
        warn(
          `No getter function has been defined for computed property "${key}".`,
          vm
        )
        getter = noop
      }
    }
    // create internal watcher for the computed property.
    /* 爲每一個計算屬性建立一個內部的監視器Watcher,保存在vm實例的_computedWatchers中 這裏的computedWatcherOptions參數傳遞了一個lazy爲true,會使得watch實例的dirty爲true */
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    /*組件定義的計算屬性不能與 data 和 property 重複定義*/
    if (!(key in vm)) {
      /*定義計算屬性*/
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      /*若是計算屬性與已定義的data或者props中的名稱衝突則發出warning*/
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

/*建立計算屬性的getter*/
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      /*實際是髒檢查,在計算屬性中的依賴發生改變的時候dirty會變成true,在get的時候從新計算計算屬性的輸出值 *若依賴沒發生變化,直接讀取 watcher.value */
      if (watcher.dirty) {
        watcher.evaluate()
      }
      /*依賴收集*/
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

複製代碼

computed 和 watch 主要區別在於使用場景,計算屬性更適用於模板渲染,依賴其餘對象值的變化,作從新計算在渲染,監聽多個值來改變一個值。而監聽屬性 watch ,是用於監聽某一個值的變化,進行一系列複雜的操做。監聽屬性能夠支持異步,計算屬性只能是同步。

vue 對數組的處理

在官方文檔上關於數組的注意事項有這麼一段

因爲 JavaScript 的限制,Vue 不能檢測如下數組的變更:

  1. 當你利用索引直接設置一個數組項時,例如:vm.items[indexOfItem] = newValue
  2. 當你修改數組的長度時,例如:vm.items.length = newLength

先看第二點,這是由於 Object.defineProperty 不能監聽數組的長度,因此直接修改數組長度是無法被監聽到的。

關於第一點,咱們看看源碼的實現

// vue\src\core\observer\index.js
constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    // 在 Observer 構造函數中,對數組類型進行特殊處理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
// observeArray 的實現
/** * Observe a list of Array items. */
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
    observe(items[i]) // 對數組的值在進行 observe
  }
}
複製代碼

observeArray 是對數組中的值進行監聽,並非數組下標,因此經過索引來修改值是監聽不到了,假如是經過監聽索引的話,那是能夠實現的。那爲啥不監聽下標呢?在 vue issue 中好像記得尤大說是性能考慮。

由於是對數組元素作的監聽,那麼數組 api 形成的修改天然就無法監聽到了,因此 vue 對數組的方法進行了變異,包裹了一層,本質仍是執行數組的 api

// vue\src\core\observer\array.js
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function (method) {
  // cache original 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)
    // notify change 調度中心通知訂閱者
    ob.dep.notify()
    return result
  })
})
複製代碼

vue 中 key 的做用

有兩點用處:快速節點比對和節點惟一標識

利用快速節點比對

用做於 vnode 的惟一標識,便於更快更準確的在舊節點列表中查找節點

在內部對兩個節點進行比較的時候,會優先判斷 key 是否一致,以下,若是 key 不一致,立馬就能夠得出結果

function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  )
}
複製代碼

列表節點惟一標識

列表循環 v-for="i in dataList" 會有提示咱們須要加上 key ,由於循環後的 dom 節點的結構沒特殊處理的話是相同的, key 的默認值是 undefined ,那麼按照上面 sameVnode 的算法,新生成的 Vnode 與 舊的節點的比較結果就是相同的,vue會對這些節點嘗試就地修改/複用相同類型元素的,這種模式是高效,可是這種模式會有反作用,好比節點是帶有狀態的,那麼就會出現異常的bug,因此這種不寫 key 的默認處理只適用於不依賴其餘狀態的列表。

注意:在不知道哪一個版本,vue 對 for 遍歷中未設置 key 的狀況,內部作了處理,默認生成一個 key , 因此從此就算不設置 key 也是容許的了。

function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  const res = []
  let i, c, last
  for (i = 0; i < children.length; i++) {
        // .... 忽略其餘代碼
        // default key for nested array children (likely generated by v-for)
        if (isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__` // set key 設置默認 key
        }
        res.push(c)
      }
    }
  }
  return res
}
複製代碼

利於節點高效查找

同一層vnode節點是以數組的方式存儲,那麼若是節點很是多,經過遍歷查找就稍微有點慢,所以,內部將 vnode 列表轉換成對象,代碼以下:

/* 生成一個key與舊VNode的key對應的哈希表 好比childre是這樣的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}] beginIdx = 0 endIdx = 2 結果生成{key0: 0, key1: 1, key2: 2} */
function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}
複製代碼

這樣一來,就能夠直接經過 key 查找到數組下標,利於加快查找時間。

參考文檔: muyiy.cn/question/fr…

虛擬dom 與直接操做 dom 相比哪一個更快?

如下是根據尤大在知乎的回答,作的總結:

  1. 首先是沒有任何一個框架能夠比純手動優化操做 dom 快,由於框架的 dom 操做層須要應對上層API可能發生的操做,因此它的實現是普適性的,因此不可能對每一個場景作優化,這就是個性能和可維護性的取捨。各大框架能夠給到即便不須要手動優化,也能夠提供較優秀的性能。
  2. 咱們看看二者的重繪性能消耗:
  • innerHTML: render html string O(template size) + 從新建立全部 DOM 元素 O(DOM size)
  • Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change) render Virtual DOM + diff O 顯然比渲染 html string 要慢,但咱們知道,這是純 js層面的計算相比, 與 DOM 層面的操做的開銷相比要小不少。 因此直接操做dom的開銷就和整個頁面相關,而虛擬dom的開銷就是 js層面的計算和計算後的 dom 的開銷,因此虛擬dom就能夠保證,無論頁面數據變化多少,每次計算後的重繪的性能都在可接受範圍內。
  1. 由於機制不同,那麼比較的時候就要看場合,好比是大量數據的更新仍是小量數據的更新。舉個例子,若是一個很是大的列表,數據全都發生了變化,那麼直接操做dom確定是更快的,那若是隻是其中的幾行發生了變化,直接全量替換dom的開銷可就大了,而虛擬dom在計算後,只須要替換個別dom便可
  2. 虛擬dom提供給開發者的價值不是性能,而是 1.爲函數式的UI編程打開大門 2.擴展性強,能夠渲染到 DOM 意外的其餘平臺
  3. 那若是開發中遇到特殊的狀況致使虛擬dom的更新效率不知足,那麼能夠犧牲必定的維護性來本身手動進行優化

參考文檔: www.zhihu.com/question/31…

vue 能夠定義函數式組件麼

函數式組件:沒有狀態(data),沒有生命週期,只接受傳遞的 props ,經常使用於純 UI 組件 定義:

  • 經過 Vue.component 構建組件時,添加 functional: true; 須要經過調用 render 函數來渲染,經常使用包裹組建或者構建高階組件
  • 對於單文件組件,在 template 上添加 functional <template functional>

v-model 的實現

經常使用於輸入框的雙向數據綁定,但我知道 vue 是單向數據流的,因此 v-model 實際上是個封裝的指令,本質是對 input 的 @input 事件作了封裝,以下代碼:

<input @input="change" :obj="obj">
change(e) {
    this.obj = e.target.value;
}
複製代碼

也能夠在自定義組件上使用,簡化編碼,邏輯更加清晰

vue 有幾種構建版本

  1. 有生成版本和開發版本的區分;
  2. 完整版和運行時版本的區分,完整版包含編譯器(用於生成渲染函數);
  3. 構建環境的區分,支持 UMD(AMD和commonjs)、commonjs、ES Module(用於構建工具的)、ES Module(用於瀏覽器的)

vuex中爲何把把異步操做封裝在action,把同步操做放在mutations?

mutations 同步主要是爲了能用 devtools 跟蹤狀態的變化,每次執行完後,就能夠當即獲得下一個狀態,這樣在devtools調試工具中,就能夠跟蹤到狀態的每一次變化,能夠作時間旅行 time-travel ,那麼若是是異步的話,就無法知道狀態何時被更新,因此就有了一個 actions 用來專門處理異步函數,但要求狀態的須要觸發 mutations ,這樣一來對於異步的更新也能夠清晰看到狀態的流轉。

參考文檔: www.zhihu.com/question/48…

vuex getter方法跟直接 state 中獲取有什麼區別

getter 相似於計算屬性,帶有緩存;當只有響應的屬性發生變化纔會更新緩存,相比直接獲取效率更好,在設計上能夠便於抽象邏輯。

vue-router 中的 link 跳轉和 a 連接跳轉的區別

  • 判斷是否有 onclick 事件,有就執行
  • 阻止默認事件
  • 使用 history.replace 或 history.push 修改地址欄,同時不觸發頁面刷新

工具相關

webpack 中的 loader 和 plugin 是幹什麼的

loader:對文件進行轉換,好比說將 ts 編譯成 js ,css預處理等等 plugin:經過監聽 webpack 運行中的廣播事件,從而進行本身的操做,如經常使用的 HtmlWebpackPlugin:建立html文件、webpack.optimize.UglifyJsPlugin:混淆壓縮

webpack 的 externals

防止將外部引用的包打包到 bundle 中,而是在運行時經過模塊化的方式從外部引用。 好比咱們經過cdn引用 jquery ,咱們不但願jq打包到 bundle 中,並且在使用時但願能經過模塊化的方式引用,那麼能夠以下配置

module.exports = {
  //...
  externals: {
    jquery: 'jQuery'
  }
};
複製代碼

接口怎麼防刷?

被人經過工具在短期內惡意大量請求服務端。經常使用優化方式以下

  • referer 校驗 UA 校驗
  • 客戶端和服務端約定簽名算法,由服務端校驗簽名
  • 服務端對請求 ip 單位時間內請求數量限制
  • 經過前置交互式驗證手段,先驗證經過在接收請求

什麼是前端工程化?

關於工程化,每一個人都有本身的理解,如下是我我的的理解,每一個點均可以展開不少點來講,這題更多對於工做中的總結

  1. 協做上:
  • 統一開發規範,代碼、命名規範,引用語法檢查工具
  • 版本管理,提交規範
  1. 項目架構上:
  • 模塊化、組件化,沉澱組件庫,下降編碼間的耦合
  • 團隊統一腳手架
  1. 構建:
  • 資源壓縮、混淆
  • 圖片處理
  1. 持續集成、部署
  • 減小人爲參與,Jenkins CI
  • 靜態資源分離,cdn、靜態文件服務器
  1. 質量跟蹤:
  • 持續的單元測試、mocha Istanbul
  • 監控
  1. 用戶體驗:
  • 性能優化,客戶端、服務端、代理服務器的

小結

由於對 react 接觸很少,因此基本沒問 react 的問題。工具相關的問題都是由某一個知識點引伸出來的,好多都想不起來了,這一塊仍是關乎有沒有在工做中有相關的實踐。

相關文章
相關標籤/搜索