Vue之数据对象
前言本篇文章旨在学习Vue对于相关属性或特性的处理,同时学习HTML特性和DOM属性的区别和联系,实际上涉及到以下内容:HTML特性和DOM属性之间的联系与区别Vue的数据对象中涉及的HTML特性、DOM属性、组件prop、特殊特性Vue中对于4种属性或特性处理所涉及的相关逻辑HTML特性和DOM属性实际上对于HTML特性和DOM属性,之前对于其的理解是相同的东西,实际上它们是不同...
前言
本篇文章旨在学习Vue对于相关属性或特性的处理,同时学习HTML特性和DOM属性的区别和联系,实际上涉及到以下内容:
- HTML特性和DOM属性之间的联系与区别
- Vue的数据对象中涉及的HTML特性、DOM属性、组件prop、特殊特性
- Vue中对于4种属性或特性处理所涉及的相关逻辑
HTML特性和DOM属性
实际上对于HTML特性和DOM属性,之前对于其的理解是相同的东西,实际上它们是不同的概念。
Attribute表示特性,Property表示属性(实际上这两个单词都可以翻译成属性)。在《JavaScript高级程序设计》一书中,就存在特性和属性的不同表述。在该书中存在涉及到特性和属性区别有一些描述:
所有HTML元素都由HTMLElement或者更具体的子类型表示的,每个元素都有一个或多个特性
操作特性的DOM方法主要有三个:getAttribute()、setAttribute()和removeAttribute()。这三个方法可以针对任何特性使用,包括哪些以HTMLElement类型属性定义的特性
HTMLElement类型直接继承自Element并添加了一些属性。添加的这些属性分别对应着每个HTML元素中都存在的标准特性。
从上面几个的描述中,目前可以得到关于特性和属性的概念:
- 特性是指在HTML标签上定义的
- 属性是指DOM对象的,可以通过这些属性来修改对应HTML上的特性值
- 一个HTML元素的特性和DOM属性是存在交集的,交集的这部分可能名称相同也可能不同,正是这种机制才有了所谓的DOM修改,因为DOM通过操作对应的属性来修改指定的特性值,而修改后的特性值会立即反映在页面上
简单的来说,特性是HTML区别于与之对应DOM对象的属性的表述,不是每一个特性都有与之对应的属性,反之亦然。
实例:class特性、style特性、自定义data-key特性
看看上面几个特性在DOM对象中与之对应的属性,应该相关的DOM操作。
<div
class="attr"
style="text-align: center"
data-key="key"
>
</div>
var app = document.getElementById('app');
var dataKey = app.getAttribute('data-key');
console.log(dataKey); // "key"
// 但是自定义特性没有与之对应的属性,即app.dataKey是不存在的
console.log(app.dataKey);
// 获取class特性
var class = app.getAttribute('class');
// 与之对应的DOM属性
var classOfAttr = app.className;
// 获取style特性,获取到的值是字符串
var style = app.getAttribute('style');
// 与之对应DOM属性,获取到的结果是对象
var styleOfAttr = app.style;
Vue中涉及的属性和特性
实际上主要是渲染函数&JSX提及到的数据对象,其中涉及到的特性和属性有:
{
// 普通的HTML特性
attrs: {},
// DOM属性
domProps: {},
// 组件prop
props: {}
// 特殊特性
key: "",
ref: "",
slot: "",
scopedSlots: {
default: () => {}
}
...
}
由此提出以下系列问题:
- domProps中属性放在attrs对象中
- attrs中属性放在domProps中
- 组件props属性放在attrs或domProps中
- attr或domProps放在组件Prop中
- 组件Prop放在最外层
- 特殊特性放在attrs、domProps或组件props
实际上上面的问题都是Vue对于这种判别逻辑的处理,由于HTML特性和DOM属性会存在相对应的,所以这里就分为两类情况:
- DOM属性和HTML特性相对应的特性,例如:title
- 私有的,DOM属性例如innerText,HTML特性例如自定义属性
存在编译步骤
存在template模板内容会调用编译过程的,通过这个过程可以清楚看到Vue对所有属性和特性的分配处理,借此来对比使用JSX的数据对象应该的书写方式。
实例:
<el-button
class="attr"
key="app"
ref="appRef"
:data="value"
:text-content.prop="value"
id="btn"
type="primary"
>
</el-button>
实际上要看还是Vue的编译器部分,不过这里主要注重标签的特性解析,实际上就是主要是parseHTML的start部分,主要如下:
// template就是要解析的文本,实际上就是template中的内容
parseHTML(template, {
start: function start(tag, attrs,unary) {
// 主要关注这个函数中解析逻辑
processElement(element, options);
}
});
processElement的内容如下:
function processElement (element, options) {
// 解析key
processKey(element);
// 当前标签是文本
element.plain = !element.key && !element.attrsList.length;
// 解析ref
processRef(element);
// 解析slot
processSlot(element);
// 解析is属性和inline-template
processComponent(element);
// 解析attrs
processAttrs(element);
}
首先看看key的获取,即processKey函数主要逻辑:
function processKey(el) {
/**
getBindingAttr就是查找当前标签解析出来的所有特性(过程变量,会存在attrMap和attrList)是否包含
:key、v-bind:key、key这三种特性,实际上就是判断是否是动态prop,不是就判断是否是静态prop,然后获取对应值并删除attrList中对应的属性
*/
var exp = getBindingAttr(el, 'key');
el.key = exp;
}
ref的处理也是如此,slot的处理就稍微复杂些,需要判断slot还是slot-scope然后获取相关name属性或设置作用域(这边不是主要专注点本期就不展开)。
最主要就是processAttrs函数的处理逻辑了,HTML属性、domProps和自定义组件prop都是在这里处理(前提是存在解析器步骤)。
上图是processAttrs的主要的解析步骤,本次需要关注的是v-bind或:的解析逻辑和普通属性的解析逻辑。
普通属性的都是存放在attrs对象内,即调用addAttr函数。
v-bind或:开头的prop的处理相对想说就比较复杂,首先通过Vue.js的官方文档,可知v-bind支持3种修饰符:
- .prop:被用于绑定DOM属性
- .camel:将kebaba-case特性名转换成camelCase
- .sync:语法糖,会绑定事件处理v-on:update的处理
这里需要注意的是.prop修饰符,在实际生产中不常用,该属性支持将DOM属性通过特性方式来修改。实际官方文档就给出了一个例子:
<template>
<div :text-content.prop="text"></div>
<div :inner-html.prop="text"></div>
</template>
<script>
export default {
data() {
return {
text: '测试'
};
}
}
</script>
上面的例子HTML特性 text-content和inner-html不能看成自定义特性,可以看成DOM属性textContent 和 innerHTML,而实际效果跟DOM插入是相同:
两个div都存在内容,内容文本是text的值,即测试
而对于这种Vue的处理是将其看成prop,言外之意就是普通的prop实际上Vue会将其存储到attrs对象中
从之前上图的逻辑中也可以看出,只有满足:isProp || 特殊属性,才会调用addProp将属性保存到props对象。
isProp就是标识存在.prop修饰符的情况,而特殊属性这里简单提及下,下一篇的文章会具体该部分的梳理
实际上特殊属性的校验是调用下面函数:
var acceptValue = makeMap('input,textarea,option,select,progress');
var mustUseProp = function (tag, type, attr) {
return (
(attr === 'value' && acceptValue(tag)) && type !== 'button' ||
(attr === 'selected' && tag === 'option') ||
(attr === 'checked' && tag === 'input') ||
(attr === 'muted' && tag === 'video')
)
};
从上面可以看出特殊属性具体是:
存在value属性的元素、radio、checkbox、select、option、video
基本上都是存在复杂处理的表单元素,之所以这些需要当成动态prop来处理,是因为Vue对这些元素存在着特殊处理,实际上主要是v-model语法糖的实现(video排除在外,video muted这个是另外的问题,代码中有特殊注释),下一篇文章会具体看看v-model语法糖的实现(包含了这些特殊的表单元素)。
至此可以得到结论:
存在.prop修饰和指定标签的特殊属性,会被保存到Vue的props对象中。处了前面情况,其他属性都在Vue的attrs对象中,包含不存在.prop修饰符的动态prop
上面是解析AST过程中涉及到属性的处理逻辑,实际上在构建render还有生成code的处理逻辑,数据对象的生成在这里会有具体的逻辑,主要是:
generate -> genElement -> genData$2
genData$2就是构建大部分数据对象的处理函数,具体代码如下:
function genData$2 (el, state) {
var data = '{';
// 指令
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
// key
if (el.key) {
data += "key:" + (el.key) + ",";
}
// ref
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
// refInFor
if (el.refInFor) {
data += "refInFor:true,";
}
// pre
if (el.pre) {
data += "pre:true,";
}
// component动态组件
if (el.component) {
data += "tag:\"" + (el.tag) + "\",";
}
// module data generation functions
for (var i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el);
}
// attributes
if (el.attrs) {
data += "attrs:{" + (genProps(el.attrs)) + "},";
}
// DOM props,注意这里,所有props都是domProps
if (el.props) {
data += "domProps:{" + (genProps(el.props)) + "},";
}
// event handlers
if (el.events) {
data += (genHandlers(el.events, false, state.warn)) + ",";
}
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true, state.warn)) + ",";
}
// slot target
// only for non-scoped slots
if (el.slotTarget && !el.slotScope) {
data += "slot:" + (el.slotTarget) + ",";
}
// scoped slots
if (el.scopedSlots) {
data += (genScopedSlots(el.scopedSlots, state)) + ",";
}
return data
}
通过这边的处理,再对比通过JSX书写的数据对象:
{
// 普通的HTML特性
attrs: {},
// DOM属性
domProps: {},
// 组件prop
props: {}
// 特殊特性
key: "",
ref: "",
slot: "",
scopedSlots: {
default: () => {}
}
...
}
二者方式最主要的区别是JSX少了解析template阶段,手动调用$createElement来实现组件相关逻辑。
而在上面的解析阶段,提到了attrs和props对象,需要注意的是这两个对象的生命周期结束于render函数构建出来之后,即render函数构建完成后这两个对象就被丢弃了。
<!-- template形式 -->
<el-button
class="attr"
key="app"
ref="appRef"
:text-content.prop="value"
id="btn"
type="primary"
>
</el-button>
<!-- jsx -->
<script>
export default {
data() {
return {
value: [1, 2]
};
},
render() {
const h = this.$createElement;
return h('el-button',{
key: 'app',
ref: 'appRef',
'class': {
attr: true
},
attrs: {
id: 'btn'
},
domProps: {
textContent: this.value
},
props: {
type: 'primary'
}
});
}
}
</script>
效果:
实际上面两种形式在生成VNode时是存在不同的,实际上是data属性值不同。具体如下:
左图是template形式构建的VNode的data属性,有图是render形式构建的VNode的data属性。实际上还是存在差别的:
-
综合上面的种种,template形式下编译器存在特殊处理:
1、class内部是staticClass,即静态class,反而言之,也存在动态class,即Vue支持写法
<div class="attr" :class="value"></div>
2、props如果子组件存在对应的prop,就不会被解析到attrs对象中,会在initState函数中对props有相关处理
-
JSX形式的,VNode的data属性传递的数据对象不会变动,会在此基础上添加相关属性
到此,可以来看看下面几个问题:
domProps与attrs
由于DOM属性存在dom特有的,也存在与HTML特性相对应的,实际上:
- 如果将HTML特性放在domProps中也会生效
- dom属性只能放在domProps才能生效
组件props属性与attrs或domProps
render函数构建之后,就会执行render函数生成VNode,无论是template形式还是手动构建render函数,都会在背后调用$createElement来创建节点,而这个过程会涉及到一个非常重要的函数:extractPropsFromVNodeData。
function extractPropsFromVNodeData (
data,
Ctor,
tag
) {
// 组件的props选项
var propOptions = Ctor.options.props;
if (isUndef(propOptions)) {
return
}
var res = {};
// attrs、props
var attrs = data.attrs;
var props = data.props;
if (isDef(attrs) || isDef(props)) {
// 遍历props中属性
for (var key in propOptions) {
// 其他逻辑
// 这里会检查attrs对象的属性,这里的逻辑意味着props中的属性可以写到attrs中
checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey, false);
}
}
return res
}
即render函数传递数据对象时,props中的内容可以写到attrs对象中。
特殊特性放在attrs、domProps或组件props
特殊特性key、ref、slot、scopedSlots是Vue的组件属性,只能放在最外层,不能放在props、attrs、domProps。
总结
template形式下总结:
- 非.prop修饰符的prop只会保存在attrs中
- .prop修饰符的prop和表单元素等特殊属性的会被保存props中,构建出来对应domProps
JSX形式下总结:
- DOM属性只能数据对象domProps属性中,attrs和domProps相对应的可以写在这两个属性中
- prop属性可以写在props和attrs属性中,template形式.prop修饰的prop需要写在domProps中
- 特殊特性只能写在最外层,不能写在attrs、props、domProps中
下一篇文章会具体看v-model语法糖的实现逻辑,其中就会涉及到特殊属性例如表单元素的value等。
更多推荐
所有评论(0)