當你老了,回顧一輩子,就會發覺:何時出國讀書,何時決定作第一份職業,什麼時候選定對象而戀愛,何時結婚,其實都是命運的鉅變。只是當時站在三岔路口,眼見風雲千檣,你作出選擇的那一日,在日記上,至關沉悶和平凡,當時還覺得是生命中普通的一天。html
一個改變面試的項目 -- 面試圖譜。前端
金九銀十的秋招季近在眼前,想必你們也都心癢難耐,準備挑戰更好的工做機會。那麼,面試確定是最大的挑戰。react
對於面試來講,平時的積累確定是必須的,可是在面試前的準備也是相當重要的。git
在幾月前我我的組建了一個小團隊,花了將近半年的時間尋找大廠的面試題,篩選出了近百個知識點而後成文,並所有翻譯爲英文。今天,終於開源出了第一個版本,目前總字數已高達 10 餘萬字。團隊的每一個成員都是來自一線的工程師,其中還有一個來自谷歌的大佬。github
能夠這樣說,咱們有自信,把這個項目打形成關於面試相關中的 NO 1. 由於全部內容都沒有照搬任何的書籍,每個知識點都有詳細的查閱過資料才成文。後期更會加入更多的內容,儘量地覆蓋大部分知識點。面試
咱們認爲,一味的背面試題是沒多大做用的。只有熟悉了各個知識點並融會貫通,才能在面試中披荊斬棘。本圖譜目前包含了近百個高頻知識點,不管是面試前的準備仍是平時學習中的查漏補缺,咱們相信確定能幫助到你們。目前內容包含了 JS、網絡、瀏覽器相關、性能優化、安全、框架、Git、數據結構、算法等內容,不管是基礎仍是進階,亦或是源碼解讀,你都能在本圖譜中獲得滿意的答案,但願這個面試圖譜可以幫助到你們更好的準備面試。算法
該倉庫內容會持續更新,後期將會包含更多的內容,好比:系統設計、區塊鏈、運維、後端等等,固然這些不是個人強項,我會邀請這方面有不錯經驗的朋友來書寫內容。小程序
最近本人在尋找工做機會,若是有杭州的不錯崗位的話,歡迎聯繫我 zx597813039@gmail.com。後端
MVVM 由如下三個內容組成數組
在 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()
來實現雙向綁定,經過這個函數能夠監聽到 set
和 get
的事件。
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
}
})
}
複製代碼
以上代碼簡單的實現瞭如何監聽數據的 set
和 get
的事件,可是僅僅如此是不夠的,還須要在適當的時候給屬性添加發布訂閱
<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 來實現發佈訂閱的添加。
Obeject.defineProperty
雖然已經可以實現雙向綁定了,可是他仍是有缺陷的。
雖然 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 的變化,而後匹配路由規則,顯示相應的頁面,而且無須刷新。目前單頁面使用的路由就只有兩種實現方式
www.test.com/#/
就是 Hash URL,當 #
後面的哈希值發生變化時,不會向服務器請求數據,能夠經過 hashchange
事件來監聽到 URL 的變化,從而進行跳轉頁面。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美觀
JS 中分爲七種內置類型,七種內置類型又分爲兩大類型:基本類型和對象(Object)。
基本類型有六種: null
,undefined
,boolean
,number
,string
,symbol
。
其中 JS 的數字類型是浮點類型的,沒有整型。而且浮點類型基於 IEEE 754標準實現,在使用中會遇到某些 Bug。NaN
也屬於 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
對於基本類型,除了 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
複製代碼
在條件判斷時,除了 undefined
, null
, false
, NaN
, ''
, 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
複製代碼
toPrimitive
轉換對象unicode
字符索引來比較以上內容預計將於 9 月份更新完畢,歡迎你一塊兒參與本圖譜的建設。
面試圖譜 項目地址,若是你以爲項目還不錯,你能夠點個小星星支持咱們一下,你的支持是咱們更新的源動力。