說明
vue源碼1萬多行,徹底解析透太耗時間;裏面細節處理不少,通讀代碼,語法都不難;我的認爲重點在於理解它的思想,掌握面向數據編程的原理。
經過一個合適的例子,斷點調試來查看代碼運行流程,能夠快速瞭解編碼的思路。
vue@2.5.13html
用api說明裏面提供的命令行,生成的vue項目,稍微改動。
目錄結構:vue
components/HelloWorld.vuenode
<template> <div class="hello"> <h1>{{ propdata1 }}</h1> <h1>{{ msg }}</h1> <button @click="change">changeMsg</button> <h2>Essential Links</h2> <ul> <li> <a href="https://vuejs.org" target="_blank" > Core Docs </a> </li> <li> <a href="https://forum.vuejs.org" target="_blank" > Forum </a> </li> </ul> </div> </template> <script> export default { name: 'HelloWorld', props:{ propdata1:{default:"propdata1"} }, data () { return { msg: 'data msg' } }, created:function(){ this.msg="created msg"; }, methods:{ change:function(){ this.msg="methods msg"; } }, mounted:function(){ this.msg="mounted msg"; } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
router/index.jsweb
import Vue from 'vue' import Router from 'vue-router' import index from '../index.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'index', component: index } ] })
App.vuevue-router
<template> <div id="app"> <img src="./assets/logo.png"> <router-view/> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
index.vueexpress
<template> <div> <div class="hello"> <h1>{{ msg }}</h1> <button @click="change">changeMsg</button> <h2>index</h2> <ul> <li v-for="item in items"> <a :href="item.url" target="_blank" > {{item.name}} </a> </li> </ul> </div> <helloworld propdata1="propdata1FromIndex"></helloworld> </div> </template> <script> export default { name: 'index', components:{'helloworld':()=>import ('./components/HelloWorld.vue')}, data () { return { msg: 'data msg', items:[{url:"https://forum.vuejs.org",name:"Forum"},{url:"https://vuejs.org",name:"Core Docs"}] } }, created:function(){ this.msg="created msg"; }, methods:{ change:function(){ this.msg="methods msg"; } }, mounted:function(){ this.msg="mounted msg"; } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
main.jsnpm
import Vue from 'vue' import App from './App' import router from './router' import helloworld from './components/HelloWorld.vue' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App, helloworld}, template: '<App/>' })
建議用工具打開,裏面的縮進表明函數的層級關係編程
function Vue$3 (options) { //建立新的vue實例 this._init(options); //初始化 vm.$options = mergeOptions( //混合options resolveConstructorOptions(vm.constructor), //獲取構造函數Vue$3的options對象;key有beforeCreate、components、destroyed、directives、filters、_base options || {}, vm ); checkComponents(child);//驗證options裏面components的命名 validateComponentName(key);//校驗components的命名合法性 normalizeProps(child, vm);//規範props;無值返回;數組[string]返{string:{ type: null }},對象{key:val}返回 val爲string爲{key:{type:val}},val爲obj爲{key:val} normalizeInject(child, vm);//規範inject;無值返回;數組[string]返{string:{ from: string }},對象{key:val}返回 val爲string爲{key:{from:val}},val爲obj爲{key:extend({from:key},val)} normalizeDirectives(child);//規範directives;無值不處理;默認爲對象{key:def},僅處理def類型爲function,返回{key:{bind:def,update:def}} mergeField(key); //parent //components、directives、filters調用mergeAssets; 淺合併child到parent //_base調用defaultStrat; child無值取parent,有值取child //beforeCreate、destroyed調用mergeHook; child無值取parent,child有值 parent有值取parent.concat(child);parent無值 child是數組取child,不是數組取[child] //child父級無此屬性執行 //el調用strats.el; return defaultStrat(parent, child) //router、template調用defaultStrat //components父級有,忽略 initProxy(vm); vm._renderProxy = new Proxy(vm, handlers);//handlers = hasHandler initLifecycle(vm); /** 此時vm={ _uid: 0, _isVue: true, $options: { beforeCreate: [1], destroyed: [1], directives: {}, filters: {}, _base: Vue$3(options), el: '#app', router: VueRouter, template: '<App/>', components:{ App: {}, helloworld: {} } }, _renderProxy: new Proxy(vm, hasHandler), $parent: undefined, $root: vm, $children: [], $refs: {}, _watcher: null, _inactive: null, _directInactive: false, _isMounted: false, _isDestroyed: false, _isBeingDestroyed: false } **/ initEvents(vm); /** vm添加 { _events: {}, _hasHookEvent: false } **/ initRender(vm); /** vm添加 { _vnode: null, _staticTrees: null, $vnode: undefined, $slots: {}, $scopedSlots: {}, _c: function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }, $createElement: function (a, b, c, d) { return createElement(vm, a, b, c, d, true); } } **/ defineReactive(); var dep = new Dep(); /** 此時vm添加 { $attrs: , $listeners: } 監聽屬性變更 **/ callHook(vm, 'beforeCreate'); beforeCreate: function beforeCreate () {}//調用vue-router.js方法 this._router.init(this);//調用vue-router.js this.apps.push(app);//app爲此vue實例 var setupHashListener = function () { history.setupListeners(); }; history.transitionTo( history.getCurrentLocation(),//調return getHash();返回href; setupHashListener, setupHashListener ); Vue.util.defineReactive(this, '_route', this._router.history.current); var dep = new Dep(); registerInstance(this, this); Vue.extend = function (extendOptions) { validateComponentName(name); var Sub = function VueComponent (options) { this._init(options); }; Sub.options = mergeOptions( Super.options, extendOptions ); return Sub; initInjections(vm); // resolve injections before data/props var result = resolveInject(vm.$options.inject, vm); initState(vm); /** vm添加 { _watchers: [], _data: {} } **/ observe(vm._data = {}, true /* asRootData */); ob = new Observer(value); var dep = new Dep(); def(value, '__ob__', this); this.walk(value); initProvide(vm); callHook(vm, 'created'); vm.$mount(vm.$options.el); el = el && query(el);//獲取el元素 var ref = compileToFunctions(template, {}, this) new Function('return 1'); var compiled = compile(template, options); var finalOptions = Object.create(baseOptions); /* baseOptions = { expectHTML: true, modules: [{ staticKeys: ['staticClass'], transformNode: transformNode, genData: genData }, { staticKeys: ['staticStyle'], transformNode: transformNode$1, genData: genData$1 }, { preTransformNode: preTransformNode }], directives: { model: model, text: text, html: html }, isPreTag: isPreTag, isUnaryTag: isUnaryTag, mustUseProp: mustUseProp, canBeLeftOpenTag: canBeLeftOpenTag, isReservedTag: isReservedTag, getTagNamespace: getTagNamespace, staticKeys: genStaticKeys(modules$1) }; */ var compiled = baseCompile(template, finalOptions); var ast = parse(template.trim(), options); transforms = pluckModuleFunction(options.modules, 'transformNode'); //例:pluckModuleFunction(modules,key);遍歷modules,返回鍵爲key的值的數組 //transforms=[transformNode,transformNode$1] preTransforms = pluckModuleFunction(options.modules, 'preTransformNode'); postTransforms = pluckModuleFunction(options.modules, 'postTransformNode'); parseHTML(template, {}) var startTagMatch = parseStartTag(); /*例<App/> startTagMatch = { attrs: [], end: 6, start: 0, tagName: 'App', unarySlash: "/" } */ handleStartTag(startTagMatch); options.start(tagName, attrs, unary, match.start, match.end); var element = createASTElement(tag, attrs, currentParent); /* element = { type: 1, tag: tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), parent: parent, children: [] } */ element = preTransforms[i](element, options) || element;//preTransformNode(el, options);僅處理tag爲input processPre(element);//v-pre processFor(element);//v-for //getAndRemoveAttr(el, name, removeFromMap)刪除el.attrsList數組裏的name,removeFromMap爲真刪除el.attrsMap[name],返回el.attrsMap[name] processIf(element);//v-if v-else-if v-else processOnce(element);//v-once processElement(element, options); processKey(element);//key //getBindingAttr(el, name, getStatic);返回綁定的屬性值;有:name或v-bind:name返回parseFilters(getAndRemoveAttr(el,':'+name||'v-bind:'+name)),沒有動態屬性值查找靜態;getStatic不爲false,有name,返回JSON.stringify(getAndRemoveAttr(el,name)) processRef(element);//ref processSlot(element);//slot||template||slot-scope processComponent(element);//is||inline-template element = transforms[i](element, options) || element;//transformNode(element, options);transformNode$1(element, options);處理class和style processAttrs(element);//處理attrsList裏的屬性值 checkRootConstraints(root);//組件根約束,slot、template、v-for closeElement(element); parseEndTag(); return root; /* root={ attrsList: [], attrsMap: {}, children: [], parent: undefined, plain: true, tag: "App", type: 1 } */ optimize(ast, options); isStaticKey = genStaticKeysCached(options.staticKeys || ''); /* isStaticKey=function (val) { return map[val]; } map={ type: true, tag: true, attrsList: true, attrsMap: true, plain: true, parent: true, children: true, attrs: true, staticClass: true, staticStyle: true } */ markStatic$1(root); node.static = isStatic(node);//false markStaticRoots(root, false); node.staticRoot = false; var code = generate(ast, options); var state = new CodegenState(options); var code = ast ? genElement(ast, state) : '_c("div")'; //code="_c('App')" /* code={ render: "with(this){return _c('App')}", staticRenderFns: [] } */ /* compiled={ ast: { attrsList: [], attrsMap: {}, children: [], parent: undefined, plain: true, static: false, staticRoot: false, tag: "App", type: 1 }, render: "with(this){return _c('App')}", staticRenderFns: [] } */ errors.push.apply(errors, detectErrors(compiled.ast)); checkNode(ast, errors); /* compiled加{ errors: [], tips: [] } */ res.render = createFunction(compiled.render, fnGenErrors); return new Function(code) /* res={ render: function anonymous(){with(this){return _c('App')}} staticRenderFns: [] } */ return mount.call(this, el, hydrating) /*注: var mount = Vue$3.prototype.$mount; //1 Vue$3.prototype.$mount = function(){} //2 初次調用,1被2重寫,調用2; 此時調用,指定mount,調用1; */ el = el && inBrowser ? query(el) : undefined;//獲取el元素 return el; return mountComponent(this, el, hydrating) callHook(vm, 'beforeMount'); updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); /* 此時Watcher Watcher={ active: true, cb: ƒ noop(a, b, c), deep: false, depIds: Set(0) {}, deps: [], dirty: false, expression: "function () {↵ vm._update(vm._render(), hydrating);↵ }", getter: ƒ (), id: 1, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: false, vm : vue實例 } */ this.get(); pushTarget(this);//將Watcher實例賦值給Dep._target; value = this.getter.call(vm, vm); vm._update(vm._render(), hydrating); Vue.prototype._render vnode = render.call(vm._renderProxy, vm.$createElement); vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; return _createElement(context, tag, data, children, normalizationType)//(vue實例, 'App', undefined, undefined, undefined) vnode = createComponent(Ctor, data, context, children, tag);//(組件App, undefined, vue實例, undefined, 'App' ) Ctor = baseCtor.extend(Ctor); validateComponentName(name); var Sub = function VueComponent (options) { Sub.options = mergeOptions(Super.options,extendOptions); checkComponents(child); normalizeProps(child, vm); name = camelize(key); normalizeInject(child, vm); normalizeDirectives(child); mergeField(key); //parent //components、directives、filters調用mergeAssets; 淺合併child到parent,child無值取{} //_base調用defaultStrat; child無值取parent,有值取child //beforeCreate、destroyed調用mergeHook; child無值取parent,child有值 parent有值取parent.concat(child);parent無值 child是數組取child,不是數組取[child] //child父級無此屬性執行 //beforeDestroy調用mergeHook; //name、render、staticRenderFns、_compiled、_file、_Ctor調用defaultStrat //beforeCreate父級有,忽略 ASSET_TYPES.forEach(function (type) {}) return Sub; //Ctor=Sub; resolveConstructorOptions (Ctor) var superOptions = resolveConstructorOptions(Ctor.super); //遞歸獲取最初的options //返回混合後的options var propsData = extractPropsFromVNodeData(data, Ctor, tag); mergeHooks(data);//data={on:undefined}; var vnode = new VNode()//('vue-component-4-App',data1, undefined, undefined, undefined, vue實例,組件options, undefined,) /* data1={ hook: { destroy: ƒ destroy(vnode), init: ƒ init( vnode, hydrating, parentElm, refElm ), insert: ƒ insert(vnode), prepatch: ƒ prepatch(oldVnode, vnode) }, on: undefined } 組件options={ Ctor: ƒ VueComponent(options) children: undefined listeners: undefined propsData: undefined tag: "App" } */ return vnode; /* vnode={ asyncFactory: undefined asyncMeta: undefined children: undefined componentInstance: undefined componentOptions: {Ctor: ƒ, propsData: undefined, listeners: undefined, tag: "App", children: undefined} context: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …} data: {on: undefined, hook: {…}} elm: undefined fnContext: undefined fnOptions: undefined fnScopeId: undefined isAsyncPlaceholder: false isCloned: false isComment: false isOnce: false isRootInsert: true isStatic: false key: undefined ns: undefined parent: undefined raw: false tag: "vue-component-4-App" text: undefined } */ return vnode //vnode //vnode return vnode; //參數vm._render()爲vnode = new VNode(); vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false, vm.$options._parentElm, vm.$options._refElm) oldVnode = emptyNodeAt(oldVnode); return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm);//parentElm$1=body; createElm(vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm$1, nodeOps.nextSibling(oldElm) ); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} i(vnode, false /* hydrating */, parentElm, refElm);//i=componentVNodeHooks.init var child = vnode.componentInstance = createComponentInstanceForVnode()//(vnode,vue實例,body,下一節點) return new vnode.componentOptions.Ctor(options) /* 調function VueComponent (options); options={ parent: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …} _isComponent: true _parentElm: body _parentVnode: VNode {tag: "vue-component-4-App", data: {…}, children: undefined, text: undefined, elm: undefined, …} _refElm: text } */ this._init(options); /* 從新調用_init方法; var Sub = function VueComponent (options) { this._init(options); }; */ initInternalComponent(vm, options); initProxy(vm); vm._renderProxy = new Proxy(vm, handlers);//handlers爲getHandler initLifecycle(vm); initEvents(vm); initRender(vm); defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () { !isUpdatingChildComponent && warn("$attrs is readonly.", vm); }, true); defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () { !isUpdatingChildComponent && warn("$listeners is readonly.", vm); }, true); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); /* child={ $attrs: (...), $children: [], $createElement: ƒ (a, b, c, d), $listeners: (...), $options: {parent: Vue$3, _parentVnode: VNode, _parentElm: body, _refElm: text, propsData: undefined, …}, $parent: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, $refs: {}, $root: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, $scopedSlots: {}, $slots: {}, $vnode: VNode {tag: "vue-component-4-App", data: {…}, children: undefined, text: undefined, elm: undefined, …}, _c: ƒ (a, b, c, d), _data: {__ob__: Observer}, _directInactive: false, _events: {}, _hasHookEvent: false, _inactive: null, _isBeingDestroyed: false, _isDestroyed: false, _isMounted: false, _isVue: true, _renderProxy: Proxy {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}, _routerRoot: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, _self: VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}, _staticTrees: null, _uid: 1, _vnode: null, _watcher: null, _watchers: [] } */ child.$mount(hydrating ? vnode.elm : undefined, hydrating); return mount.call(this, el, hydrating) return mountComponent(this, el, hydrating) callHook(vm, 'beforeMount'); updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); this.get(); pushTarget(this); value = this.getter.call(vm, vm); vm._update(vm._render(), hydrating); vnode = render.call(vm._renderProxy, vm.$createElement); /* App.vue; var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c( "div", { attrs: { id: "app" } }, [ _c("img", { attrs: { src: require("./assets/logo.png") } }), _vm._v(" "), _c("router-view") ], 1 ) } */ initComponent(vnode, insertedVnodeQueue); insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); if (isPatchable(vnode)) {} invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode);//updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、_enter_、create、updateDirectives //_enter_ enter(vnode); //create registerRef(vnode); setScope(vnode); nodeOps.createElement(tag, vnode); setScope(vnode); createChildren(vnode, children, insertedVnodeQueue); checkDuplicateKeys(children); //3個createElm createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} nodeOps.createElement(tag, vnode); setScope(vnode); createChildren(vnode, children, insertedVnodeQueue); invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode); insert(parentElm, vnode.elm, refElm); nodeOps.appendChild(parent, elm); createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); nodeOps.appendChild(parent, elm); createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} i(vnode, false /* hydrating */, parentElm, refElm); initComponent(vnode, insertedVnodeQueue); insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); if (isPatchable(vnode)) {} invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode);//updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、_enter_、create、updateDirectives //_enter_ enter(vnode); //create registerRef(vnode); setScope(vnode); invokeCreateHooks(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); nodeOps.insertBefore(parent, elm, ref$$1);//頁面展現出來,created data; invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm popTarget(); Dep.target = targetStack.pop(); this.cleanupDeps(); dep.removeSub(this$1); remove(this.subs, sub); this.newDepIds.clear(); return value;//value=undefined; return vm;//$el爲div#app這個VueComponent;
這邊有一篇文章 剖析Vue原理&實現雙向綁定MVVM 講的很細,就再也不重複寫
以上面文章的內容爲基礎補充一張 vue數據雙向綁定原理圖segmentfault