共计 7901 个字符,预计需要花费 20 分钟才能阅读完成。
如果你写过 vue,对 v -bind 这个指令肯定不生疏。上面我将从源码层面去带大家分析一下 v -bind 背地的原理。
会从以下几个方面去摸索:
v-bind 要害源码剖析
v-bind 化的属性对立存储在哪里:attrsMap 与 attrsList
绑定属性获取函数 getBindingAttr 和 属性操作函数 getAndRemoveAttr
v-bind 如何解决不同的绑定属性
v-bind:key 源码剖析
v-bind:title 源码剖析
v-bind:class 源码剖析
v-bind:style 源码剖析
v-bind:text-content.prop 源码剖析
v-bind 的修饰符.camel .sync 源码剖析
v-bind 要害源码剖析
v-bind 化的属性对立存储在哪里:attrsMap 与 attrsList
<p v-bind:title=”vBindTitle”></p>
复制代码
假如为 p 标签 v -bind 化了 title 属性,咱们来剖析 title 属性在 vue 中是如何被解决的。
vue 在拿到这个 html 标签之后,解决 title 属性,会做以下几步:
解析 HTML,解析出属性汇合 attrs,在 start 回调中返回
在 start 回调中创立 ASTElement,createASTElement(… ,attrs, …)
创立后 ASTElement 会生成 attrsList 和 attrsMap
至于创立之后是如何解决 v -bind:title 这种一般的属性值的,能够在下文的 v -bind:src 源码剖析中一探到底。前端培训
解析 HTML,解析出属性汇合 attrs,在 start 回调中返回
function handleStartTag (match) {
...
const l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {const args = match.attrs[i]
...
attrs[i] = {name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
}
}
…
if (options.start) {
// 在这里上传到 start 函数
options.start(tagName, attrs, unary, match.start, match.end)
}
}
复制代码
在 start 回调中创立 ASTElement,createASTElement(… ,attrs, …)
// 解析 HMTL
parseHTML(template, {
...
start(tag, attrs, unary, start, end) {let element: ASTElement = createASTElement(tag, attrs, currentParent) // 留神此处的 attrs
}
})
复制代码
创立后 ASTElement 会生成 attrsList 和 attrsMap
// 创立 AST 元素
export function createASTElement (
tag: string,
attrs: Array<ASTAttr>, // 属性对象数组
parent: ASTElement | void // 父元素也是 ASTElement
): ASTElement {// 返回的也是 ASTElement
return {
type: 1,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
rawAttrsMap: {},
parent,
children: []
}
}
复制代码
attrs 的数据类型定义
// 申明一个 ASTAttr 属性形象语法树对象 数据类型
declare type ASTAttr = {
name: string; // 属性名
value: any; // 属性值
dynamic?: boolean; // 是否是动静属性
start?: number;
end?: number
};
复制代码
绑定属性获取函数 getBindingAttr 和 属性操作函数 getAndRemoveAttr
getBindingAttr 及其子函数 getAndRemoveAttr 在解决特定场景下的 v -bind 非常有用,也就是”v-bind 如何解决不同的绑定属性“章节很有用。这里将其列举进去供下文 v -bind:key 源码剖析;v-bind:src 源码剖析;v-bind:class 源码剖析;v-bind:style 源码剖析;v-bind:dataset.prop 源码剖析源码剖析参照。
export function getBindingAttr (
el: ASTElement,
name: string,
getStatic?: boolean
): ?string {
const dynamicValue =
getAndRemoveAttr(el, ':' + name) ||
getAndRemoveAttr(el, 'v-bind:' + name)
if (dynamicValue != null) {
return parseFilters(dynamicValue)
} else if (getStatic !== false) {
const staticValue = getAndRemoveAttr(el, name)
if (staticValue != null) {return JSON.stringify(staticValue)
}
}
}
复制代码
// 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"
}
复制代码
<div
v-bind:key=”vBind.key”
v-bind:title=”vBind.title”
v-bind:class=”vBind.class”
v-bind:style=”vBind.style”
v-bind:text-content.prop=”vBind.textContent”
/>
</div>
复制代码
v-bind:key 源码剖析
function processKey (el) {
const exp = getBindingAttr(el, ‘key’)
if(exp){
...
el.key = exp;
}
}
复制代码
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。
function processAttrs(el){
const list = el.attrsList;
...
if (bindRE.test(name)) { // v-bind
name = name.replace(bindRE, '')
value = parseFilters(value)
...
addAttr(el, name, value, list[i], ...)
}
}
export const bindRE = /^:|^.|^v-bind:/
export function addAttr (el: ASTElement, name: string, value: any, range?: Range, dynamic?: boolean) {
const attrs = dynamic
? (el.dynamicAttrs || (el.dynamicAttrs = []))
: (el.attrs || (el.attrs = []))
attrs.push(rangeSetItem({ name, value, dynamic}, range))
el.plain = false
}
复制代码
通过浏览源码咱们看出:对于原生的属性,比方 title 这样的属性,vue 会首先解析出 name 和 value,而后再进行一系列的是否有 modifiers 的判断(modifier 的局部在下文中会具体解说),最终向更新 ASTElement 的 attrs,从而 attrsList 和 attrsMap 也同步更新。
v-bind:class 源码剖析
css 的 class 在前端开发的展示层面,是十分重要的一层。因而 vue 在对于 class 属性也做了很多非凡的解决。
function transformNode (el: ASTElement, options: CompilerOptions) {
const warn = options.warn || baseWarn
const staticClass = getAndRemoveAttr(el, ‘class’)
if (staticClass) {
el.staticClass = JSON.stringify(staticClass)
}
const classBinding = getBindingAttr(el, ‘class’, false / getStatic /)
if (classBinding) {
el.classBinding = classBinding
}
}
复制代码
在 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 对这个属性也做了非凡的解决。
function transformNode (el: ASTElement, options: CompilerOptions) {
const warn = options.warn || baseWarn
const staticStyle = getAndRemoveAttr(el, ‘style’)
if (staticStyle) {
el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
}
const styleBinding = getBindingAttr(el, ‘style’, false / getStatic /)
if (styleBinding) {
el.styleBinding = styleBinding
}
}
复制代码
在 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 节点上做批改。
上面咱们来看源码。
function processAttrs (el) {
const list = el.attrsList
…
if (bindRE.test(name)) {// v-bind
if (modifiers) {if (modifiers.prop && !isDynamic) {name = camelize(name)
if (name === 'innerHtml') name = 'innerHTML'
}
}
if (modifiers && modifiers.prop) {addProp(el, name, value, list[i], isDynamic)
}
}
}
export function addProp (el: ASTElement, name: string, value: string, range?: Range, dynamic?: boolean) {
(el.props || (el.props = [])).push(rangeSetItem({ name, value, dynamic}, range))
el.plain = false
}
props?: Array<ASTAttr>;
复制代码
通过下面的源码咱们能够看出,v-bind:text-content.prop 中的 text-content 首先被驼峰化为 textContent(这是因为 DOM property 都是驼峰的格局),vue 还对 innerHtml 谬误写法做了兼容也是有心,之后再通过 prop 标识符,将 textContent 属性减少到 ASTElement 的 props 中,而这里的 props 实质上也是一个 ASTAttr。
有一个很值得思考的问题:为什么要这么做?与 HTML attribute 有何异同?
没有 HTML attribute 能够间接批改 DOM 的文本内容,所以须要独自去标识
比通过 js 去手动更新 DOM 的文本节点更加快捷,省去了查问 dom 而后替换文本内容的步骤
在标签上即可看到咱们对哪个属性进行了 v -bind,十分直观
其实 v -bind:title 能够了解为 v -bind:title.attr,v-bind:text-content.prop 只不过 vue 默认不加修饰符的就是 HTML attribute 罢了
v-bind 的修饰符.camel .sync 源码剖析
.camel 仅仅是驼峰化,很简略。然而.sync 就不是这么简略了,它会扩大成一个更新父组件绑定值的 v -on 侦听器。
其实刚开始看到这个.sync 修饰符我是一脸懵逼的,然而仔细阅读一下组件的.sync 再结合实际工作,就会发现它的弱小了。
<Parent
v-bind:foo=”parent.foo”
v-on:updateFoo=”parent.foo = $event”
</Parent>
复制代码
在 vue 中,父组件向子组件传递的 props 是无奈被子组件间接通过 this.props.foo = newFoo 去批改的。除非咱们在组件 this.$emit(“updateFoo”, newFoo),而后在父组件应用 v -on 做事件监听 updateFoo 事件。若是想要可读性更好,能够在 $emit 的 name 上改为 update:foo, 而后 v -on:update:foo。
有没有一种更加简洁的写法呢???那就是咱们这里的.sync 操作符。能够简写为:
<Parent v-bind:foo.sync=”parent.foo”></Parent>
复制代码
而后在子组件通过 this.$emit(“update:foo”, newFoo); 去触发,留神这里的事件名必须是 update:xxx 的格局,因为在 vue 的源码中,应用.sync 修饰符的属性,会自定生成一个 v -on:update:xxx 的监听。
上面咱们来看源码:
if (modifiers.camel && !isDynamic) {
name = camelize(name)
}
if (modifiers.sync) {
syncGen = genAssignmentCode(value, $event
)
if (!isDynamic) {
addHandler(el,`update:${camelize(name)}`,syncGen,null,false,warn,list[i])
// Hyphenate 是连字符化函数,其中 camelize 是驼峰化函数
if (hyphenate(name) !== camelize(name)) {addHandler(el,`update:${hyphenate(name)}`,syncGen,null,false,warn,list[i])
}
} else {
// handler w/ dynamic event name
addHandler(el,`"update:"+(${name})`,syncGen,null,false,warn,list[i],true)
}
}
复制代码
通过浏览源码咱们能够看到:对于 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’)。为咱们提供了一种子组件快捷更新父组件数据的形式。