目录

  • 11. 条件渲染
    • 11.1 v-if
    • 11.2 v-show
  • 12. 列表渲染
    • 12.1 v-for(基本列表使用)
    • 12.2 key的作用与原理
      • 真实DOM和其解析流程
      • 虚拟 DOM 的好处
      • 虚拟DOM中key的作用
      • 用index作为key可能会引发的问题:
      • 面试题: react、vue中的key有什么作用?(key的内部原理)
    • 12.3 列表过滤(模糊搜索)
    • 12.4 列表排序(升序、降序、原顺序)
    • 12.5 Vue监测 data 中的数据
      • 模拟一下 Vue 中的数据监测:
      • Vue.set()方法的使用(Vue.$set)
      • 总结:(Vue监测数据的原理)
  • 13. 收集表单数据
  • 持续更新中

11. 条件渲染

11.1 v-if

  • 写法:

    v-if="表达式"

    v-else-if="表达式"

    v-else

  • 适用于:切换频率较低的场景。

  • 特点:不展示的DOM元素直接被移除。

  • 注意:v-if 可以和 v-else-ifv-else 一起使用,但要求结构不能被打断(就是标签与标签必须紧挨在一起)。

示例代码

<div id="app"><h2>当前的n值是:{{n}}</h2><button @click="n++">点我n+1</button><!-- 使用v-if做条件渲染 --><h2 v-if="true">Hello,{{name}}!</h2><h2 v-if="n === 1">Hello,{{name}}!</h2><!-- v-if、v-else-if和v-else --><div v-if="n === 1">Vue</div><div v-else-if="n === 2">React</div><div v-else>JavaScript</div><!-- v-if与template的配合使用,就不需要写很多个判断,在template标签内写一个就行 --><!-- 这里的思想就像事件代理的使用 --><template v-if="n === 1"><h2>你好</h2><h2>{{name}}</h2><h2>{{n}}</h2></template>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {name: '盖伦',n: 0,}}})
</script>

11.2 v-show

  • 写法:v-show="表达式"
  • 适用于:切换频率较高的场景。
  • 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)。

示例代码

<div id="app"><h2>当前的n值是:{{n}}</h2><button @click="n++">点我n+1</button><!-- 使用v-show做条件渲染 --><h2 v-show="true">Hello,{{name}}!</h2><h2 v-show="n === 1">Hello,{{name}}!</h2><h2 v-show="n === 2">Hello,{{name}}!</h2>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {name: '盖伦',n: 0,}}})
</script>

备注:

  • 使用 v-if 的时候,元素可能无法获取到,而使用 v-show 一定可以获取到。
  • v-if 是实打实地改变DOM元素,v-show 是隐藏或显示DOM元素。

12. 列表渲染

12.1 v-for(基本列表使用)

  • 语法:v-for="(item, index) in xxx" :key="yyy"
  • 用于展示列表数据。
  • 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)。

示例代码

<div id="app"><!-- 遍历数组 --><h2>人员列表(遍历数组)</h2><ul><li v-for="(person) in persons" :key="person.id">{{person.id}}-{{person.name}}-{{person.age}}</li></ul><!-- 遍历对象 --><h2>汽车信息(遍历对象)</h2><ul><li v-for="(value,key) in car" :key="key">{{key}}--------{{value}}</li></ul><!-- 遍历字符串 --><h2>测试遍历字符串(用的少)</h2><ul><li v-for="(char,index) in str" :key="index">{{char}}--------{{index}}</li></ul><!-- 遍历指定次数 --><h2>测试遍历指定次数(用的少)</h2><ul><li v-for="(number,index) in 5" :key="index">{{index}}--------{{number}}</li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {persons: [{ id: 1, name: '盖伦', age: 18 },{ id: 2, name: '皇子', age: 19 },{ id: 3, name: '赵信', age: 20 },],car: {name: '兰博基尼',price: '200w',color: '雅黑',},str:'hello',}}})
</script>

12.2 key的作用与原理

Vue中的key有什么作用?(key的内部原理)

了解Vue中key的原理先要了解Vue中的虚拟DOM,下面就来说一下Vue的虚拟DOM。

Vue会根据 data 中的数据生成虚拟DOM,如果是第一次生成页面,就将虚拟DOM转化成真实DOM,然后在浏览器页面展示出来。

虚拟DOM有啥用?每次Vue实例对象中 _data 内的数据发生改变,都会触发生成新的虚拟DOM,新的虚拟DOM会跟旧的虚拟DOM进行比较,如果有相同的虚拟DOM,在生成真实DOM时,这部分相同的虚拟DOM就不会再重新生成,只需要将两者之间不同的虚拟DOM转化成真实DOM,再与原来的真实DOM(就是两者比较之后,相同的真实DOM)进行拼接,我的理解是虚拟DOM就是起到了一个DOM复用的作用,还有避免重复多余的操作,下面有详细解释。

而key有啥用呢?

key是虚拟DOM的标识。

那啥是真实DOM?真实DOM和虚拟DOM有什么区别?如何使用代码展现真实DOM和虚拟DOM?下面我们来了解一下:

真实DOM和其解析流程

这里参考我是你的超级英雄大佬的文章:https://juejin.cn/post/6844903895467032589

webkit 渲染引擎工作流程图:

中文版:

所有的浏览器渲染引擎工作流程大致分为5步:创建DOM树(DOM Tree)—> 创建CSS规则树(Style Rules)-> 构建渲染树(Render Tree)—> 布局(Layout)—> 绘制(Painting)。

  • 第一步,构建DOM树(DOM Tree:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到。

  • 第二步,生成样式表(Style Rules:用CSS分析器,分析CSS文件和元素上的 inline 样式,生成页面的样式表。

    渲染阻塞:当浏览器遇到一个script标签时,DOM构建将暂停,直到脚本加载执行,然后继续构建DOM树。每次去执行JavaScript脚本都会严重阻塞DOM树构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM没有下载和构建,那么浏览器甚至会延迟脚本执行和构建DOM,直到这个CSSOM的下载和构建。所以,script标签引入很重要,实际使用时可以遵循下面两个原则:

    • CSS优先:引入顺序上,CSS资源先于JS资源。
    • JS后置:JS代码放在底部,且JS应尽量少影响DOM构建。
      • 还有一个小知识:当解析HTML时,会把新来的元素插入DOM树里,同时去查找CSS,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序匹配的例如:div p {…},会先寻找所有p标签并判断它的父标签是否为div之后才决定要不要采用这个样式渲染。所以平时写CSS尽量用class或者id,不要过度层叠。
  • 第三步,构建渲染树(Render Tree:通过DOM树和CSS规则我们可以构建渲染树。浏览器会从DOM树根节点开始遍历每个可见节点**(注意是可见节点)**对每个可见节点,找到其适配的CSS规则并应用。渲染树构建完后,每个节点都是可见节点并且都含有其内容和对应的规则的样式。这也是渲染树和DOM树最大的区别所在。渲染是用于显示,那些不可见的元素就不会在这棵树出现了。除此以外,display none 的元素也不会被显示在这棵树里。visibility hidden 的元素会出现在这棵树里。

  • 第四步,渲染布局(Layout:布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

  • 第五步,渲染树绘制(Painting:在绘制阶段,遍历渲染树,调用渲染器的 paint() 方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

注意点:

1、DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。

2、Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。

3、CSS 的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。

4、JS 操作真实 DOM 的代价? 传统DOM结构操作方式对性能的影响很大,原因是频繁操作DOM结构操作会引起页面的重排(reflow)和重绘(repaint),浏览器不得不频繁地计算布局,重新排列和绘制页面元素,导致浏览器产生巨大的性能开销。直接操作真实DOM的性能特别差,我们可以来演示一遍。

示例代码:

<div id="app"></div>
<script>// 获取 DIV 元素let box = document.querySelector('#app');console.log(box);// 真实 DOM 操作console.time('a');for (let i = 0; i <= 10000; i++) {box.innerHTML = i;}console.timeEnd('a');// 虚拟 DOM 操作let num = 0;console.time('b');for (let i = 0; i <= 10000; i++) {num = i;}box.innerHTML = num;console.timeEnd('b');
</script>

实现效果:

从上图可以看出,操作真实DOM用时 57.77099609375 ms,操作虚拟DOM用时 0.116943359375 ms,由此可知,操作真实 DOM 的性能是非常差的,所以我们要尽可能的复用,减少 DOM 操作。

虚拟 DOM 的好处

虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attchDOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。

​ 虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。

​ 回到最开始的问题,虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tagpropschildren 三个属性。

接下来我们手动实现下 虚拟 DOM。

分两种实现方式:

一种原生 js DOM 操作实现。

另一种主流虚拟 DOM 库(snabbdomvirtual-dom)的实现(用h函数渲染)(暂时还不理解)

算法实现

(1) 用 JS 对象模拟 DOM 树:

<div id="virtual-dom"><p>Virtual DOM</p><ul id="list"><li class="item">Item 1</li><li class="item">Item 2</li><li class="item">Item 3</li></ul><div>Hello World</div>
</div>

我们用 JavaScript 对象来表示 DOM 节点,使用对象的属性记录节点的类型、属性、子节点等。

/*** Element virdual-dom 对象定义* @param {String} tagName - dom 元素名称* @param {Object} props - dom 属性* @param {Array<Element|String>} - 子节点*/
function Element(tagName, props, children) {this.tagName = tagName;this.props = props;this.children = children;// dom 元素的 key 值,用作唯一标识符if (props.key) {this.key = props.key}
}
function el(tagName, props, children) {return new Element(tagName, props, children);
}

构建虚拟的 DOM ,用 javascript 对象来表示

let ul = el('div', { id: 'Virtual DOM' }, [el('p', {}, ['Virtual DOM']),el('ul', { id: 'list' }, [el('li', { class: 'item' }, ['Item 1']),el('li', { class: 'item' }, ['Item 2']),el('li', { class: 'item' }, ['Item 3'])]),el('div', {}, ['Hello, World'])
])

现在 ul 就是我们用 JavaScript 对象表示的 DOM 结构,我们输出查看 ul 对应的数据结构如下:

(2) 将用 js 对象表示的虚拟 DOM 转换成真实 DOM:需要用到 js 原生操作 DOM 的方法。

/*** render 将virdual-dom 对象渲染为实际 DOM 元素*/
Element.prototype.render = function () {// 创建节点let el = document.createElement(this.tagName);let props = this.props;// 设置节点的 DOM 属性for (let propName in props) {let propValue = props[propName];el.setAttribute(propName, propValue)}let children = this.children || []for (let child of children) {let childEl = (child instanceof Element)? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点: document.createTextNode(child) // 如果是文本,就构建文本节点el.appendChild(childEl);}return el;
}

我们通过查看以上 render 方法,会根据 tagName 构建一个真正的 DOM 节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。

我们将构建好的 DOM 结构添加到页面 body 上面,如下:

let ulRoot = ul.render();
document.body.appendChild(ulRoot);

这样,页面 body 里面就有真正的 DOM 结构,效果如下图所示:

我们知道虚拟 DOM 的好处和虚拟 DOM 的实现后就要讲讲 key 的作用了。

贴一下上面实现地完整代码:

<div id="virtual-dom"><p>虚拟 DOM</p><ul id="list"><li class="item">项目 1</li><li class="item">项目 2</li><li class="item">项目 3</li></ul><div>Hello World</div>
</div><script>let ul = el('div', { id: 'Virtual DOM' }, [el('p', {}, ['虚拟 DOM']),el('ul', { id: 'list' }, [el('li', { class: 'item' }, ['项目 1']),el('li', { class: 'item' }, ['项目 2']),el('li', { class: 'item' }, ['项目 3'])]),el('div', {}, ['Hello, World'])])/**Element virdual - dom 对象定义@param { String } tagName - dom 元素名称@param { Object } props - dom 属性@param { Array < Element | String >} - 子节点*/function Element(tagName, props, children) {this.tagName = tagName;this.props = props;this.children = children;// dom 元素的 key 值,用作唯一标识符if (props.key) {this.key = props.key}}function el(tagName, props, children) {return new Element(tagName, props, children);}/*** render 将virdual-dom 对象渲染为实际 DOM 元素*/Element.prototype.render = function () {// 创建节点let el = document.createElement(this.tagName);let props = this.props;// 设置节点的 DOM 属性for (let propName in props) {let propValue = props[propName];el.setAttribute(propName, propValue)}let children = this.children || []for (let child of children) {let childEl = (child instanceof Element)? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点: document.createTextNode(child) // 如果是文本,就构建文本节点el.appendChild(childEl);}return el;}let ulRoot = ul.render();document.body.appendChild(ulRoot);console.log(ul)
</script>
虚拟DOM中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:

    • ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM。
    • ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
  • 旧虚拟DOM中未找到与新虚拟DOM相同的key:
    • 创建新的真实DOM,随后渲染到到页面。

好了,我们知道了最简单的key的原理,如果要继续研究下去就要涉及到vue的核心之一-Diff算法,后面会详细介绍。

用index作为key可能会引发的问题:

若对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

示例代码:

<div id="app"><h2>人员列表</h2><button @click.once="add()">添加一个蛮王</button><ul><li v-for="(person,index) in persons" :key="index">{{index}}--{{person.name}}--{{person.age}}<input type="text" name="" id=""></li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {persons: [{ id: 1, name: '盖伦', age: 18 },{ id: 2, name: '嘉文', age: 19 },{ id: 3, name: '赵信', age: 20 },],}},methods: {add() {let p = { id: 4, name: '蛮王', age: 21 };this.persons.unshift(p);}},})
</script>

解释:

初始数据:

persons: [{ id: 1, name: '盖伦', age: 18 },{ id: 2, name: '嘉文', age: 19 },{ id: 3, name: '赵信', age: 20 },
],

vue根据数据生成虚拟 DOM:

// 初始虚拟DOM
<li key='0'>盖伦-18<input type="text"></li>
<li key='1'>嘉文-19<input type="text"></li>
<li key='2'>赵信-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM:

this.persons.unshift({ id: 4, name: '蛮王', age: 21 })

persons 数组最前面添加上 { id: 4, name: '蛮王', age: 21 }

新数据:

persons: [{ id: 4, name: '蛮王', age: 21 },{ id: 1, name: '盖伦', age: 18 },{ id: 2, name: '嘉文', age: 19 },{ id: 3, name: '赵信', age: 20 },
]

vue根据数据生成虚拟 DOM:

<li key='0'>蛮王-21<input type="text"></li>
<li key='1'>盖伦-18<input type="text"></li>
<li key='2'>嘉文-19<input type="text"></li>
<li key='3'>赵信-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM:

因为蛮王被逆序添加到第一个,重刷了 key 的值,Vue Diff算法 根据 key 的值判断 虚拟DOM全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,盖伦、嘉文、赵信并没有发生更改,是可以直接复用之前的 真实DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。

来张 尚硅谷 天禹老师的图(使用index作为key)

如果结构中还包含输入类的DOM:

会产生错误DOM更新 ==> 界面有问题。

这回造成的就不是性能浪费了,会直接导致页面的错误。

下图为输入后点击效果:

结论:

  • 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

来张 尚硅谷 天禹老师的图(使用id作为key)

面试题: react、vue中的key有什么作用?(key的内部原理)
  • 虚拟DOM中 key 的作用:key 是虚拟DOM中对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  • 对比规则:

    • 旧虚拟DOM中找到了与新虚拟DOM相同的 key

      • 若虚拟DOM中内容没变, 直接使用之前的真实DOM。
      • 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    • 旧虚拟DOM中未找到与新虚拟DOM相同的 key:创建新的真实DOM,随后渲染到到页面。
  • index 作为 key 可能会引发的问题:

    • 若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
    • 若结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
  • 开发中如何选择 key?

    • 最好使用每条数据的唯一标识作为 key,比如 id 、手机号、身份证号、学号等唯一值。
    • 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用 index 作为 key 是没有问题的。

12.3 列表过滤(模糊搜索)

示例代码:(用watch实现模糊搜索)

<div id="app"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><ul><li v-for="(person,index) in newPersons" :key="index">{{person.id}}-{{person.name}}-{{person.age}}-{{person.sex}}</li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。// 用watch实现模糊搜索new Vue({el: '#app',data() {return {keyWord: '',persons: [{ id: 1, name: '马冬梅', age: 18, sex: '女' },{ id: 2, name: '周冬雨', age: 19, sex: '女' },{ id: 3, name: '周杰伦', age: 20, sex: '男' },{ id: 4, name: '温兆伦', age: 21, sex: '男' },],newPersons: [],}},watch: {keyWord: {immediate:true,handler(val) {this.newPersons = this.persons.filter((person) => {return person.name.indexOf(val) !== -1;})}}}})
</script>

示例代码:(用computed实现模糊搜索)

<div id="app"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><ul><li v-for="(person,index) in newPersons" :key="index">{{person.id}}-{{person.name}}-{{person.age}}-{{person.sex}}</li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。// 用computed实现new Vue({el: '#app',data() {return {keyWord: '',persons: [{ id: 1, name: '马冬梅', age: 18, sex: '女' },{ id: 2, name: '周冬雨', age: 19, sex: '女' },{ id: 3, name: '周杰伦', age: 20, sex: '男' },{ id: 4, name: '温兆伦', age: 21, sex: '男' },],}},computed: {newPersons() {return this.persons.filter((person) => {return person.name.indexOf(this.keyWord) !== -1;})}}})
</script>

由上两个示例代码可见,用computed 比用 watch 代码更简洁,由此可得出,当 compputedwatch 都能实现的时候,优先使用 computed

12.4 列表排序(升序、降序、原顺序)

示例代码:

<div id="app"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><button @click="sortType = 2">年龄升序</button><button @click="sortType = 1">年龄降序</button><button @click="sortType = 0">原顺序</button><ul><li v-for="(person,index) in newPersons" :key="person.id">{{person.id}}-{{person.name}}-{{person.age}}-{{person.sex}}<input type="text" name="" id=""></li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。new Vue({el: '#app',data() {return {keyWord: '',sortType: 0, // 0原顺序 1降序 2升序persons: [{ id: 1, name: '马冬梅', age: 30, sex: '女' },{ id: 2, name: '周冬雨', age: 28, sex: '女' },{ id: 3, name: '周杰伦', age: 45, sex: '男' },{ id: 4, name: '温兆伦', age: 40, sex: '男' },],}},computed: {newPersons() {let arr = this.persons.filter((person) => {return person.name.indexOf(this.keyWord) !== -1;})// 判断一下是否需要排序if (this.sortType) {arr.sort((a, b) => {return this.sortType === 1 ? b.age - a.age : a.age - b.age;})}return arr;},}})
</script>

效果:(输入后点击年龄降序如下图)

**注意:**如果结构中还包含输入类(如上代码中 input 标签)的DOM,key 使用 index ,会产生错误DOM更新,从而导致页面的错误。所以 key 要使用唯一值(如 id 、手机号、身份证号、学号等)。

如果 key 使用 index,会产生如下效果:(输入后点击年龄降序如下图)

12.5 Vue监测 data 中的数据

示例代码:(先来个案例引入一下)

<div id="app"><h2>人员列表</h2><button @click="updateMei()">更新马冬梅的信息</button><ul><li v-for="(person,index) in persons" :key="index">{{index}}-{{person.name}}-{{person.age}}-{{person.sex}}</li></ul>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {persons: [{ id: 1, name: '马冬梅', age: 30, sex: '女' },{ id: 2, name: '周冬雨', age: 28, sex: '女' },{ id: 3, name: '周杰伦', age: 45, sex: '男' },{ id: 4, name: '温兆伦', age: 40, sex: '男' },],}},methods: {updateMei() {// this.persons[0].name = '马老师' //奏效// this.persons[0].age = 60 //奏效// this.persons[0].sex = '男' //奏效this.persons[0] = { id: 1, name: '马老师', age: 60, sex: '男' } //不奏效// this.persons.splice(0, 1, { id: 1, name: '马老师', age: 60, sex: '男' })}}})
</script>

点击更新马冬梅的信息,马冬梅的数据并没有发生改变。

我们来看看浏览器的控制台:

控制台上的数据发生了改变,说明,这个更改的数据并没有被 Vue 监测到。

所以我们来研究一下 Vue 监测的原理。

我们先研究 Vue 如何监测对象里的数据。

示例代码:

<div id="app"><h2>网站名称:{{name}}</h2><h2>网站地址:{{address}}</h2>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {name: '百度',address: 'http://wwww.baidu.com',student: {name: '赵信',age: {rAge: 40,sAge: 29,},friends: [{ name: '嘉文', age: 35 }]}}},})
</script>

Vue监测数据改变的原理就是通过调用 setter 对页面重新解析,所以需要对 data 进行加工处理,从 data 变成 vm._data 大概是经过了下图中的步骤:

流程示意图:

重新渲染Vue模板背后的操作:1.调用 set 方法时,就会去解析模板 ------> 2.生成新的虚拟 DOM ------> 3.新旧 DOM 之间做对比 ------> 4.重新渲染页面。


模拟一下 Vue 中的数据监测:

示例代码:

<script type="text/javascript">let data = {name: '百度',address: 'http://wwww.baidu.com',}// 创建一个监视的实例对象,用于监视data中属性的变化const obs = new Observer(data)console.log(obs);// 准备一个vm实例对象let vm = {}vm._data = data = obsfunction Observer(obj) {// 汇总对象中所有的属性形成一个数组const keys = Object.keys(obj)// 遍历keys.forEach(k => {Object.defineProperty(this, k, {get() {return obj[k]},set(value) {console.log(`${k}被改了,我要去解析模板,生成虚拟DOM......我要开始忙了`);obj[k] = value}})})}
</script>

效果:

模拟的数据监测和 Vue 相比的缺陷:
1.只能实现一层对象的 gettersetter,而 Vue 可以实现只要是对象,都会有 gettersetter
2.如上图所示,只能这样修改数据 vm._data.name='新浪',而 Vue 可以 vm._data.name='新浪' 或者 vm.name='新浪' 来达到修改数据。

Vue.set()方法的使用(Vue.$set)

官网Vue.set()的使用链接:Vue API :Vue.set()的使用

写法:

Vue.set(target,propertyName/index,value)vm.$set(target,propertyName/index,value)

参数:

  • target:可以是对象或数组。
  • propertyName/index:可以是字符串或数字。
  • value:什么值都可以。

用法

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')

示例代码:(Vue监测对象中的数据)

<div id="app"><h1>学生信息</h1><button @click="addSex">点击添加爱好属性和性别属性,性别默认值是男</button><h2>姓名:{{student.name}}</h2><h2 v-if="student.sex">性别:{{student.sex}}</h2><h2>年龄:{{student.age}}</h2><h2>爱好:{{student.hobby}}</h2>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {student: {name: '盖伦',age: 40,},}},methods: {addSex() {// Vue.set(this.student,'sex','男')this.$set(this.student, 'sex', '男')// Vue.set(this.student,'hobby','抽烟')this.$set(this.student, 'hobby', '抽烟')},}})
</script>

注意:Vue.set()vm.$set() 有缺陷。

意思就是说,不能在 Vue实例对象 或 Vue实例对象 的根数据对象(就是Vue实例对象下的 _data )添加属性。如上代码中和 student 同级是不能添加的,只能在student中的下一级添加,即 student 中的 nameage 平级,或者下下级,下下下级…都能添加。

示例代码:

// 其它代码省略....
data() {return {aaa:{ }, // aaa在student同级,aaa不能添加student: {name: '盖伦',age: 40,bbb:{ // bbb在name和age同级,bbb可以添加// ccc和ddd在bbb的下一级,ccc和ddd可以添加,一直向下都可以添加ccc:'xxx',ddd:{ } }},}
},

了解完了Vue监测对象中的数据,再来看看Vue如何监测数组里的数据。

示例代码:(Vue监测数组中的数据)

<div id="app"><h2>爱好:</h2><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul>
</div><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {student: {hobby: ['抽烟', '喝酒', '烫头'],}}},methods: {}})
</script>

所以通过以下代码改不奏效:

vm._data.student.hobby[0] = 'aaa'  //不奏效

Vue监测在数组那没有 gettersetter,所以监测不到数据的更改,也不会引起页面的更新。

效果图:

那Vue是如何对数组的数据进行监视的呢?

Vue对数组的监测是通过包装数组上常用的用于修改数组的方法来实现的。

官网链接:变更方法

push() // 向数组的末尾添加一个或更多元素,并返回新的长度。
pop() // 删除数组的最后一个元素并返回删除的元素。
shift() // 删除并返回数组的第一个元素。
unshift() // 向数组的开头添加一个或更多元素,并返回新的长度。
splice() // 从数组中添加、替换或删除元素。
sort() // 对数组的元素进行排序。
reverse() // 反转数组的元素顺序。

如果对JS数组方法不是很熟悉的可以点击链接看下:JavaScript Array 对象方法

来,我们试一下效果:

总结:(Vue监测数据的原理)

Vue监视数据的原理:

  • Vue会监视 data 中所有层次的数据。

  • 如何监测对象中的数据?

    通过 setter 实现监视,且要在new Vue时就传入要监测的数据。

    • 对象中后追加的属性,Vue默认不做响应式处理。

    • 如需给后添加的属性做响应式,请使用如下API:

      Vue.set(target,propertyName/index,value)

      vm.$set(target,propertyName/index,value)

  • 如何监测数组中的数据?

    通过包裹数组更新元素的方法实现,本质就是做了两件事:

    • 调用原生对应的方法对数组进行更新。
    • 重新解析模板,进而更新页面。
  • 在Vue修改数组中的某个元素一定要用如下方法:

    • 使用这些API:push()pop()shift()unshift()splice()sort()reverse()
    • Vue.set()vm.$set()

特别注意:Vue.set()vm.$set() 不能给 vm 或 vm的根数据对象 添加属性!!!

示例代码:

<!-- 准备好一个容器-->
<div id="app"><button @click="student.age++">年龄+1岁</button><br><button @click="addSex">添加性别属性,默认值:男</button><br><button @click="student.sex = '未知'">修改性别</button><br><button @click="addFiend">在列表首位添加一个朋友</button><br><button @click="updateFirstFriendName">修改第一个朋友的名字为:蛮王</button><br><button @click="addHobby">添加一个爱好</button><br><button @click="updateHobby">修改第一个爱好为:打LOL</button><br><button @click="removeSmoke">过滤掉爱好中的喝酒</button><br><h1>学生信息</h1><h2>姓名:{{student.name}}</h2><h2>年龄:{{student.age}}</h2><h2>性别:{{student.sex}}</h2><h2>爱好:</h2><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul><h3>朋友们</h3><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}}----{{f.age}}</li></ul>
</div><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {student: {name: '盖伦',age: 20,hobby: ['抽烟', '喝酒', '烫头'],friends: [{ name: '赵信', age: 25 },{ name: '嘉文', age: 30 },]}}},methods: {addSex() {// Vue.set(this.student, 'sex', '男')this.$set(this.student, 'sex', '男')},addFiend() {this.student.friends.unshift({ name: '光辉', age: 18 })},updateFirstFriendName() {this.student.friends[0].name = '蛮王'// this.student.friends[0].age = 70},addHobby() {this.student.hobby.push('打篮球')},updateHobby() {// this.student.hobby.splice(0, 1, '打LOL')// Vue.set(this.student.hobby, 0, '打LOL')this.$set(this.student.hobby, 0, '打LOL')},removeSmoke() {this.student.hobby = this.student.hobby.filter(h => h !== '喝酒')}}})
</script>

13. 收集表单数据

若:<input type="text"/>,则 v-model 收集的是 value 值,用户输入的内容就是 value 值。(如:输入账号、密码、年龄…)

示例代码:

<div id="app"><form @submit.prevent="demo">账号:<input type="text" v-model.trim="userInfo.account"> <br /><br />密码:<input type="password" v-model="userInfo.password"> <br /><br />年龄:<input type="number" v-model.number="userInfo.age"> <br /><br /><button>提交</button></form>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {userInfo: {account: '',password: '',age: 18,}}},methods: {demo() {console.log(JSON.stringify(this.userInfo));}}})
</script>

若:<input type="radio"/>,则 v-model 收集的是 value 值,且要给标签配置 value 属性。(如:单选框)

示例代码:

<div id="app"><form @submit.prevent="demo">性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male">女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br /><br /><button>提交</button></form>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {userInfo: {sex: 'male',}}},methods: {demo() {console.log(JSON.stringify(this.userInfo));}}})
</script>

若:<input type="checkbox"/>

  • 没有配置 value 属性,那么收集的是 checked 属性(勾选 or 未勾选,是布尔值)(如:只有一个多选框)
  • 配置了 value 属性:
    • v-model 的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    • v-model 的初始值是数组,那么收集的就是 value 组成的数组。(如:多个多选框)

示例代码:

<div id="app"><form @submit.prevent="demo">爱好:抽烟<input type="checkbox" v-model="userInfo.hobby" value="smoking">喝酒<input type="checkbox" v-model="userInfo.hobby" value="drink">烫头<input type="checkbox" v-model="userInfo.hobby" value="perm"> <br /><br />地址:<select v-model="userInfo.city"><option value="">请选择地址</option><option value="beijing">北京</option><option value="sahnghai">上海</option><option value="chengdu">成都</option></select><br /><br />其它信息:<textarea v-model.lazy="userInfo.other"></textarea> <br /><br /><input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.beidu.com">《用户协议》</a> <br /><br /><button>提交</button></form>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {userInfo: {hobby: [],city: 'chengdu',other: '',agree: '',}}},methods: {demo() {console.log(JSON.stringify(this.userInfo));}}})
</script>

效果图:

备注:

  • v-model 的三个修饰符:

    • lazy :失去焦点后再收集数据。
    • number :输入字符串转为有效的数字。
    • trim :输入首尾空格过滤。

示例代码:(汇总如上表单所有代码)

<div id="app"><form @submit.prevent="demo">账号:<input type="text" v-model.trim="userInfo.account"> <br /><br />密码:<input type="password" v-model.trim="userInfo.password"> <br /><br />年龄:<input type="number" v-model.number="userInfo.age"> <br /><br />性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male">女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br /><br />爱好:抽烟<input type="checkbox" v-model="userInfo.hobby" value="smoking">喝酒<input type="checkbox" v-model="userInfo.hobby" value="drink">烫头<input type="checkbox" v-model="userInfo.hobby" value="perm"> <br /><br />地址:<select v-model="userInfo.city"><option value="">请选择地址</option><option value="beijing">北京</option><option value="sahnghai">上海</option><option value="chengdu">成都</option></select><br /><br />其它信息:<textarea v-model.lazy="userInfo.other"></textarea> <br /><br /><input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.beidu.com">《用户协议》</a> <br /><br /><button>提交</button></form>
</div><script>Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#app',data() {return {userInfo: {account: '',password: '',age: 18,sex: 'male',hobby: [],city: 'chengdu',other: '',agree: '',}}},methods: {demo() {console.log(JSON.stringify(this.userInfo));}}})
</script>

持续更新中

【Vue】基础(三)条件渲染 - 列表渲染(key的作用与原理虚拟DOM解析) - 收集表单数据 - 持续更新中相关推荐

  1. vue.js循环for(列表渲染)详解

    vue.js循环for(列表渲染)详解 一.总结 一句话总结: v-for <ul id="example-1"> <li v-for="item in ...

  2. 前端自学Vue笔记干货(第一版,持续更新中~~~)

    学习笔记 Vue笔记 nprogress使用 npm i nprogress -S 基本上都是在对axios进行二次封装.前置守卫路由或者封装成工具函数的.js文件中用到 import nprogre ...

  3. vue通用后台管理系统(保姆级)--持续更新中

    配合目录使用更加友好哦,文章中分享的项目搭建是完全从0-1搭建,完全适用于小白,可用于vue练手项目,目前还在持续更新中,本篇文章不会断更,因工作原因,只能晚上给大家更新,感觉还行的可以给个关注或者收 ...

  4. 【Vue全家桶+SSR+Koa2全栈开发】项目搭建过程 整合 学习目录(持续更新中)

    写在开头 大家好,这里是lionLoveVue,基础知识决定了编程思维,学如逆水行舟,不进则退.金三银四,为了面试也还在慢慢积累知识,Github上面可以直接查看所有前端知识点梳理,github传送门 ...

  5. webpack1.x环境配置与打包基础【附带各种 “坑“ 与解决方案!持续更新中...】

    webpack1.x环境配置与打包基础[附带各种 "坑" 与解决方案!持续更新中...] 参考文章: (1)webpack1.x环境配置与打包基础[附带各种 "坑&quo ...

  6. 最好的Vue组件库之Vuetify的入坑指南(持续更新中)

    目录 安装Vuetify 文档结构 快速入门 特性 样式和动画 首先先声明,个人不是什么很牛逼的大佬,只是想向那些想入坑Vuetify的前端新手或者嫌文档太长不知如何入手的人提供一些浅显的建议而已,能 ...

  7. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?

    既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异? 考点: Vue的变化侦测原理 前置知识: 依赖收集.虚拟DOM.响应式系统 现代前端框架有两种方式侦测变化,一 ...

  8. 26.Vue列表渲染中key的作用与原理(内含虚拟DOM的对比算法详解)

    目录 1.暴露问题,使用index作为key 2.使用唯一标识p.id作为key 3.不写key的配置 4.key的工作原理及虚拟DOM的对比算法 5.总结 25.Vue列表渲染_爱米酱的博客-CSD ...

  9. React条件渲染列表渲染

    一.React条件渲染 某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容: 在vue中,我们会通过指令来控制:比如v-if.v-show: 在React中,所有的条件判 ...

最新文章

  1. 自然语言处理的现实应用
  2. Kafka整体结构图、Consumer与topic关系、Kafka消息分发、Consumer的负载均衡、Kafka文件存储机制、Kafka partition segment等(来自学习资料)
  3. 计算机二级语义网络的研究现状与展望,计算机二级access选择题题库研究.doc
  4. 【POJ - 1456】Supermarket (贪心,优先队列 或并查集)
  5. 致力于绿色环保的美国大型数据中心
  6. 战胜棋王后,人工智能是否可以颠覆安全?
  7. ATA和ATAPI类型硬盘区别方法
  8. Liberal Arts:志存高远
  9. 文件创建失败 无法继续下载_iOS更新失败了怎么办?教你如何排除故障解决问题...
  10. c# meiju(摘)
  11. 解决win10系统飞秋不在线问题
  12. python单词表首字母排序_python3 列表排序(字母顺序排序、字母相反顺序排序和倒序)...
  13. 【区块链108将】千方基金点付大头:投资区块链,不要让过往认知限制你的想象...
  14. 江西“葫芦夫妻”的“甜蜜”事业
  15. 人工智能技术对我们的生活,有多少影响?
  16. php使用QQ登录API,QQ的账号登录及api操作
  17. SparkSteaming实时接收网络端口数据和hdfs做Wordcount
  18. 民航导航技术发展及北斗应用分析
  19. Tello无人机版之使用Scratch2和ROS进行机器人图形化编程学习
  20. 2017全国大学生电子设计竞赛个人总结--B题滚球控制系统

热门文章

  1. filebeat使用include_lines时,入库慢的解决方法
  2. 加拿大己亥猪年生肖邮票亮相 选用猪八戒形象
  3. DTOS帝拓思的3D引擎将取代游戏引擎巨兽,实现国产化替代
  4. Android bitmap裁剪任意形状为透明的png图片
  5. Android studio如何运行java程序代码
  6. font-family最佳字体设置
  7. selenum模块抓取网易云网页搜索结果,并拿到MP3地址
  8. discard long time none received connection
  9. Mac下JDK、Maven、Tomcat、Git开发安装及环境变量配置
  10. JDK1.8_API 中文 翻译版,百度云免费下载