前言
现在自己所用的技术栈基本都是vue2.0,之前有了解过vue的基本原理,即:数据劫持、收集依赖、派发更新。但具体的实现过程并没有去深究,这里想尝试去的去了解下vue2的源码,看看到底是如何实现的。由于自己的水平有限,可能不能深入地进行分析,只能简要说明源码各部分的用法跟自己的理解吧。第一篇中主要想解释下vue的初始化与其响应式配置的源码。
vue初始化
platforms/web/entry-runtime-with-compiler.js
不管你在初始化vue实例时,使用的是template、el、还是render函数,其判定的优先级是render > template > le。且这段代码会根据你使用的不同方式,采取不同的处理方法,最终都会得到渲染函数,也就是一个render方法,再将render函数放置在opions.render中。其实实现的就是扩展了$mount方法,能够处理el和template,执行模板解析与编译工作
platforms/web/runtime/index.js
1、安装web平台特有指令和组件;
2、实现patch方法:补丁函数,把用户传入的虚拟DOM转换成为真实DOM,其实只有2种执行情景:初始化时的赋值、每次用户更新DOM时的diff算法。
3、实现**mount就是传入一个宿主文件,将vue挂载在该宿主文件上。该功能就是初始化,然后将首次渲染的结果来替换el。
core/index.js
初始化全局api
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
initUse(Vue) // 实现Vue.use函数
initMixin(Vue) // 实现Vue.mixin函数
initExtend(Vue) // 实现Vue.extend函数
initAssetRegisters(Vue) // 注册实现Vue.component/directive/filter
core/instance/index.js
该文件定义了vue的构造函数方法,其实只是执行了this._init方法。
同时定义了并混入了很多的实例方法:1、和状态相关的实例方法stateMixin中的delete、data、on、off、nextTick:什么时候用,做了一个操作,同时你想立刻看到数据的结果,则你必须写在$nextTick的回调函数中,因为vue是一个异步更新的框架。
function Vue (options) {
// 构造函数仅执行了_init
this._init(options)
}
initMixin(Vue) // 实现init函数,通过该方法给vue添加_init方法。
stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch
eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit
lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy
renderMixin(Vue)// 渲染api _render,$nextTick
core/instance/init.js
创建组件实例的init方法的定义,初始化其数据、属性、事件等
initLifecycle(vm)
// $parent,$root,$children,$refs 设置跟生命周期相关的变量,
//先设置父组件、祖先组件,因此组件创建的顺序是自上而下的,但挂载的顺序是自下而上的。
initEvents(vm)
//添加监听父组件传递的事件和回调,因为事件谁派发,谁监听,事件本身就是子组件在派发。
initRender(vm)
//render(h) 此处的$createElement就是h;
//声明$slots,$scopedSlots,_c,$createElement,在此初始化插槽内容。
callHook(vm, 'beforeCreate')
//调用beforeCreate钩子
initInjections(vm) // 获取注入数据,依赖注入
initState(vm)
// 初始化props,methods,data,computed,watch,同时实现响应式。
initProvide(vm) // 提供数据注入,注入的数据是不会做响应式的
callHook(vm, 'created')
core/instance/lifecycle.js**
mountComponent:声明了updateComponent,且创建了一个与组件相关的Watcher。
render:渲染组件,调用用户定义的render方法,获取vdom
updateComponent:执行更新,将传入的
5-vdom转换为dom,初始化时执行的是dom创建操作,在任何组件发生变化时,updateComponent该函数都会重新再执行一次。
总体流程
new Vue():调用init => _init():初始化各种属性 => $mount():调用mountComponent => mountComponent:声明了一个updateComponent函数,并创建一个和组件相关的Watcher => render():获取虚拟DOM => update(Component):把虚拟DOM转换为真实DOM
数据响应式
src\core\instance\state.js
初始化数据,包括props、methods、data、computed和watch。
核心代码initData将data数据响应化,即代理这些数据到实例上,同时会判断是否有重复。
core/observer/index.js
observe方法返回一个Observer实例。如果已经存在则直接返回,不存在则新建实例对象。每一个响应式的数据对象,都会被附加上一个Observer来进行监听。
Observer对象根据数据类型执行对应的响应化操作,在保存值得同时,还会额外生成一个dep。DEP的用法:1、我们操作数组通常使用push、pop、splice等方法,此时是没有办法得知数组的变化的,这时候vue采取的策略是拦截这些方法,并通知对应dep。2、object里面新增或者删除属性,同样也会通知dep。
defineReactive定义对象属性的getter/setter,getter负责添加依赖,setter负责通知更新。如果有依赖存在,则收集依赖,同时会查询是否存在子ob,子ob也会收集这个依赖,同时如果值是数组,同样要将其所有项均添加依赖。有几个key就会有几个dep,Watcher一个组件只会有1个,Observer一个对象就会有一个。
core/observer/dep.js
Dep负责管理一组Watcher,包括watcher实例的增删及通知更新
Watcher
Watcher解析一个表达式并收集依赖,当数值变化时触发回调函数,常用于$watch API和指令中。
有对应的Watcher,数值变化会触发其update函数导致重新渲染。相关API: $watcher
src\core\observer\array.js
数组数据变化的侦测跟对象不同,我们操作数组通常使用push、pop、splice等方法,此时没有办法得知数据变化。所以vue中采取的策略是拦截这些方法并通知dep。为数组原型中的7个可以改变内容的方法定义拦截器Observer****中覆盖数组原型
vue2.0响应式的缺点:
1、递归、循环遍历比较多,性能会收到影响,因此vue3.0使用proxy来代替,只用在外面加上一层代理。
2、API不统一,对于数组与对象采取的是两套方法来进行实现。