Vue.js 源码分析(十五) 指令篇 v-bind指令详解
程序员文章站
2022-08-09 20:17:17
指令是Vue.js模板中最常用的一项功能,它带有前缀v-,比如上面说的v-if、v-html、v-pre等。指令的主要职责就是当其表达式的值改变时,相应的将某些行为应用到DOM上,先介绍v-bind指令 v-bind用于动态地绑定一个或多个特性,或一个组件 prop 到表达式。 例如: 渲染为: 当 ......
指令是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()函数,如下:
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设置属性 } }
推荐阅读
-
Vue.js 源码分析(二十二) 指令篇 v-model指令详解
-
Vue.js 源码分析(二十五) 高级应用 插槽 详解
-
Vue.js 源码分析(十六) 指令篇 v-on指令详解
-
Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解
-
Vue.js 源码分析(十二) 基础篇 组件详解
-
Vue.js 源码分析(二十四) 高级应用 自定义指令详解
-
Vue.js 源码分析(十五) 指令篇 v-bind指令详解
-
Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
-
Vue.js 源码分析(十八) 指令篇 v-for 指令详解
-
Vue.js 源码分析(十三) 基础篇 组件 props属性详解