Vue的全局API: directive、filter和component註冊以下:node
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (
id,
definition
) {
if (!definition) {// 過濾器註冊
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (type === 'component') {
validateComponentName(id);
}
// 若是是全局註冊組件,經過Vue.extend生成子組件構造函數
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
}
// 註冊或獲取全局指令,此時並未失效,若是definition是函數則默認監聽bind和update事件
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition };
}
this.options[type + 's'][id] = definition;
return definition
}
};
});
}
複製代碼
以模板爲例:express
<div id="app"><input type="text" class="form-control" v-model="keywords" v-focus></div>
複製代碼
在模板編譯階段,處理屬性的過程當中,會把v-model和v-focus這種解析成:bash
[
{arg: null, end: 72, isDynamicArg: false, modifiers: undefined, name: "model",rawName: "v-model",start: 54, value: "keywords"},
{arg: null,end: 83,isDynamicArg: false,modifiers: undefined,name: "focus",rawName: "v-focus",start: 73, value: ""}
]
複製代碼
存在el.directives屬性中,生成的渲染函數以下:app
with(this){return _c('div',{
attrs:{"id":"app"}
},[
_c('input',{
directives:
[
{
name:"model",
rawName:"v-model",
value:(keywords),
expression:"keywords"
},
{
name:"focus",
rawName:"v-focus"
}
],
staticClass:"form-control",
attrs:{"type":"text"},
domProps:{"value":(keywords)},
on:{"input":function($event){
if($event.target.composing)
return;
keywords=$event.target.value
}
}
})
])}
複製代碼
input生成的Vnode以下:dom
{
asyncFactory: undefined,
asyncMeta: undefined,
children: undefined,
componentInstance: undefined,
componentOptions: undefined,
context: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …},
data: {directives: Array(2), staticClass: "form-control", attrs: {type: "text"}, domProps: {value: undefined}, on: {input: f}},
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: "input",
text: undefined,
child: undefined
}
複製代碼
在渲染階段,patchVnode過程當中,會執行async
for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
複製代碼
更新屬性的函數爲函數
[
ƒ updateAttrs(oldVnode, vnode),
ƒ updateClass(oldVnode, vnode),
ƒ updateDOMListeners(oldVnode, vnode),
ƒ updateDOMProps(oldVnode, vnode),
ƒ updateStyle(oldVnode, vnode),
ƒ update(oldVnode, vnode),
ƒ updateDirectives(oldVnode, vnode)
]
複製代碼
其中指令相關的處理邏輯post
function updateDirectives (oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
複製代碼
_update函數爲:ui
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
// 新的指令觸發bind方法
callHook$1(dir, 'bind', vnode, oldVnode);
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
// 指令存在,觸發update
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
callHook$1(dir, 'update', vnode, oldVnode);
// 判斷是否設置componentUpdated方法,有則將其添加到列表中,確保組件的VNode和其子VNode所有更新後再調用指令的componentupdated方法
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
// 指令添加到dirsWithInsert,保證全部的bind執行完畢再執行insert函數
if (dirsWithInsert.length) {
var callInsert = function () {
for (var i = 0; i < dirsWithInsert.length; i++) {
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
// 判斷虛擬節點是不是新建立節點
if (isCreate) {
// 能夠將一個鉤子函數與虛擬節點現有的鉤子函數合併在一塊兒,當虛擬節點觸發鉤子函數時,新增的鉤子也會觸發,這裏應該等到元素被插入到父節點後再執行指令的insert
mergeVNodeHook(vnode, 'insert', callInsert);
} else {
// 不須要延遲到綁定元素插入到父節點後進行,直接執行callInsert
callInsert();
}
}
if (dirsWithPostpatch.length) {
// 觸發update鉤子後,再觸發postpatch
mergeVNodeHook(vnode, 'postpatch', function () {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
// componentUpdated指令推遲到vnode和oldVnode更新後
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
if (!isCreate) {
// 新建立的虛擬節點不須要解綁,若是newDirs中不存在,則觸發unbind方法
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}
複製代碼
這裏通過normalizeDirectives函數處理後變成:this
[
{
v-focus: {
def: {bind: ƒ, inserted: ƒ, updated: ƒ},
modifiers: {},
name: "focus",
oldArg: undefined,
oldValue: undefined,
rawName: "v-focus"
}
},
v-model:{
def: {inserted: ƒ, componentUpdated: ƒ},
expression: "keywords",
modifiers: {},
name: "model",
oldArg: undefined,
oldValue: "wewe",
rawName: "v-model",
value: "wewew"
}
]
複製代碼
callHook如何執行的鉤子函數以下:
function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) {
var fn = dir.def && dir.def[hook];
if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy);
} catch (e) {
handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
}
}
}
複製代碼
更新完directive後繼續進行diff,最後掛載到頁面。