帶你理解vue中的v-bind
一、v-bind關(guān)鍵源碼分析
1、v-bind化的屬性統(tǒng)一存儲在哪里:attrsMap與attrsList
<p v-bind:title="vBindTitle"></p>
假設(shè)為p標(biāo)簽v-bind
化了title
屬性,我們來分析title
屬性在vue
中是如何被處理的。
vue在拿到這個html標(biāo)簽之后,處理title屬性,會做以下幾步:
- 解析
HTML
,解析出屬性集合attrs
,在start
回調(diào)中返回 - 在start回調(diào)中創(chuàng)建
ASTElement,createASTElement(... ,attrs, ...)
- 創(chuàng)建后
ASTElement
會生成attrsList
和attrsMap
至于創(chuàng)建之后是如何處理v-bind:title
這種普通的屬性值的,可以在下文的v-bind:src源碼分析中一探究竟。
2、解析HTML,解析出屬性集合attrs,在start回調(diào)中返回
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函數(shù) options.start(tagName, attrs, unary, match.start, match.end) } }
3、在start回調(diào)中創(chuàng)建ASTElement,createASTElement(... ,attrs, ...)
// 解析HMTL parseHTML(template, { ... start(tag, attrs, unary, start, end) { let element: ASTElement = createASTElement(tag, attrs, currentParent) // 注意此處的attrs } })
4、創(chuàng)建后ASTElement會生成attrsList和attrsMap
// 創(chuàng)建AST元素 export function createASTElement ( tag: string, attrs: Array<ASTAttr>, // 屬性對象數(shù)組 parent: ASTElement | void // 父元素也是ASTElement ): ASTElement { // 返回的也是ASTElement return { type: 1, tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), rawAttrsMap: {}, parent, children: [] } }
5、attrs的數(shù)據(jù)類型定義
// 聲明一個ASTAttr 屬性抽象語法樹對象 數(shù)據(jù)類型 declare type ASTAttr = { name: string; // 屬性名 value: any; // 屬性值 dynamic?: boolean; // 是否是動態(tài)屬性 start?: number; end?: number };
6、綁定屬性獲取函數(shù)
綁定屬性獲取函數(shù)getBindingAttr 和 屬性操作函數(shù) getAndRemoveAttr
getBindingAttr
及其子函數(shù)getAndRemoveAttr
在處理特定場景下的v-bind
十分有用,也就是”v-bind
如何處理不同的綁定屬性“章節(jié)很有用。 這里將其列舉出來供下文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>
1、v-bind:key源碼分析
function processKey (el) { const exp = getBindingAttr(el, 'key') if(exp){ ... el.key = exp; } }
processKey
函數(shù)中用到了getBindingAttr
函數(shù),由于我們用的是v-bind
,沒有用:,所以const dynamicValue = getAndRemoveAttr(el, 'v-bind:'+'key')
;,getAndRemoveAttr(el, 'v-bind:key')
函數(shù)到attrsMap
中判斷是否存在 'v-bind:key'
,取這個屬性的值賦為val并從從attrsList
刪除,但是不會從attrsMap
刪除,最后將 'v-bind:key'
的值,也就是val作為dynamicValue
,之后再返回解析過濾后的結(jié)果,最后將結(jié)果set
為processKey
中將元素的key property
。然后存儲在segments
中,至于segments
是什么,在上面的源碼中可以看到。
2、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
,然后再進(jìn)行一系列的是否有modifiers
的判斷(modifier
的部分在下文中會詳細(xì)講解),最終向更新ASTElement
的attrs
,從而attrsList
和attrsMap
也同步更新。
3、v-bind:class源碼分析
css
的class
在前端開發(fā)的展現(xiàn)層面,是非常重要的一層。 因此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
函數(shù)中,會通過getAndRemoveAttr
得到靜態(tài)class
,也就是class="foo";在getBindingAttr
得到綁定的class,也就是v-bind:class="vBind.class
"即v-bind:class="{ borderRadius: isBorderRadius
}",將ASTElement的classBinding賦值為我們綁定的屬性供后續(xù)使用。
4、、v-bind:style源碼分析
style是直接操作樣式的優(yōu)先級僅次于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
函數(shù)中,會通過getAndRemoveAttr
得到靜態(tài)style,也就是style="{fontSize: '12px'
}";在getBindingAttr
得到綁定的style,也就是v-bind:style="vBind.style"即v-bind:class={ minHeight: 100 + 'px' , maxHeight
}",其中maxHeight是一個變量,將ASTElement
的styleBinding
賦值為我們綁定的屬性供后續(xù)使用。
5、v-bind:text-content.prop源碼分析
textContent
是DOM對象的原生屬性,所以可以通過prop進(jìn)行標(biāo)識。 如果我們想對某個DOM prop直接通過vue進(jìn)行set,可以在DOM節(jié)點(diǎn)上做修改。
下面我們來看源碼。
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
(這是因?yàn)?code>DOM property都是駝峰的格式),vue還對innerHtml
錯誤寫法做了兼容也是有心,之后再通過prop標(biāo)識符,將textContent屬性增加到ASTElement的props
中,而這里的props本質(zhì)上也是一個ASTAttr。
有一個很值得思考的問題:為什么要這么做?與HTML attribute有何異同?
- 沒有
HTML attribute
可以直接修改DOM的文本內(nèi)容,所以需要單獨(dú)去標(biāo)識 - 比通過js去手動更新DOM的文本節(jié)點(diǎn)更加快捷,省去了查詢dom然后替換文本內(nèi)容的步驟
- 在標(biāo)簽上即可看到我們對哪個屬性進(jìn)行了v-bind,非常直觀
- 其實(shí)v-bind:title可以理解為
v-bind:title.attr,v-bind:text-content.prop
只不過vue
默許不加修飾符的就是HTMLattribute
罷了
6、v-bind的修飾符.camel .sync源碼分析
.camel
僅僅是駝峰化,很簡單。 但是.sync就不是這么簡單了,它會擴(kuò)展成一個更新父組件綁定值的v-on偵聽器。
其實(shí)剛開始看到這個.sync修飾符我是一臉懵逼的,但是仔細(xì)閱讀一下組件的.sync再結(jié)合實(shí)際工作,就會發(fā)現(xiàn)它的強(qiáng)大了。
<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做事件監(jiān)聽updateFoo
事件。若是想要可讀性更好,可以在$emit
的name上改為update:foo,然后v-on:update:foo
。
有沒有一種更加簡潔的寫法呢??? 那就是我們這里的.sync操作符。 可以簡寫為:
<Parent v-bind:foo.sync="parent.foo"></Parent>
然后在子組件通過this.$emit("update:foo", newFoo
);去觸發(fā),注意這里的事件名必須是update:xxx的格式,因?yàn)樵趘ue的源碼中,使用.sync修飾符的屬性,會自定生成一個v-on:update:xxx
的監(jiān)聽。
下面我們來看源碼:
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是連字符化函數(shù),其中camelize是駝峰化函數(shù) 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會判斷屬性是否為動態(tài)屬性。 若不是動態(tài)屬性,首先為其增加駝峰化后的監(jiān)聽,然后再為其增加一個連字符的監(jiān)聽,例如v-bind:foo-bar.sync
,首先v-on:update:fooBar
,然后v-on:update:foo-bar
。v-on監(jiān)聽是通過addHandle
r加上的。 若是動態(tài)屬性,就不駝峰化也不連字符化了,通過addHandler(el,update:${name}, ...),
老老實(shí)實(shí)監(jiān)聽那個動態(tài)屬性的事件。
一句話概括.sync
: .sync
是一個語法糖,簡化v-bind和v-on為v-bind.sync
和this.$emit('update:xxx'
)。為我們提供了一種子組件快捷更新父組件數(shù)據(jù)的方式。
到此這篇關(guān)于帶你理解vue中的v-bind的文章就介紹到這了,更多相關(guān)vue中的v-bind內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue@cli3項(xiàng)目模板怎么使用public目錄下的靜態(tài)文件
這篇文章主要介紹了vue@cli3項(xiàng)目模板怎么使用public目錄下的靜態(tài)文件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07ElementUI實(shí)現(xiàn)el-table列寬自適應(yīng)的代碼詳解
這篇文章給大家介紹了ElementUI實(shí)現(xiàn)el-table列寬自適應(yīng)的詳細(xì)步驟,文中通過代碼示例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01vue 組件之間事件觸發(fā)($emit)與event Bus($on)的用法說明
這篇文章主要介紹了vue 組件之間事件觸發(fā)($emit)與event Bus($on)的用法說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07vue中選中多個選項(xiàng)并且改變選中的樣式的實(shí)例代碼
這篇文章主要介紹了vue中選中多個選項(xiàng)并且改變選中的樣式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09vue-cli中實(shí)現(xiàn)響應(yīng)式布局的方法
這篇文章主要介紹了vue-cli中實(shí)現(xiàn)響應(yīng)式布局的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03vuex中遇到的坑,vuex數(shù)據(jù)改變,組件中頁面不渲染操作
這篇文章主要介紹了vuex中遇到的坑,vuex數(shù)據(jù)改變,組件中頁面不渲染操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11