指令是Vue.js模板中最經常使用的一項功能,它帶有前綴v-,好比上面說的v-if、v-html、v-pre等。指令的主要職責就是當其表達式的值改變時,相應的將某些行爲應用到DOM上,先介紹v-bind指令html
v-bind用於動態地綁定一個或多個特性,或一個組件 prop 到表達式。vue
例如:node
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <title>Document</title> </head> <body> <div id="app"><a v-bind:href="href">連接</a></div> <script> Vue.config.productionTip=false; Vue.config.devtools=false; var app = new Vue({ el:'#app', data:{href:"http://www.baidu.com"} }) </script> </body> </html>
渲染爲:npm
當咱們點擊整個超連接時將跳轉到http://www.baidu.com,若是在控制檯輸入app.href="http://www.taobao.com"時:app
點擊按鈕後就跳轉到淘寶了函數
源碼分析源碼分析
以上面的例子爲例,Vue內部將DOM解析成AST對象的時候會執行parse()函數,該函數解析到a節點時會執行到processElement()函數,該函數先將key、ref、插槽、class和style解析完後就會執行processAttrs()函數,以下:this
function processAttrs (el) { //第9526行 對剩餘的屬性進行分析 var list = el.attrsList; var i, l, name, rawName, value, modifiers, isProp; for (i = 0, l = list.length; i < l; i++) { //遍歷每一個屬性 name = rawName = list[i].name; value = list[i].value; if (dirRE.test(name)) { //若是該屬性以v-、@或:開頭,表示這是Vue內部指令 // mark element as dynamic el.hasBindings = true; // modifiers modifiers = parseModifiers(name); //獲取修飾符,好比:{native: true,prevent: true} if (modifiers) { name = name.replace(modifierRE, ''); } if (bindRE.test(name)) { // v-bind //bindRD等於/^:|^v-bind:/ ,即該屬性是v-bind指令時 例如:<a :href="url">你好</a> name = name.replace(bindRE, ''); //去掉指令特性,獲取特性名,好比 href value = parseFilters(value); //對一些表達式作解析,例如{a|func1|func2} isProp = false; //是否綁定到DOM對象上 if (modifiers) { if (modifiers.prop) { //若是有修飾符 isProp = true; name = camelize(name); if (name === 'innerHtml') { name = 'innerHTML'; } } if (modifiers.camel) { name = camelize(name); } if (modifiers.sync) { addHandler( el, ("update:" + (camelize(name))), genAssignmentCode(value, "$event") ); } } if (isProp || ( !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) //若是isProp爲true )) { //則調用addProp() addProp(el, name, value); } else { addAttr(el, name, value); //不然調用addAttr() } } else if (onRE.test(name)) { // v-on //onRE等於/^@|^v-on:/,即該屬性是v-on指令時 name = name.replace(onRE, ''); addHandler(el, name, value, modifiers, false, warn$2); } else { // normal directives //普通指令 name = name.replace(dirRE, ''); // parse arg var argMatch = name.match(argRE); var arg = argMatch && argMatch[1]; if (arg) { name = name.slice(0, -(arg.length + 1)); } addDirective(el, name, rawName, value, arg, modifiers); if ("development" !== 'production' && name === 'model') { checkForAliasModel(el, value); } } } else { /*略*/ } } }
addAttr()函數用於在AST對象上新增一個attrs屬性,以下:url
function addAttr (el, name, value) { //第6550行 (el.attrs || (el.attrs = [])).push({ name: name, value: value }); //將{name: name,value: value}保存到el.attrs裏面 el.plain = false; //修正el.plain爲false }
例子裏執行到這裏時對應的AST對象爲:spa
執行generate()函數獲取data$2時會判斷是否有attrs屬性,若是有則將屬性保存到attrs上,例子裏的實例渲染後render函數等於:
if (el.attrs) { //第10306行 data += "attrs:{" + (genProps(el.attrs)) + "},"; }
genProps用於拼湊對應的值,以下:
function genProps (props) { //第10537行 拼湊AST對象的屬性或DOM屬性用的 var res = ''; for (var i = 0; i < props.length; i++) { //遍歷prps var prop = props[i]; //對應的值 /* istanbul ignore if */ { res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ","; //拼湊字符串 } } return res.slice(0, -1) }
例子執行到這裏渲染的render函數等於:
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
},
[_c('a', {
attrs: {
"href": href
}
},
[_v("連接")])])
}
這樣當該函數執行的時候就會觸發Vue實例的href屬性,此時就會將渲染watcher做爲href屬性的訂閱者了,當href修改時就會觸發渲染watcher的從新渲染了。
最後當a標籤整個DOM元素生成以後會觸發attrs模塊的create事件去設置href特性,以下:
function updateAttrs (oldVnode, vnode) { //第6294行 更新attrs var opts = vnode.componentOptions; //獲取vnode.componentOptions(組件纔有) if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { return } if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { //若是在oldVnode和vnode上都沒有定義attrs屬性 return //則直接返回,不作處理 } var key, cur, old; var elm = vnode.elm; var oldAttrs = oldVnode.data.attrs || {}; var attrs = vnode.data.attrs || {}; //新VNode的attrs屬性 // clone observed objects, as the user probably wants to mutate it if (isDef(attrs.__ob__)) { attrs = vnode.data.attrs = extend({}, attrs); } for (key in attrs) { //遍歷新VNode的每一個attrs cur = attrs[key]; old = oldAttrs[key]; if (old !== cur) { setAttr(elm, key, cur); //則調用setAttr設置屬性 } } // #4391: in IE9, setting type can reset value for input[type=radio] // #6666: IE/Edge forces progress value down to 1 before setting a max /* istanbul ignore if */ if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { //IE9的特殊狀況 setAttr(elm, 'value', attrs.value); } for (key in oldAttrs) { if (isUndef(attrs[key])) { if (isXlink(key)) { elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); } else if (!isEnumeratedAttr(key)) { elm.removeAttribute(key); } } } } function setAttr (el, key, value) { //設置el元素的key屬性爲value if (el.tagName.indexOf('-') > -1) { //若是el的標籤名裏含有- baseSetAttr(el, key, value); } else if (isBooleanAttr(key)) { //若是key是布爾類型的變量(好比:disabled、selected) // set attribute for blank value // e.g. <option disabled>Select one</option> if (isFalsyAttrValue(value)) { el.removeAttribute(key); } else { // technically allowfullscreen is a boolean attribute for <iframe>, // but Flash expects a value of "true" when used on <embed> tag value = key === 'allowfullscreen' && el.tagName === 'EMBED' ? 'true' : key; el.setAttribute(key, value); } } else if (isEnumeratedAttr(key)) { //若是key是這三個之一:contenteditable,draggable,spellcheck el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'); } else if (isXlink(key)) { if (isFalsyAttrValue(value)) { el.removeAttributeNS(xlinkNS, getXlinkProp(key)); } else { el.setAttributeNS(xlinkNS, key, value); } } else { //不知足上述的狀況就直接調用baseSetAttr設置屬性 baseSetAttr(el, key, value); } } function baseSetAttr (el, key, value) { //設置el的key屬性爲value if (isFalsyAttrValue(value)) { //若是value是null或false el.removeAttribute(key); //則刪除屬性 } else { // #7138: IE10 & 11 fires input event when setting placeholder on // <textarea>... block the first input event and remove the blocker // immediately. /* istanbul ignore if */ if ( isIE && !isIE9 && el.tagName === 'TEXTAREA' && key === 'placeholder' && !el.__ieph ) { 特殊狀況 var blocker = function (e) { e.stopImmediatePropagation(); el.removeEventListener('input', blocker); }; el.addEventListener('input', blocker); // $flow-disable-line el.__ieph = true; /* IE placeholder patched */ } el.setAttribute(key, value); //直接調用原生DOMAPI setAttribute設置屬性 } }