vue响应式原理
Observer类通过Object.defineProperty处理所有属性(包括子属性)object数据,实现object数据可观测。- 封装了依赖管理器
Dep,用于存储收集到的依赖。在getter中收集依赖,在setter中通知依赖更新 - 每一个依赖都创建了一个
Watcher实例,当外界通过Watcher读取数据时,会触发getter通过dep.depend()从而将Watcher添加到依赖Dep中;数据发生了变化时,会触发setter通过dep.notify()通知Watcher实例,由Watcher实例去做真实的更新操作。 Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。
export default class Dep {
constructor () {
this.subs = [] // watcher
}
// 添加一个依赖
depend () {
if (window.target) {
this.addSub(window.target)
}
}
// 通知所有依赖更新
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function defineReactive (obj,key,val) {
if (arguments.length === 2) {
val = obj[key]
}
if(typeof val === 'object'){
new Observer(val)
}
const dep = new Dep() //实例化一个依赖管理器,生成一个依赖管理数组dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
dep.depend() // 在getter中收集依赖
return val;
},
set(newVal){
if(val === newVal){
return
}
val = newVal;
dep.notify() // 在setter中通知依赖更新
}
})
}
export default class Watcher {
constructor (vm,expOrFn,cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn)
this.value = this.get()
}
get () {
window.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined;
return value
}
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
模板编译
-
一堆字符串模板解析成抽象语法树
AST后,我们就可以对其进行各种操作处理了,处理完后用处理后的AST来生成render函数 -
compileToFunctions函数是模板编译的入口函数,包含parse和generate的执行,返回值是一个render函数- 模板解析阶段:将一堆模板字符串用正则等方式解析成抽象语法树
AST;解析器(parser)模块 - 优化阶段:遍历
AST,找出其中的静态节点,并打上标记; - 代码生成阶段:将
AST转换成渲染函数;
- 模板解析阶段:将一堆模板字符串用正则等方式解析成抽象语法树
生命周期
父子组件生命周期调用顺序
beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。

- 初始化阶段:为
Vue实例上初始化一些属性,事件以及响应式数据;- new Vue
- initLifecycle
- initEvents
- initInjections
- initState
- 该初始化函数内部总共初始化了5个选项,分别是:
props、methods、data、computed和watch。 - 这5个选项的初始化顺序不是任意的,而是经过精心安排的。只有按照这种顺序初始化我们才能在开发中在
data中可以使用props,在watch中可以观察data和props。
- 该初始化函数内部总共初始化了5个选项,分别是:
- 模板编译阶段:将模板编译成渲染函数;
- 挂载阶段:将实例挂载到指定的
DOM上,即将模板渲染到真实DOM中;vm.$mounted- 第一部分是将模板渲染到视图上,第二部分是开启对模板中数据(状态)的监控。
- 销毁阶段:将实例自身从父组件中删除,并取消依赖追踪及事件监听器;
vm.$destory- 将当前的
Vue实例从其父级实例中删除,取消当前实例上的所有依赖追踪并且移除实例上的所有事件监听器。
- 将当前的
简单的Vuex实现
const createStore = ({ state, mutations }) {
return new Vue({
data: { state },
methods: {
commit (mutationType) {
mutations[mutationType](this.state)
}
}
})
}
const store = createStore({
state: { count: 0 },
mutations: {
inc (state) {
state.count++
}
}
})
const Counter = {
render(h) {
return h('div', store.state.count)
}
}
new Vue({
el: '#app',
components: { Counter },
methods: {
inc() {
store.commit('inc')
}
}
})
简单的vue-router实现
// '/user/123?foo=bar'
// path-to-regexp 匹配路由
const routerObj = {
path: '/user/123',
params: { username: '123' },
query: { foo: 'bar' }
}
const Bar = { template: `<div>bar</div>`}
const Foo = { template: `<div>foo</div>`}
const NotFound = { template: `<div>notFound</div>`}
const routerTable = {
'foo/:id': Foo,
'bar': Bar
}
// 匹配路由参数
const compiledRouterTable = []
Object.keys(routerTable).forEach(path => {
const dynamicSegments = []
const regex = pathToRegex(path, dynamicSegments)
const component = routerTable[path]
compiledRouterTable.push({
component,
regex,
dynamicSegments
})
})
window.addEventListerner('hashchange', () => {
app.url = window.location.hash.slice(1)
})
const app = new Vue({
el: '#app',
url: window.location.hash.slice(1)
render (h) {
const path = '/' + this.url
let componentToRender
let props = {}
// 根据已经存的取组件和参数
compiledRouters.some(router => {
// foo/123 => match [foo/123 123] segments {name: id} => props { id: 123 }
const match = router.regex.exec(path)
if(match) {
componentToRender = router.component
router.dynamicSegments.forEach((segment, index) => {
props[segment.name] = match[index + 1]
})
}
})
return h('div', [
h(componentToRender, { props }),
h('a', { attrs: { href: '#foo/123' }}, 'foo 123'),
' | ',
h('a', { attrs: { href: '#foo/234' }}, 'foo 234'),
' | ',
h('a', { attrs: { href: '#bar' }}, 'bar'),
])
}
})
const validationPlugin = {
install (Vue) {
Vue.mixin({
computed: {
$v() {
const rules = this.$options.validations
}
}
})
}
}
diff算法
当数据改变时,会触发setter,并且通过Dep.notify去通知所有订阅者Watcher,订阅者们就会调用patch方法,给真实DOM打补丁,更新相应的视图。
-
Diff算法是一种对比算法。对比两者是
旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,而不用更新其他数据没发生改变的节点,实现精准地更新真实DOM,进而提高效率。虚拟DOM算法操作真实DOM,性能高于直接操作真实DOM -
Diff算法比较只会在同层级进行,深度优先算法
。 时间复杂度:O(n) -
patch 对比当前同层的虚拟节点是否为同一种类型的标签
都有子节点执行
updateChildren函数比较子节点1、
oldS 和 newS使用sameVnode方法进行比较,sameVnode(oldS, newS)2、
oldS 和 newE使用sameVnode方法进行比较,sameVnode(oldS, newE)3、
oldE 和 newS使用sameVnode方法进行比较,sameVnode(oldE, newS)4、
oldE 和 newE使用sameVnode方法进行比较,sameVnode(oldE, newE)5、如果以上逻辑都匹配不到,再把所有旧子节点的
key做一个映射到旧节点下标的key -> index表,然后用新vnode的key去找出在旧节点中可以复用的位置。
nextTick源码
let callbacks = []; //回调函数
let pending = false;
function flushCallbacks() {
pending = false; //把标志还原为false
// 依次执行回调
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}
let timerFunc; //先采用微任务并按照优先级优雅降级的方式实现异步刷新
if (typeof Promise !== "undefined") {
// 如果支持promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== "undefined") {
// MutationObserver 主要是监听dom变化 也是一个异步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== "undefined") {
// 如果前面都不支持 判断setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后降级采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
timerFunc();
}
}
术语
搜索引擎优化 SEO Search Engine Optimization
单页面 SPA
服务器渲染 SSR
函数式编程
"函数式编程"是一种"编程范式"(programming paradigm)函数式编程是把运算过程尽量写成一系列嵌套的函数调用
- 函数是"第一等公民"
- 只用"表达式",不用"语句"
- 没有"副作用"
- 不修改状态
- 引用透明
关系型数据库复杂模型比如a.b.c这种关联可以通过id来维护关系来实现数据的扁平化