前言
本文是vue2.x源码分析的第八篇,主要看v-*指令的处理过程!
实例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue</title>
<script src="./vue.js" type="text/javascript" charset="utf-8" ></script>
</head>
<body>
<div id="app">
<!-- <div>{{pre}}</div> -->
<!-- <div v-text='text'></div> -->
<!-- <div v-html='html'></div> -->
<!-- <div v-pre>{{pre}}</div> -->
<!-- <div v-for='item in items' >{{item}}</div> -->
<!-- <div v-cloak>{{cloak}}</div> -->
<!-- <div v-once>{{once}}</div> -->
<!-- <div v-show='show'>v-show</div> -->
<!-- <div v-on:click='click'>click</div> -->
<!-- <div v-bind:me='message'></div> -->
<div v-model='model'></div>
</div>
<script>
var vm=new Vue({
el:'#app',
name:'app',
data:{
items:[1,2,3],
pre:'v-pre',
once:'v-once-1',
show:true,
text:'v-text测试',
html:'<span>v-html测试</span>',
cloak:"v-cloak",
message:'message',
model:'model'
},
methods:{
click:function(){
console.log('click')
}
}
});
</script>
</body>
</html>
根据AST的不同,将v-*指令系列分为三部分
1 插值
基本的插值AST有9个基本属性
2 v-if,v-on,v-pre,v-once,v-for,v-bind
这6个指令会在9个基本属性上添加自己的属性(有的会去掉部分基本属性)
3 v-show,v-model,v-text,v-html,v-cloak
这5个指令都会加上directives,hasBindings两个属性
此外,自定义的指令如v-focus等的处理过程与这5个指令类似
4 invokeCreateHooks(vnode, insertedVnodeQueue)
在上一节中提到createElm函数的四步中,第三步其实是很重要的
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) { i.create(emptyNode, vnode); }
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
这里的cbs如下:
activate:Array(1)
create:Array(8)
destroy:Array(2)
remove:Array(1)
update:Array(7)
__proto__:Object
其中的create是含有8个函数的数组,这8个函数依次是
//1、处理data对象中的attrs,
function updateAttrs(oldVnode, vnode) {
if (!oldVnode.data.attrs && !vnode.data.attrs) {
return
}
var key, cur, old;
var elm = vnode.elm;
var oldAttrs = oldVnode.data.attrs || {};
var attrs = vnode.data.attrs || {};
// clone observed objects, as the user probably wants to mutate it
if (attrs.__ob__) {
attrs = vnode.data.attrs = extend({}, attrs);
}
for (key in attrs) {
cur = attrs[key];
old = oldAttrs[key];
if (old !== cur) {
setAttr(elm, key, cur);
}
}
// #4391: in IE9, setting type can reset value for input[type=radio]
/* istanbul ignore if */
if (isIE9 && attrs.value !== oldAttrs.value) {
setAttr(elm, 'value', attrs.value);
}
for (key in oldAttrs) {
if (attrs[key] == null) {
if (isXlink(key)) {
elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
} else if (!isEnumeratedAttr(key)) {
elm.removeAttribute(key);
}
}
}
}
//2 处理data对象中的class和staticClass
function updateClass(oldVnode, vnode) {
var el = vnode.elm;
var data = vnode.data;
var oldData = oldVnode.data;
if (!data.staticClass && !data.class && (!oldData || (!oldData.staticClass && !oldData.class))) {
return
}
var cls = genClassForVnode(vnode);
// handle transition classes
var transitionClass = el._transitionClasses;
if (transitionClass) {
cls = concat(cls, stringifyClass(transitionClass));
}
// set the class
if (cls !== el._prevClass) {
el.setAttribute('class', cls);
el._prevClass = cls;
}
}
//3 处理data对象中的on,v-on就是在这儿处理的
function updateDOMListeners(oldVnode, vnode) {
if (!oldVnode.data.on && !vnode.data.on) {
return
}
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, vnode.context);
}
//4 处理data对象中的domProps,v-on就是在这儿处理的,v-text和v-html就是在这儿处理
function updateDOMProps(oldVnode, vnode) {
if (!oldVnode.data.domProps && !vnode.data.domProps) {
return
}
var key, cur;
var elm = vnode.elm;
var oldProps = oldVnode.data.domProps || {};
var props = vnode.data.domProps || {};
// clone observed objects, as the user probably wants to mutate it
if (props.__ob__) {
props = vnode.data.domProps = extend({}, props);
}
for (key in oldProps) {
if (props[key] == null) {
elm[key] = '';
}
}
for (key in props) {
cur = props[key];
// ignore children if the node has textContent or innerHTML,
// as these will throw away existing DOM nodes and cause removal errors
// on subsequent patches (#3360)
if (key === 'textContent' || key === 'innerHTML') {
if (vnode.children) {
vnode.children.length = 0;
}
if (cur === oldProps[key]) {
continue
}
}
if (key === 'value') {
// store value as _value as well since
// non-string values will be stringified
elm._value = cur;
// avoid resetting cursor position when value is the same
var strCur = cur == null ? '' : String(cur);
if (shouldUpdateValue(elm, vnode, strCur)) {
elm.value = strCur;
}
} else {
elm[key] = cur;
}
}
}
//5 处理data对象中的style和staticStyle
function updateStyle(oldVnode, vnode) {
var data = vnode.data;
var oldData = oldVnode.data;
if (!data.staticStyle && !data.style && !oldData.staticStyle && !oldData.style) {
return
}
var cur, name;
var el = vnode.elm;
var oldStaticStyle = oldVnode.data.staticStyle;
var oldStyleBinding = oldVnode.data.style || {};
// if static style exists, stylebinding already merged into it when doing normalizeStyleData
var oldStyle = oldStaticStyle || oldStyleBinding;
var style = normalizeStyleBinding(vnode.data.style) || {};
vnode.data.style = style.__ob__ ? extend({}, style) : style;
var newStyle = getStyle(vnode, true);
for (name in oldStyle) {
if (newStyle[name] == null) {
setProp(el, name, '');
}
}
for (name in newStyle) {
cur = newStyle[name];
if (cur !== oldStyle[name]) {
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur);
}
}
}
//6 处理data对象中的show,与transition有关
function _enter(_, vnode) {
if (!vnode.data.show) {
enter(vnode);
}
}
//7 处理data对象中的ref
create: function create(_, vnode) {
registerRef(vnode);
}
//其中registerRef函数如下:
function registerRef(vnode, isRemoval) {
var key = vnode.data.ref;
if (!key) {
return
}
var vm = vnode.context;
var ref = vnode.componentInstance || vnode.elm;
var refs = vm.$refs;
if (isRemoval) {
if (Array.isArray(refs[key])) {
remove(refs[key], ref);
} else if (refs[key] === ref) {
refs[key] = undefined;
}
} else {
if (vnode.data.refInFor) {
if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {
refs[key].push(ref);
} else {
refs[key] = [ref];
}
} else {
refs[key] = ref;
}
}
}
//8 处理data对象中的directives
function updateDirectives(oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
//其中_updata函数如下:
function _update(oldVnode, vnode) {
var isCreate = oldVnode === emptyNode;
var isDestroy = vnode === emptyNode;
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
var dirsWithInsert = [];
var dirsWithPostpatch = [];
var key, oldDir, dir;
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
if (!oldDir) {
// new directive, bind
callHook$1(dir, 'bind', vnode, oldVnode);
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
dir.oldValue = oldDir.value;
callHook$1(dir, 'update', vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
if (dirsWithInsert.length) {
var callInsert = function() {
for (var i = 0; i < dirsWithInsert.length; i++) {
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
if (isCreate) {
mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert);
} else {
callInsert();
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function() {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}
patch在createElm调用完后,还调用了invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);这个就是执行指令中的inserted函数
对于内置的指令v-show和v-model,vue已经为其准备好了bind等函数,若要自定义指令,则需自己定义bind,inserted,update,updateComponent,unbind函数
如:
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。
inserted: function (el) {
el.focus()
}
})
//若简写成这样
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', function (el) {
el.focus()
})
则vue会自动将该函数视为bind和update函数,此时不会取得focus效果,因为el.focus()执行时机过早,尚未插入父元素,不在文档流中