Vue.js 源碼分析(十五) 指令篇 v-bind指令詳解

指令是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設置屬性
  }
}
相關文章
相關標籤/搜索