Vue源码学习 在今年的10月份尤大大在多伦多发布了在明年发布Vue3.0的消息,但是我自己对vue的源码不是很了解,虽然在去年看了一下vue的源码,了解到数据双向绑定的原理,但是其他的方面没了解过,后面有段时间想去继续了解,但是没有坚持,所以打算在vue3.0来之前弥补自己的过错。 将从这几个方面来学习vue的源码
数据响应之Observer
数据响应之Watcher
模板渲染之render
模板渲染之解析vnode
模板渲染之patch
时间期限:至今到2019-01-01
Vue的数据响应 Vue提供了基于依赖收集的数据绑定的机制,当你数据发生改变,就会立即通知该数据的依赖对象。想必大家都知道Vue2.0是依赖于Object.defineProperty重新定义属性,通过getter/setter函数实现数据响应。那现在我们来看看源码是怎样实现数据的响应。
==大家注意了,请系好安全带,Vue的公交车要发动了==
我们先来看一个Demo,通过小案例来看vue源码的构建过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > 源码学习</title > <script src ="./vue.js" > </script > </head > <body > <div id ="app" > <span > hello world !!!{{name}}</span > <p > {{message}}</p > <button @click ="changeName" > change name</button > </div > <script > var vm = new Vue ({ el :'#app' , data :{ name :'Ming' }, methods :{ changeName ( ){ this .name = 'UZI' } }, computed : { message ( ){ return `RNG ${this .name} ` } }, watch : { name ( ){ console .log ('change Name' ) } } }) </script > </body > </html >
Vue的构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Vue (options) { if (!(this instanceof Vue )) { warn ('Vue is a constructor and should be called with the `new` keyword' ); } this ._init (options); } initMixin (Vue );stateMixin (Vue );eventsMixin (Vue );lifecycleMixin (Vue );renderMixin (Vue );
可以从上面的Vue的构造函数看出,Vue构造时调用了_init的函数
进行配置项的初始化,忽略在开发模式下的警告外,你是不是觉得Vue的构造函数就一行,特别简单,接下来看看_init
函数中到底做了什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 function initMixin (Vue ) { Vue .prototype ._init = function (options ) { var vm = this ; vm._uid = uid$3 ++; var startTag, endTag; if (config.performance && mark) { startTag = "vue-perf-start:" + (vm._uid ); endTag = "vue-perf-end:" + (vm._uid ); mark (startTag); } vm._isVue = true ; if (options && options._isComponent ) { initInternalComponent (vm, options); } else { vm.$options = mergeOptions ( resolveConstructorOptions (vm.constructor ), options || {}, vm ); } { initProxy (vm); } vm._self = vm; initLifecycle (vm); initEvents (vm); initRender (vm); callHook (vm, 'beforeCreate' ); initInjections (vm); initState (vm); initProvide (vm); callHook (vm, 'created' ); if (config.performance && mark) { vm._name = formatComponentName (vm, false ); mark (endTag); measure (("vue " + (vm._name ) + " init" ), startTag, endTag); } if (vm.$options .el ) { vm.$mount(vm.$options .el ); } }; }
当你看到源码中的_init
函数,是不是感觉很复杂的,只要你仔细的阅读一下源码,你立马就能知道只有其中几行代码是核心的,现在我们去掉Vue警告和报错,还有一些判断条件,就可得到以下代码:
1 2 3 4 5 6 7 8 9 10 11 vm._self = vm; initLifecycle (vm);initEvents (vm);initRender (vm);callHook (vm, 'beforeCreate' );initInjections (vm); initState (vm); initProvide (vm); callHook (vm, 'created' );
这一段代码承担了Vue的初始化大部分的工作,也是Vue中最核心代码的所在地。只要我们弄懂了这个几个函数,那么我们差不多知道Vue的运行的工作原理,我们这一章中就会从其中的一个函数开始,大概大家都已经知道了。我们继续往下看:
1 2 3 4 // 将VM实例挂载到dom元素上 if (vm.$options.el) { vm.$mount(vm.$options.el); }
这里mount
函数通过传递的options的el元素挂载,我们也可以通过Vue官网提供的api函数$mount
将Vue实例挂载到dom
元素上。
不知不觉就已经完成了对Vue初始化,让我们通过_init函数深度探究Vue中数据响应是咋会事
大家提起精神来,这一章我们主要关注数据响应和处理,数据的依赖关系是怎样建立的;所以我们从data
开始,这时候initState
这扇门就需要你推开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function initState (vm) { vm._watchers = []; var opts = vm.$options ; if (opts.props ) { initProps (vm, opts.props ); } if (opts.methods ) { initMethods (vm, opts.methods ); } if (opts.data ) { initData (vm); } else { observe (vm._data = {}, true ); } if (opts.computed ) { initComputed (vm, opts.computed ); } if (opts.watch && opts.watch !== nativeWatch) { initWatch (vm, opts.watch ); } }
从上面代码可以看到主要是对props
,methods
,data
,computed
,watch
这五个部分,为了探究Vue的数据响应,我们先对其data
的初始化进行分析,看看initData
函数主要的工作是啥?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function initData (vm) { var data = vm.$options .data ; data = vm._data = typeof data === 'function' ? getData (data, vm) : data || {}; if (!isPlainObject (data)) { data = {}; warn ( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function' , vm ); } var keys = Object .keys (data); var props = vm.$options .props ; var methods = vm.$options .methods ; var i = keys.length ; while (i--) { var key = keys[i]; { if (methods && hasOwn (methods, key)) { warn ( ("Method \"" + key + "\" has already been defined as a data property." ), vm ); } } if (props && hasOwn (props, key)) { warn ( "The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead." , vm ); } else if (!isReserved (key)) { proxy (vm, "_data" , key); } } observe (data, true ); }
通过上面代码的注释,我们差不多了解到其他代码是起什么作用的,让我们来看最后一行代码:
其实只要我们弄懂最后一行代码,就大概了解了observe
是如何工作的,这是Vue数据响应的核心代码。
Vue的数据响应—Observer 上一节了解到Vue是怎么构造函数的,接下来我们去探究observe
是如何工作的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function observe (value, asRootData) { if (!isObject (value) || value instanceof VNode ) { return } var ob; if (hasOwn (value, '__ob__' ) && value.__ob__ instanceof Observer ) { ob = value.__ob__ ; } else if ( shouldObserve && !isServerRendering () && (Array .isArray (value) || isPlainObject (value)) && Object .isExtensible (value) && !value._isVue ) { ob = new Observer (value); } if (asRootData && ob) { ob.vmCount ++; } return ob }
其中有一些if条件判断,我们先不管if判断,代码精简一下,就是剩下两行:
1 2 3 4 5 function observe (value, asRootData) { <!--创建Observer 实例--> ob = new Observer (value); return ob }
通过精简的两行代码看到主要的逻辑就是创建Observer实例,但observe函数用于递归将传递进来的data
定义为响应式,直到传递来的value不是Object类型。那先让我们看看Observer
里面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var Observer = function Observer (value) { this .value = value; this .dep = new Dep (); this .vmCount = 0 ; def (value, '__ob__' , this ); if (Array .isArray (value)) { if (hasProto) { protoAugment (value, arrayMethods); } else { copyAugment (value, arrayMethods, arrayKeys); } this .observeArray (value); } else { this .walk (value); } };
其中的if判断,主要是对数组判断,如果是数组,需要对数组中的每一个值进行递归observe
,让数组中的数据被定义为响应式。
1 2 3 4 5 Observer .prototype .observeArray = function observeArray (items) { for (var i = 0 , l = items.length ; i < l; i++) { observe (items[i]); } };
显然如果不是,最终进入到walk
函数中。那我们看一下walk函数里面代码
1 2 3 4 5 6 Observer .prototype .walk = function walk (obj) { var keys = Object .keys (obj); for (var i = 0 ; i < keys.length ; i++) { defineReactive$$1 (obj, keys[i]); } };
这段代码主要逻辑是将对象的每个属性进行遍历,同时进行defineReactive$$1
操作,defineReactive$$1
函数里面用Object.defineProperty函数重新定义属性,通过getter/setter函数劫持读写操作,实现数据响应式。看到这个函数是不是很兴奋,我们已经来到了最核心的部分,也是Vue依赖收集的工作原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 function defineReactive$$1 (obj,key,val,customSetter,shallow) { var dep = new Dep (); var property = Object .getOwnPropertyDescriptor (obj, key); if (property && property.configurable === false ) { return } var getter = property && property.get ; var setter = property && property.set ; if ((!getter || setter) && arguments .length === 2 ) { val = obj[key]; } var childOb = !shallow && observe (val); Object .defineProperty (obj, key, { enumerable : true , configurable : true , get : function reactiveGetter () { var value = getter ? getter.call (obj) : val; if (Dep .target ) { dep.depend (); if (childOb) { childOb.dep .depend (); if (Array .isArray (value)) { dependArray (value); } } } return value }, set : function reactiveSetter (newVal) { var value = getter ? getter.call (obj) : val; if (newVal === value || (newVal !== newVal && value !== value)) { return } if (customSetter) { customSetter (); } if (getter && !setter) { return } if (setter) { setter.call (obj, newVal); } else { val = newVal; } childOb = !shallow && observe (newVal); dep.notify (); } }); }
前面我们提到observe
函数用于递归,直到value不是Object类型;上面代码中变量dep用于val的依赖收集,变量childOb用于obeserve函数递归。上面可以看到getter函数里面调用了observe函数,实现对每个属性进行重新定义,通过getter/setter实现数据(响应)劫持。通过getter获取依赖收集,在属性中的get函数中判断是否存在Watcher依赖,会将其保存到每个属性中的dep内。
那我们继续看看set
函数:
1 2 3 4 5 6 7 8 9 10 set : function reactiveSetter (newVal) { var value = getter ? getter.call (obj) : val; if (setter) { setter.call (obj, newVal); } else { val = newVal; } childOb = !shallow && observe (newVal); dep.notify (); }
我们删除一点警告性代码和if判断,主要代码其实做了两件事,一是如果存在自定义setter,就直接调用;二是通过dep.notify()
来通知所有的监听器,告诉他更新数据。
总结 就今天的Vue的数据响应,主要是通过Object.defineProperty函数重新定义对象属性,数据通过getter来收集依赖,在函数中dep添加对应的对象属性的Watcher依赖。同时还介绍了Vue的初始化。这一部分只是依赖收集的准备部分。接下来的一章将介绍Watcher和Observer之间联系和触发过程。今天这一章还有些需要添加的地方,在写第二章之前会补充完毕。