1.initData 初始化state
对data中的对象和数组通过Observer递归劫持(Object.defineProperty),数组劫持的是push,unshift,splice等方法,添加进对象时劫持并将对象通过Observer劫持
<div id="app">hello {{ msg }}</div>
<script>
let vm = new Vue({
el:"#app",
data() {
return {
msg: "world"
}
}
})
</script>
2.获取dom
将模板进行编译变成ast语法树,变成render(),生成虚拟节点(vnode),变成真实dom,放到页面上
1.ast语法树:
解析方式:
获取到html(el.outerHtml):
<div id="app">hello {{ msg }}</div>
通过正则从头开始匹配
while(html)循环,将匹配到的转换为ast后就删除,直到字符串为空
<div id="app">hello {{ msg }}</div> =>
id="app">hello {{ msg }}</div> =>
hello {{ msg }}</div> =>......
最终变成以下ast语法树格式
{
tag: "div",
attrs: [{name:'id',value:'app'}],
type: 1,
parent: null,
children: [{type: 3,text: "hello {{ msg }}"}]
}
2.render():
code = `_c('div',{"id": "app"}, _v("hello"+_s(msg)))`
此时的render为字符串, 要变成函数
let render = new Function(`with(this){
return ${code}
}`)
// with: 目的是在实例中调用render函数的时候,能够获取到在vue实例中挂载的_c, _v, _s
let obj = {a:1,b:2}
with(obj) {
console.log(a,b) //1,2
}
在vue实例中挂载_c(处理标签)、_v(处理文本)、_s(处理变量)
3.虚拟dom:
{
tag: 'div'
data: {
id: 'app'
},
key: undefine,
children: [
{
tag: undefine,
data: undefine,
text: 'hello world',
key: undefine,
children: undefine
}
],
}
4.真实dom
遍历虚拟dom 通过creatElement创建元素,appendChild添加子元素,得到真实的DOM(html结构)
<div id="app">
hello world
</div>
获取到父元素body,并插入到body中,删除旧的dom
3.生命周期
将全局的生命周期和各组件的生命周期按先后顺序通过 策略模式混入到一起,挂载到$options上
合并后:
{
$el: "app",
$options: {
created: [a,b,c] //a b为全局混入的created,c为组件内的created 三个都是函数
data: [],
watch: []
}
}
调用生命周期(设计模式,订阅发布):
遍历created: [a,b,c]
init初始化时调用create
挂载dom时调用mount
4.Watcher 自动更新视图
不能每次都手动调用vm._updata
第四个参数判断是初次渲染还是更新
1.Watcher
import {pushTarget,popTarget} from "./Dep"
let id = 0
class Watcher {
constructor(vm,updataComponent,cb,options){
this.vm = vm
this.exprOrfn = updataComponent
this.cb = cb
this.options = options
this.id = id++ //每一次new Watcher 都会生成一个id
if(typeof updataComponent === 'function'){
this.getter = updataComponent //用来更新视图
}
}
//更新视图
this.get()
//初次渲染
get() {
pushTarget(this)//给dep 添加 watcher
this.getter() //渲染
popTarget()//给dep 取消 watcher
}
//更新
update() {
this.getter()
}
}
export default Watcher
收集依赖 vue dep watcher data:{name,msg} dep: dep和data中的属性是一一对应 watcher: 比如dep.name(data.name), 在视图上用到了几处,就有几个watcher,用来在数据变化时更新视图 dep与watcher: 一对多(下面会有多对多) dep.name=[watcher1,watcher2]
2.Dep
class Dep {
constructor() {
this.subs = []
}
//收集依赖
depend() {
this.subs.push(Dep.target)
}
//更新
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
// *******************此时的Dep.target是一个全局变量**********************
//Vue源码中,Dep.target 是在 Dep 类中定义的一个静态属性
Dep.target = null
//添加watcher
export function pushTarget(watcher) {
Dep.target = watcher
}
export function popTarget() {
Dep.target = null
}
export default Dep
3.收集依赖
劫持对象数据时收集依赖
4.更新视图
数据发生改变时,发布通知,更新视图
//更新
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
//更新
update() {
this.getter()
}
5.双向依赖收集
dep和watcher多对多 (computed):
let id = 0
class Dep {
constructor() {
this.id = id++
this.subs = []
}
//收集依赖
depend() {
//希望watcher可以存放dep ****************************************
//双向记忆
// this.subs.push(Dep.target)
Dep.target.addDep(this)
}
//***********************
addSub(watcher) {
this.subs.push(watcher)
}
//更新
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
// *******************此时的Dep.target是一个全局变量**********************
//Vue源码中,Dep.target 是在 Dep 类中定义的一个静态属性
Dep.target = null
//添加watcher
export function pushTarget(watcher) {
Dep.target = watcher
}
export function popTarget() {
Dep.target = null
}
export default Dep
import {pushTarget,popTarget} from "./Dep"
let id = 0
class Watcher {
constructor(vm,updataComponent,cb,options){
this.vm = vm
this.exprOrfn = updataComponent
this.cb = cb
this.options = options
this.id = id++ //每一次new Watcher 都会生成一个id
this.deps = [] //watcher存放dep
this.depsId = new Set() //存放id set可以去重
if(typeof updataComponent === 'function'){
this.getter = updataComponent //用来更新视图
}
}
//更新视图
this.get()
addDep() {
//1.去重
let id = dep.id
if(this.depsId.has(id)) {
this.deps.push(dep)
this.depsId.add(id)
deps.addSub(this)
}
}
//初次渲染
get() {
pushTarget(this)//给dep 添加 watcher
this.getter() //渲染
popTarget()//给dep 取消 watcher
}
//更新
update() {
this.getter()
}
}
export default Watcher
双向依赖收集的过程如下:
- 首先,在执行模板编译时,会遍历模板中的每个指令、表达式或计算属性,为每个表达式创建一个 Watcher 实例。
- 当创建 Watcher 实例时,会将当前的 Watcher 对象赋值给 Dep.target,然后执行表达式的求值操作。在求值过程中,如果访问了响应式数据(如访问了 data 对象的属性),就会触发属性的 getter 方法。
- 在属性的 getter 方法中,会判断当前是否存在 Dep.target(即是否正在进行依赖收集),如果存在,则将当前的 Dep 实例添加到 Watcher 实例的依赖列表中,并同时将 Watcher 实例添加到 Dep 实例的观察者列表中。这样就建立了 Dep 和 Watcher 之间的双向依赖关系。
- 接着,如果在表达式的求值过程中,访问了其他的响应式数据,同样会触发相应的 getter 方法,进而进行依赖收集,建立其他属性与当前 Watcher 之间的依赖关系。
- 当任意一个响应式数据发生变化时,对应的 setter 方法会通知相应的 Dep 实例,然后 Dep 实例会遍历其中的所有观察者,并调用观察者的 update 方法进行更新。
通过这种双向依赖收集的机制,Vue 能够准确地追踪数据的依赖关系,并在数据变化时自动触发相关的依赖更新,保证了视图与数据的同步。 需要注意的是,Vue 3 对于依赖收集的方式进行了一些改进,使用了 Proxy 对象和 ES6 的原生 Proxy API 来代替了 Vue 2 中的双向依赖收集机制,以提高性能和效率。
6.数组收集
思路: 1.给所有对象类型增加一个dep (数组[]的类型是object)