Vue源码
观察者模式 & 发布订阅
观察者模式:中心一对多 + 系统单点间的灵活和拓展(广播的方式)
发布订阅:将注册列表遍历发布给订阅者
initInject initState initProvide他们挂载顺序为什么这样设计?
initstate 是将 data | props & methods & watch | computed 初始化
那么你需要 provide 就必须要存在 data | props...等等初始化数据
inject 为什么要在 initstate 之前呢?
因为如果我们当前实例有用到 Inject 的地方 那么必须要先 inject 才能操作它
Vue3做了哪些优化
源码结构上:
monorepo(原子结构 可独立拆分引用)(可作业务上的拆分)
性能上:
移除了很多使用率比较低的api
tree-shaking => 打包产物优化 按需引入
编译上:
compoile阶段 静态模板 进行分析 => 分析树 <= PatchFlag
数据劫持上:
Object.defineProperty 本身无法检测对象属性的增加或者删除
Vue2使用$set $delete 数组 push pop ... 层级较深 => 递归遍历
Vue3 proxy => 底层优化
模板编译分段
词法分析阶段: template(baseCompile => baseParse) => AST
指令和语法的转化阶段(transform => node节点打标签): AST => 解析不同的节点进行区分 => 不同类型转化
可执行函数的生成阶段(generate):转化后的AST生成渲染函数
基于Proxy的响应式
数据劫持 | 数据响应(reactive):数据变化 => 函数监听执行
依赖收集(effect)
当前vm实例上挂载effect => 当前activeEffect切换为effect => 在effect上创建deps
属性,用于传递依赖
Vue2中 Watcher和Dep之间怎么处理的?
本质上就是 当render函数读取getter响应式变量的时候 会触发依赖收集 创建一个Watcher
当这个变量被setter的时候 会告诉Watcher去让组件重新生成render函数
手写一个响应式
js
class Vue {
constructor(options) {
const data = options.data
this._data = data
// 数据劫持 => initData
_proxy(this, '_data', data)
// 核心逻辑
observe(data)
new Watch(this, function() {
return data.name + '创建响应式'
}, function() {
console.log('watch cb:', this.value)
})
}
}
const _proxy = function(vm, sourceKey, data) {
const keys = Object.keys(data);
keys.forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm[sourceKey][key]
},
set(val) {
vm[sourceKey][key] = val
}
})
})
}
const observe = function(data) {
const ob = new Observer(data)
}
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key)
})
}
}
const defineReactive = function(obj, key) {
let val = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('依赖收集')
dep.depend()
return val
},
set(newVal) {
console.log('派发更新')
val = newVal
dep.notify()
}
})
}
class Dep {
constructor() {
this.id = Dep.uid++
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
removeSub(sub) {
const subIndex = this.subs.indexOf(sub)
this.subs.splices(subIndex, 1)
}
}
Dep.uid = 0
Dep.target = null
class Watch {
constructor(vm, render, cb) {
this.vm = vm
this.render = render
this.cb = cb
this.deps = []
this.depsIds = new Set()
this.newDeps = []
this.newDepsIds = new Set()
this.value = this.get()
this.cb(this.value)
}
get() {
Dep.target = this
this.newDeps = []
this.newDepsIds = new Set()
const value = this.render()
Dep.target = null
this.deps.forEach(oldDep => {
const notExistInNewDeps = !this.newDepsIds.has(oldDep.id)
if (notExistInNewDeps) {
oldDep.removeSub(this)
}
})
this.deps = this.newDeps
this.depsIds = this.newDepsIds
return value
}
addDep(dep) {
const depId = dep.id
if (!this.newDepsIds.has(depId)) {
this.newDeps.push(dep)
this.newDepsIds.add(depId)
if (!this.depsIds.has(depId)) {
dep.addSub(this)
}
}
}
update() {
this.value = this.get()
this.cb(this.value)
}
}
let zhaowa = new Vue({
data: {
name: 'yy',
course1: 100,
course2: 99
}
})
zhaowa.course1 = 1
zhaowa.course2 = 2