当你老了,回顾一辈子,就会发觉:何时出国读书,何时决定作第一份职业,什么时候选定对象而恋爱,何时结婚,其实都是命运的巨变。只是当时站在三岔路口,眼见风云千樯,你作出选择的那一日,在日记上,至关沉闷和平凡,当时还觉得是生命中普通的一天。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 月份更新完毕,欢迎你一块儿参与本图谱的建设。
面试图谱 项目地址,若是你以为项目还不错,你能够点个小星星支持咱们一下,你的支持是咱们更新的源动力。