指令是Vue.js模板中最常用的一项功能,它带有前缀v-,比如上面说的v-if、v-html、v-pre等。指令的主要职责就是当其表达式的值改变时,相应的将某些行为应用到DOM上,先介绍v-bind指令

v-bind用于动态地绑定一个或多个特性,或一个组件 prop 到表达式。

例如:

<!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>

渲染为:

当我们点击整个超链接时将跳转到http://www.baidu.com,如果在控制台输入app.href="http://www.taobao.com"时:

点击按钮后就跳转到淘宝了

源码分析


以上面的例子为例,Vue内部将DOM解析成AST对象的时候会执行parse()函数,该函数解析到a节点时会执行到processElement()函数,该函数先将key、ref、插槽、class和style解析完后就会执行processAttrs()函数,如下:

writer by:大沙漠 QQ:22969969

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属性,如下:

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对象为:

执行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设置属性
}
}

最新文章

  1. [翻译svg教程]Path元素 svg中最神奇的元素!
  2. Yii中的错误及异常处理
  3. iOS coredata 级联删除
  4. (41) Aeroo 模板设计基础教程
  5. codeforces194a
  6. MySQL 批量插入 Update时Replace
  7. windows初始化后做了哪些事情
  8. wget 批量下载目录文件
  9. PHP中0、空、null和false的总结
  10. image控件读取数据库二进制图片
  11. python json.load 的奇葩错误
  12. 八张图学通JavaScript 转自52
  13. Docker for Web Developers目录
  14. linux下内存的统计和内存泄露类问题的定位
  15. Swift中不用桥接文件和.h头文件直接和C代码交互的方法
  16. Linux3.10.0块IO子系统流程(5)-- 为SCSI命令准备聚散列表
  17. BZOJ4001[TJOI2015]概率论——卡特兰数
  18. 关于hot miami的沙盒生存俯视角射击游戏
  19. Oracle EBS 应收API只创建收款没有核销行以及消息堆栈
  20. .Net程序集强签名详解

热门文章

  1. 一个Web前端工程师或程序员的发展方向,未来困境及穷途末路
  2. vue中template的作用及使用
  3. GCC预编译宏查看
  4. 人生苦短?试试Groovy进行单元测试
  5. linux如何修改权限详解
  6. 使用 FiddlerCore 自定义 HTTP/HTTPS 网络代理
  7. css精灵图&amp;字体图标
  8. 利用Fiddler模拟通过Dynamics 365的OAuth 2 Client Credentials认证后调用Web API
  9. linux系统管理-输入输出
  10. python 小数据池 深浅拷贝 集合