在去年末開始換工做,直到如今算是告了一個段落,斷斷續續的也面試了很多公司,如今回想起來,那段時間經歷了被面試官手撕,被筆試題狂懟,悲傷的時候差點留下沒技術的淚水。javascript
這篇文章我打算把我找工做遇到的各類面試題(每次面試完我都會總結)和我本身複習遇到比較有意思的題目,作一份彙總,年後是跳槽高峯期,也許能幫到一些小夥伴。css
先說下這些題目難度,大部分都是基礎題,由於這段經歷給個人感受就是,無論你面試的是高級仍是初級,基礎的知識必定會問到,甚至會有必定的深度,因此基礎仍是很是重要的。html
我將根據類型分爲幾篇文章來寫:前端
面試總結:javascript 面試點彙總(萬字長文)(已完成) 強烈你們看看這篇,面試中 js 是大頭vue
面試總結:nodejs 面試點彙總(已完成)java
面試總結:瀏覽器相關 面試點彙總(已完成)node
面試總結:css 面試點彙總(已完成)react
面試總結:框架 vue 和工程相關的面試點彙總(已完成)jquery
面試總結:非技術問題彙總(已完成)webpack
我會抓緊時間把未完成的總結補全的~
這篇文章是對 框架 vue 和工程相關
相關的題目作總結,歡迎朋友們先收藏在看。
先看看目錄
這部分是 vue 相關的整理。
響應式原理是 vue 的核心思想之一,後續打算單獨整理一篇,這裏簡單介紹響應式的三大件
觀察者,使用 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(調度中心)添加觀察者。
扮演的角色是訂閱者,他的主要做用是爲觀察屬性提供通知函數,當被觀察的值發生變化時,會接收到來自訂閱中心 dep 的通知,從而觸發依賴更新。
核心方法有: get() 得到getter的值而且從新進行依賴收集 addDep(dep: Dep) 添加一個依賴關係到訂閱中心 Dep 集合中 update() 提供給訂閱中心的通知接口,若是不是同步的(sync),那麼會放到隊列中,異步執行,在下一個事件循環中執行(採用 Promise、MutationObserver以及setTimeout來異步執行)
扮演的角色是調度中心,主要的做用就是收集觀察者 Watcher 和通知觀察者目標更新。 每個屬性都有一個 Dep 對象,用於存放全部訂閱了該屬性的觀察者對象,當數據發生改變時,會遍歷觀察者列表(dep.subs),通知全部的 watch,讓訂閱者執行本身的 update 邏輯。
從編碼上 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 ,是用於監聽某一個值的變化,進行一系列複雜的操做。監聽屬性能夠支持異步,計算屬性只能是同步。
在官方文檔上關於數組的注意事項有這麼一段
因爲 JavaScript 的限制,Vue 不能檢測如下數組的變更:
先看第二點,這是由於 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 }) }) 複製代碼
有兩點用處:快速節點比對和節點惟一標識
用做於 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…
如下是根據尤大在知乎的回答,作的總結:
參考文檔: www.zhihu.com/question/31…
函數式組件:沒有狀態(data),沒有生命週期,只接受傳遞的 props ,經常使用於純 UI 組件 定義:
Vue.component
構建組件時,添加 functional: true;
須要經過調用 render 函數來渲染,經常使用包裹組建或者構建高階組件<template functional>
經常使用於輸入框的雙向數據綁定,但我知道 vue 是單向數據流的,因此 v-model
實際上是個封裝的指令,本質是對 input 的 @input
事件作了封裝,以下代碼:
<input @input="change" :obj="obj"> change(e) { this.obj = e.target.value; } 複製代碼
也能夠在自定義組件上使用,簡化編碼,邏輯更加清晰
mutations 同步主要是爲了能用 devtools 跟蹤狀態的變化,每次執行完後,就能夠當即獲得下一個狀態,這樣在devtools調試工具中,就能夠跟蹤到狀態的每一次變化,能夠作時間旅行 time-travel ,那麼若是是異步的話,就無法知道狀態何時被更新,因此就有了一個 actions 用來專門處理異步函數,但要求狀態的須要觸發 mutations ,這樣一來對於異步的更新也能夠清晰看到狀態的流轉。
參考文檔: www.zhihu.com/question/48…
getter
相似於計算屬性,帶有緩存;當只有響應的屬性發生變化纔會更新緩存,相比直接獲取效率更好,在設計上能夠便於抽象邏輯。
loader:對文件進行轉換,好比說將 ts 編譯成 js ,css預處理等等 plugin:經過監聽 webpack 運行中的廣播事件,從而進行本身的操做,如經常使用的 HtmlWebpackPlugin:建立html文件、webpack.optimize.UglifyJsPlugin:混淆壓縮
防止將外部引用的包打包到 bundle 中,而是在運行時經過模塊化的方式從外部引用。 好比咱們經過cdn引用 jquery ,咱們不但願jq打包到 bundle 中,並且在使用時但願能經過模塊化的方式引用,那麼能夠以下配置
module.exports = { //... externals: { jquery: 'jQuery' } }; 複製代碼
被人經過工具在短期內惡意大量請求服務端。經常使用優化方式以下
關於工程化,每一個人都有本身的理解,如下是我我的的理解,每一個點均可以展開不少點來講,這題更多對於工做中的總結
由於對 react 接觸很少,因此基本沒問 react 的問題。工具相關的問題都是由某一個知識點引伸出來的,好多都想不起來了,這一塊仍是關乎有沒有在工做中有相關的實踐。