vue的构建流程
1.初始化数据
new Vue调用原型链上的_init方法,将this挂载到vm上,new Vue中的参数options挂载到$options上,根据参数开始初始化
如果传入了data,就初始化data
initData函数中,判断根组件的data是不是函数,是就调用call到vm上,是对象直接使用
2.响应式原理,数据劫持
在1的基础上,initData函数中拿到data后,observe(data)观察数据
observe中对对象进行劫持数据(null或者非对象直接return),为了避免重复劫持,用实例的形式即new Observer()
Observer类中Object.keys遍历使用Object.defineProperty,定义get/set方法
Object.defineProperty定义后,但是没有使用它,就没有绑定get/set方法,需要vm._data = data使用一下挂载到vm实例上
这样绑定只绑定了第一层,如果是引用类型,需要递归observe()
为了把数据绑定到vm上而不是vm._data要继续通过循环Object.defineProperty对每一个属性绑定get/set,这样就可以直接使用vm.name,vm.age了
3.数组的劫持
另外一种情况就是对数组的劫持,分为两种情况,一种是对数组的push/pop/shift/unshift/reverse/splice/sort的重写,一种是对数组里的对象直接添加响应式
重写push等:
通过Object.create(Array.prototype),在这个实例对象上去改写push方法,其他的方法如concat全部继承使用
然后通过push,unshift,splice添加的元素进行数据劫持,不可枚举的属性去拿到父类Observer上的观测数组方法递归使用即可
Observer类中判断isArray,forEach遍历添加observe响应式
4.模版引擎
响应式处理完成,接下来和视图挂钩,定义原型链上的$mount方法
new Vue中如果定义render函数,没有的话看看有template字段,没有的话通过el去拿元素,通过编译生成ast语法树和render方法
5.模版转ast语法树,语法树转为render函数
6.render函数的准备
通过5拿到了render函数的字符串,要转换为函数,就通过new Function(),
render中的参数是挂载在vm,需要用到with(vm){}
定义mountComponent实现组件的挂载,在mountComponent中调用_render函数
调用render生成虚拟dom
将虚拟dom转换成真实dom
插入到el元素中
初始化数据 + 响应式
模板引擎转化为ast语法树,生成render方法
后续每次更新只执行render方法就行了
7.实现render函数,从虚拟dom转向真实dom
render:
调用render函数,实现render中_c,_v,_s函数
_c:createElement转为元素节点
_v:createTextVNode转为文本节点
_s:文本转为stringify转为字符串
update:
首次渲染和后续更新,都会调用update中的patch函数
初次渲染的时候,判断是不是真实dom,真实dom通过removeChild移除,把虚拟dom转化成真实dom,通过render方法递归,将产生的真实dom insertBefore插入即可
问题是:
修改数据时,不能主动触发,还得需要vm._update(vm._render())才可以触发,怎么解决?-->依赖收集
8.依赖收集--观察者模式
- 1.创建Watcher类,在mountComponent中new Watcher传入vm._update(vm._render())方法,即初渲染和重新渲染时才去回调调用vm._update(vm._render())方法
- 2.在Watcher类中将渲染方法挂载到类上即get方法,调用this.get()方法相当于初渲染或重新渲染了(有取值的操作,即Object.defineProperty中的get方法)
- 3.需要将属性和watcher关联起来,在属性上添加dep,属性的dep和watcher是多对多的关系
- 4.新建Dep类,在响应式中Object.defineProperty中,new Dep给每个属性劫持添加dep
- 5.在Dep类上添加一个静态参数target,在Watcher中调用get渲染之前,把watcher赋值给target,渲染后赋值为null,这样渲染的时候触发defineproperty中get方法就能判断Dep.target是否存在,即是否经历了渲染过程
- 6.响应式的get方法中判断Dep.target是否存在,存在的话,往dep中手机watcher,同时watcher中收集dep
- 7.6中互相标记就完成了,在响应式的set中重新渲染就出发Dep.notify()方法,就可以遍历Dep所有相关的watcher,让这些watcher更新也就是触发watcher中的get方法