A: 为何要作源码解读?
Q: 咱们新到一个环境,第一件事情就是熟悉环境熟悉项目,这个很考验阅读源码的能力以及耐心。vue是个很好的库,知名度高,对js的学习具备向上性,因此搞清楚逻辑是有好处的。
A: 阅读源码的程度?
Q: 咱们彻底不必从头至尾细细品味,只须要知道一些核心的实现就行了,毕竟vue也算是个产品,咱们不必搞清楚一个产品,咱们只须要知道产品的核心就够了,多余的也是业务代码。(相对来讲)vue
new Vue({ // 初始化vue实例 //components: { App } // vue1.0的写法 render: h => h(App) // 最早执行,返回一个符合component的对象,vue2.0的写法 }) .$mount('#app') // 挂载vue实例到 ‘#app’
render: h => h(App) 就是 render:function(h){ return h(App) } 即 render: function (createElement) { return createElement(App) }
import Vue from 'vue'
找到node
node-modules/vue
打开package.json 找到react
"main": "dist/vue.runtime.common.js"
"main"是 npm模块曝光的主要文件.npm
打开vue.runtime.common.jsctrl/command + a, ctrl/command + k, ctrl/command + 1
,快捷键把全部方法折叠json
你会发现 update执行了2次,我明明只初始化了一次vue实例,为何update2次了呢?缘由在下方代表。数组
拉到最下面,app
module.exports = Vue;
导出的是一个Vue构造函数。
当前文件搜索 Vue 找到 构造函数dom
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); // 调用 初始化方法 }
var uid$3 = 0; vm._uid = uid$3++; //每一个vue实例 拥有惟一id,从0开始 ++ // 合并初始化vue实例的参数(入参options和默认参数) vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); initLifecycle();// 初始化实例生命周期相关的参数 使用Object.create(null)用来获取一个没有原型链的对象类型 initEvents(); // 初始化实例事件触发相关的参数 initRender(); // 初始化实例渲染相关的参数 //create的准备工做作好了,触发beforeCreate的生命周期 callHook(vm, 'beforeCreate'); initState(); // 初始化实例状态 // 状态也初始化好了,触发create的生命周期,因此 create和 breforCreate的区别就在 create的时候 有状态。 callHook(vm, 'created');
至此,vue实例的初始化完成,而后挂载到节点函数
// 将mount('#app') => query('#app') 查找到dom对象,赋值给vue.$el // 触发beforeMount的生命周期,因此beforeMount 和 create的区别就在 beforeMount的时候 有挂载节点。 callHook(vm, 'beforeMount'); // 拿到当前将要挂载的Vnode(虚拟dom对象) vm._render() vm.$vnode // (更新)渲染页面 vm._update(vm._render(), hydrating); vm.__patch__(); createElm(); // 按照虚拟dom生成真实dom
// 这是一个递归方法,vue实例的初始化是建立一个根节点,而后再将render函数传入的组件挂载,这就是流程图执行2次update的缘由。 // 若是是组件,则去作 组件的初始化 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } // 已经删减,只留主要逻辑,判断虚拟dom 的 tag属性 if (isDef(tag)) { { // 遍历虚拟dom的子节点,而且建立,而后递归当前方法。 createChildren(vnode, children, insertedVnodeQueue); if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue); } insert(parentElm, vnode.elm, refElm); } } // 若是没有子节点的,直接建立dom,而后插入 else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text); insert(parentElm, vnode.elm, refElm); } else { vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); } // 触发Mount的生命周期 callHook(vm, 'mounted');
至此就是 Vue根节点初始化挂载和渲染的流程.学习
首先 咱们改造下 app.vue,像官网同样,咱们新增一个双向绑定的文本框。
如今咱们知道了,第一次的update只是挂载了根节点,那么咱们新增了文本框的组件实际上是在第二次init的时候初始化的。
咱们能够着重看第二次的流程,搞清楚,数据的监听与更新。
在每次vue实例初始化的时候 都会执行initState,这里面作了vue实例 数据的监听。
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) { // 这里判断vm.$options.data,从而执行initData()或者 直接 监听 vm._data={}做为根状态 initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
vm.$options.data 哪里来的呢?
是在 Vue._init方法中
if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options); } else { vm.$options = mergeOptions( // 这个方法中 给data赋值,也就是咱们render中 的 data(){return{//咱们组件的数据}} resolveConstructorOptions(vm.constructor), options || {}, vm ); }
function initData (vm) { var data = vm.$options.data; // 把从render函数返回的data函数对象赋值给data,而后data.call(this,this),也就是vm.data();拿到data返回值 data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; // observe data observe(data, true /* asRootData */); } function getData (data, vm) { pushTarget(); try { return data.call(vm, vm) } catch (e) { handleError(e, vm, "data()"); return {} } finally { popTarget(); } }
function observe (value, asRootData) { 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); // 返回一个 新建的 Observer } return ob }
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); // 这里的dep 稍后会说 this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; // 若是是对象,则按照key来劫持 Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } }; // 若是是数组,就遍历每一个数组元素,再每一个元素再判断是否为数组,对象 Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };
function defineReactive ( obj, key, val, customSetter, shallow ) { var dep = new Dep(); // 这里的dep 稍后会说 var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; 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(); // 这里的dep 稍后会说 if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); // 这里的dep 稍后会说 } }); }
咱们在使用vue的时候,data方法会返回对象,包含了全部咱们想要观察的数据属性,一样 vue也会帮咱们监听这些属性的变化,可是,假如咱们在data中设置了多个属性,可是在模板中只使用了1个,又会如何呢?咱们在脚本中设置value2
的值(this.value3= 'hello world'),那么vue监听到变化 还会去通知模板从新渲染么?
new Vue({ template: `<div> <span>value1:</span> {{value1}} <div>`, data: { value1: 'value1', value2: 'value2', value3: 'value3', ... } });
天然是不会的,vue很聪明的使用了依赖收集
Dep : 一个订阅者的容器,能够增长或删除订阅者,能够向订阅者发送消息;
Watcher : 订阅者类。它在初始化时能够接受getter, callback两个函数做为参数。getter用来计算Watcher对象的值。当Watcher被触发时,会从新经过getter计算当前Watcher的值,若是值改变,则会执行callback.
未完待续。。。