// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr (
el: ASTElement,
name: string,
removeFromMap?: boolean
): ?string {
let val
if ((val = el.attrsMap[name]) != null) {
const list = el.attrsList
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1) // 从attrsList删除一个属性,不会从attrsMap删除
break
}
}
}
if (removeFromMap) {
delete el.attrsMap[name]
}
return val
}
如何获取v-bind的值
以下面代码为例从源码分析vue是如何获取v-bind的值。
会从记下几个场景去分析:
常见的key属性
绑定一个普通html attribute:title
绑定class和style
绑定一个html DOM property:textContent
vBind:{
key: +new Date(),
title: "This is a HTML attribute v-bind",
class: "{ borderRadius: isBorderRadius }"
style: "{ minHeight: 100 + 'px' , maxHeight}"
text-content: "hello vue v-bind"
}
如果你写过vue,对v-bind这个指令一定不陌生。 下面我将从源码层面去带大家剖析一下v-bind背后的原理。
会从以下几个方面去探索:
v-bind关键源码分析
v-bind化的属性统一存储在哪里:attrsMap与attrsList
假设为p标签v-bind化了title属性,我们来分析title属性在vue中是如何被处理的。
vue在拿到这个html标签之后,处理title属性,会做以下几步:
createASTElement(... ,attrs, ...)
至于创建之后是如何处理v-bind:title这种普通的属性值的,可以在下文的v-bind:src源码分析中一探究竟。
解析HTML,解析出属性集合attrs,在start回调中返回
在start回调中创建ASTElement,
createASTElement(... ,attrs, ...)
创建后ASTElement会生成attrsList和attrsMap
attrs的数据类型定义
绑定属性获取函数 getBindingAttr 和 属性操作函数 getAndRemoveAttr
getBindingAttr及其子函数getAndRemoveAttr在处理特定场景下的v-bind十分有用,也就是”v-bind如何处理不同的绑定属性“章节很有用。 这里将其列举出来供下文
v-bind:key源码分析;v-bind:src源码分析;v-bind:class源码分析;v-bind:style源码分析;v-bind:dataset.prop源码分析
源码分析参照。如何获取v-bind的值
以下面代码为例从源码分析vue是如何获取v-bind的值。
会从记下几个场景去分析:
v-bind:key源码分析
processKey函数中用到了getBindingAttr函数,由于我们用的是v-bind,没有用
:
,所以const dynamicValue = getAndRemoveAttr(el, 'v-bind:'+'key');
,getAndRemoveAttr(el, 'v-bind:key')函数到attrsMap中判断是否存在'v-bind:key',取这个属性的值赋为val并从从attrsList删除,但是不会从attrsMap删除,最后将'v-bind:key'的值,也就是val作为dynamicValue,之后再返回解析过滤后的结果,最后将结果set为processKey中将元素的key property。然后存储在segments中,至于segments是什么,在上面的源码中可以看到。v-bind:title源码分析
title是一种“非vue特殊的”也就是普通的HTML attribute。
通过阅读源码我们看出:对于原生的属性,比如title这样的属性,vue会首先解析出name和value,然后再进行一系列的是否有modifiers的判断(modifier的部分在下文中会详细讲解),最终向更新ASTElement的attrs,从而attrsList和attrsMap也同步更新。
v-bind:class源码分析
css的class在前端开发的展现层面,是非常重要的一层。 因此vue在对于class属性也做了很多特殊的处理。
在transfromNode函数中,会通过getAndRemoveAttr得到静态class,也就是
class="foo"
;在getBindingAttr得到绑定的class,也就是v-bind:class="vBind.class"
即v-bind:class="{ borderRadius: isBorderRadius }"
,将ASTElement的classBinding赋值为我们绑定的属性供后续使用。v-bind:style源码分析
style是直接操作样式的优先级仅次于important,比class更加直观的操作样式的一个HTML attribute。 vue对这个属性也做了特殊的处理。
在transfromNode函数中,会通过getAndRemoveAttr得到静态style,也就是
style="{fontSize: '12px'}"
;在getBindingAttr得到绑定的style,也就是v-bind:style="vBind.style"
即v-bind:class={ minHeight: 100 + 'px' , maxHeight}"
,其中maxHeight是一个变量,将ASTElement的styleBinding赋值为我们绑定的属性供后续使用。v-bind:text-content.prop源码分析
textContent是DOM对象的原生属性,所以可以通过prop进行标识。 如果我们想对某个DOM prop直接通过vue进行set,可以在DOM节点上做修改。
下面我们来看源码。
通过上面的源码我们可以看出,
v-bind:text-content.prop
中的text-content首先被驼峰化为textContent(这是因为DOM property都是驼峰的格式),vue还对innerHtml错误写法做了兼容也是有心,之后再通过prop标识符,将textContent属性增加到ASTElement的props中,而这里的props本质上也是一个ASTAttr。有一个很值得思考的问题:为什么要这么做?与HTML attribute有何异同?
v-bind:title.attr,v-bind:text-content.prop
只不过vue默许不加修饰符的就是HTML attribute罢了v-bind的修饰符.camel .sync源码分析
.camel仅仅是驼峰化,很简单。 但是.sync就不是这么简单了,它会扩展成一个更新父组件绑定值的v-on侦听器。
其实刚开始看到这个.sync修饰符我是一脸懵逼的,但是仔细阅读一下组件的.sync再结合实际工作,就会发现它的强大了。
在vue中,父组件向子组件传递的props是无法被子组件直接通过
this.props.foo = newFoo
去修改的。 除非我们在组件this.$emit("updateFoo", newFoo)
,然后在父组件使用v-on做事件监听updateFoo事件。若是想要可读性更好,可以在$emit的name上改为update:foo,然后v-on:update:foo。有没有一种更加简洁的写法呢??? 那就是我们这里的.sync操作符。 可以简写为:
然后在子组件通过
this.$emit("update:foo", newFoo);
去触发,注意这里的事件名必须是update:xxx的格式,因为在vue的源码中,使用.sync修饰符的属性,会自定生成一个v-on:update:xxx的监听。下面我们来看源码:
通过阅读源码我们可以看到: 对于v-bind:foo.sync的属性,vue会判断属性是否为动态属性。 若不是动态属性,首先为其增加驼峰化后的监听,然后再为其增加一个连字符的监听,例如v-bind:foo-bar.sync,首先v-on:update:fooBar,然后v-on:update:foo-bar。v-on监听是通过addHandler加上的。 若是动态属性,就不驼峰化也不连字符化了,通过
addHandler(el,
update:${name}, ...)
,老老实实监听那个动态属性的事件。一句话概括.sync: .sync是一个语法糖,简化v-bind和v-on为v-bind.sync和this.$emit('update:xxx')。为我们提供了一种子组件快捷更新父组件数据的方式。
参考资料: https://cn.vuejs.org/v2/api/#v-bind https://github.com/vuejs/vue/tree/dev/src https://cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6