说明
vue源码1万多行,完全解析透太耗时间;里面细节处理很多,通读代码,语法都不难;个人认为重点在于理解它的思想,掌握面向数据编程的原理。
通过一个合适的例子,断点调试来查看代码运行流程,可以快速了解编码的思路。
[email protected]
一、案例代码及运行流程
用api说明里面提供的命令行,生成的vue项目,稍微改动。
目录结构:
components/HelloWorld.vue
router/index.js
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.vue
index.vue
main.js
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: ' '
})
断点运行流程
建议用工具打开,里面的缩进代表函数的层级关系
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: ' ',
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();
/*例
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数据双向绑定原理图