Vue.js 源码分析(十五) 指令篇 v-bind指令详解
指令是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设置属性
}
}
Vue.js 源码分析(十五) 指令篇 v-bind指令详解的更多相关文章
- Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解
我们在开发组件时有时需要和父组件沟通,此时可以用自定义事件来实现 组件的事件分为自定义事件和原生事件,前者用于子组件给父组件发送消息的,后者用于在组件的根元素上直接监听一个原生事件,区别就是绑定原生事 ...
- Vue.js 源码分析(十二) 基础篇 组件详解
组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...
- Vue.js 源码分析(十八) 指令篇 v-for 指令详解
我们可以用 v-for 指令基于一个数组or对象来渲染一个列表,有五种使用方法,如下: <!DOCTYPE html> <html lang="en"> & ...
- Vue.js 源码分析(十九) 指令篇 v-html和v-text指令详解
双大括号会将数据解释为普通文本,而非 HTML 代码.为了输出真正的 HTML,你需要使用 v-html 指令,例如: <!DOCTYPE html> <html lang=&quo ...
- Vue.js 源码分析(十六) 指令篇 v-on指令详解
可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码,例如: <!DOCTYPE html> <html lang="en"& ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
- jQuery 源码分析(十二) 数据操作模块 html特性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
- Vue.js 源码分析(一) 代码结构
关于Vue vue是一个兴起的前端js库,是一个精简的MVVM.MVVM模式是由经典的软件架构MVC衍生来的,当View(视图层)变化时,会自动更新到ViewModel(视图模型),反之亦然,View ...
随机推荐
- jsp模板
<%String path = request.getContextPath();String basePath = request.getScheme()+"://"+re ...
- JQuery学习笔记(4)——ajax
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML) 原生 例子 点击按钮,访问服务器上的ajax_info.txt文件,获得txt ...
- C# 同步转异步 AutoResetEvent
当我们的程序运行时,调用了一段异步的逻辑A,这段异步的逻辑无法转化为同步(如动画.下载进度等) 而,我们又需要等待异步逻辑A处理完成,然后再执行其它逻辑B. AutoResetEvent 同步转异步 ...
- python读取Excel的值
上代码: import pandas as pd if __name__ == '__main__': #默认的读取第一个sheet df = pd.read_excel("E:\\MyPr ...
- Javase之多线程(2)
多线程(2) 线程的生命周期 新建:创建线程对象 就绪:有执行资格,没有执行权 运行:有资格运行,有执行权 阻塞:由一些操作让线程处于改状态.没有执行资格,没有执行权,而通过另一些操作激活它,激活 ...
- arcgis api for javascript 学习(五) 实现地图绘制工具
1.本文实现的功能为鼠标与地图之间的交互,能够在地图上绘制不同形状的图形 2.代码部分主要讲到的为Graphic函数的相关功能 <!DOCTYPE html> <html> & ...
- [20191010]bash行计算器.txt
[20191010]bash行计算器.txt --//写一个bash行计算器,为了避免冲突,函数命名为2个=,1个=感觉不是很好(心里上^_^).--//使用bc计算器,里面函数不能使用圆括号,使用中 ...
- 错误:shell 打开出现一大堆 错误 declare -x 之类的消息
像图中这种情况:这是什么情况呢? 原因:可能是你最近修改了.bashrc 或者 bash_profile 之类的文件.其中export 命令,要求export 命令写在单独的一行上: 就像下面这样,如 ...
- [Go] gocron源码阅读-flag包实现命令行参数获取
调用flag包可以方便的获取到命令行中传递的参数,比如可以实现类似nginx执行程序获取命令行参数执行不同操作的目标 package main import ( "flag" &q ...
- python简单面试题
在这个即将进入金9银10的跳槽季节的时候,肯定需要一波面试题了,安静总结了一些经常遇到的python面试题,让我们一起撸起来. python面试题 1.求出1-100之间的和 # coidng:utf ...