一、Vue实例
new Vue(options)
每个 Vue 实例与容器一一对应,并且在真实开发中往往只有一个 Vue 实例,一个 Vue 实例配合对个 组件实例 使用,可以将 Vue 实例称为 vm ( MVVM中的VM ),组件实例称为 vc ( VueComponent )
1.Vue 工作准备
- 引入 Vue
- new Vue(options) 创建实例,options 是一个配置对象
2. options 配置对象
-
el (挂载点)
- ‘#root’,挂载的容器的 css 选择器
- doucment.querySelect(’#root’),挂载的容器 dom 元素
注意: 在创建实例时可以不设定 el 属性,可以通过手动调用 Vue 提供的一个 api 方法 vm.$mount(el),传入 el 的值,并手动挂载。若在配置项中添加 el 属性,在 Vue 底层实现中会帮我们调用这个方法
-
data 数据对象
-
对象式
-
函数式
比较:
-
对象式的 data 属性在组件复用时可能会出现数据之间存在引用关系的情况;
// Student export default { data: { name: '小王', age:18 }, methods: { changeAge() { this.age = 20 } } } // Course1 import student from Student student.methods.changeAge = 20 // Course2 import student from Student console.log(student.data.age) // 20
如上述代码,在其他组件引入同一个组件时,如果 data 属性采用的是对象式的方式,则引入该组件的其他组件获得的是同一个 data 对象,因此就可能因为引用关系造成数据的污染。
**注意:**之所以其他组件引入该组件时获取是同一个引用,是因为 ES6 语法的 import 拿到的其实是一个只读引用(类似于 Linux 中的 ‘符号链接’),每当模块中的原始值改变时 import 加载的值也会随之改变。
-
函数式的 data 属性,在 Vue 底层获取配置对象上用户定义的数据时会调用 data 函数,并且每次调用 data 函数返回的都是一个新的拷贝
// Student export default { data(){ return { name: '小王', age:18 } }, methods: { changeAge() { this.age = 20 } } } // Course1 import student from Student student.methods.changeAge = 20 // Course2 import student from Student console.log(student.data.age) // 18
如上述代码,在其他组件引入同一个组件时,如果 data 属性采用的是函数式的方式,则引入该组件的其他组件获得的是 data 对象的一个拷贝(深拷贝),因此不会出现因为复用组件导致数据污染的情况。
-
3. Vue 中的 this 指向
-
在 Vue 中,只要是有 Vue 管理的函数都不能写成箭头函数(箭头函数的特性),需要写成普通函数的形式;
-
因为 Vue 中默认帮我们绑定了函数的 this 指向为当前实例对象,为了访问到定义在配置对象上的属性和方法,需要 this 指向为当前的实例对象。
-
所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
注意: 实例对象分为根实例对象和组件实例对象,根实例对象是 new Vue() 得到的实例对象,而组件实例对象是我们定义配置属性或调用 Vue.extend 这个 api 后并’注册使用’而得到的,详情见后面的非单文件组件与单文件组件。
二、指令
v-bind:简写:’:’
单向数据绑定,数据只能从 data 流向页面;
-
扩展
-
绑定样式
-
绑定 class
写法 :class=“xxx” xxx 可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
-
绑定 style
写法 :style="{fontSize: xxx}" 其中 xxx 是动态值。
:style="[a,b]" 其中a、b是样式对象。\
-
-
示例
<template> <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --> <div class="basic" :class="mood">{{name}}</div> <br/><br/> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 --> <div class="basic" :class="['atguigu1','atguigu2','atguigu3']">{{name}}</div> <br/><br/> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 --> <div class="basic" :class="{ atguigu1:false, atguigu2:false, }">{{name}}</div> <br/><br/> <!-- 绑定style样式--对象写法 --> <div class="basic" :style="{ fontSize: '40px', color:'red', }">{{name}}</div> <br/><br/> <!-- 绑定style样式--数组写法 --> <div class="basic" :style=":[ { fontSize: '40px', color:'blue', }, { backgroundColor:'gray' } ]">{{name}}</div> </template> <script> export default { data:{ name:'尚硅谷', mood:'normal', }, } </script> <style> .normal{ background-color: skyblue; } .atguigu1{ background-color: yellowgreen; } .atguigu2{ font-size: 30px; text-shadow:2px 2px 10px red; } .atguigu3{ border-radius: 20px; } </style>
-
v-model:
双向数据绑定,数据不仅能从data流向页面,还可以从页面流向data
-
双向数据绑定往往用在表单类元素上(input、form、select),因为往往只有表单类元素需要有数据的交互传递
-
v-model:value 简写形式为 v-model,因为 v-model 默认收集的为value 值
-
收集表单数据:
若:
<input type="text"/>
,则 v-model 收集的是 value 值,用户输入的就是 value 值。 若:
<input type="radio"/>
,则 v-model 收集的是 value 值,且要给标签配置 value 值。 若:
<input type="checkbox"/>
1.没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1) v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
(2) v-model 的初始值是数组,那么收集的的就是 value 组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
v-on:简写:’@’
-
methods 属性中定义的方法需写成普通函数的形式,否则 this 指向不为当前实例
-
@click=“demo($event, params)”,既想获取事件对象,又想传入参数的格式
-
事件修饰符
-
prevent:阻止默认事件(常用),如页面跳转、表单提交,原生采用 e.preventDefault()阻止 ;
-
stop:阻止事件冒泡(常用),原生采用 e.stopPropagation() 阻止;
-
once:事件只触发一次(常用);
-
capture:使用事件的捕获模式 ;
事件分为捕获、冒泡两阶段,即从元素外层到内层、内层到外层触发事件的两阶段。
-
self:只有 event.target 是当前操作的元素时才触发事件;
通过 self 也能实现 stop 能实现的功能,不过代码更为繁琐。
-
passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
如拖动滚动条的事件,一种是边拖动边触发事件,另一种是执行完之间回调再移动拖动。第二种事件如果不处理可能会导致页面卡死的情况。
-
-
键盘事件
- Vue中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right
-
Vue 未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为 kebab-case(短横线命名)
如, caps-lock(大小写切换)
-
系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
@ctrl.keyup.1
(2).配合 keydown 使用:正常触发事件。
-
也可以使用keyCode去指定具体的按键(不推荐)
因为不同浏览器所的 keyCode 规格可能不同,并且 keyCode 已经被废除,请采用 key|code
-
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
v-if、v-else-if、v-else:
不满足条件直接移除 dom 元素,三者可一起使用,但是结构不能 “打断” 必须连续
注意: template 标签仅能配合 v-if 使用,不能配合 v-show
v-show:
适用于切换频率较高场景,dom 元素未移除,仅仅是将 display:none
v-for:
- 用于展示列表数据
- 语法:v-for="(item, index) in xxx" :key=“yyy”
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
v-text:
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html;
-
作用:向指定节点中渲染包含html结构的内容。
-
与插值语法的区别:
- v-html会替换掉节点中所有的内容,{{xx}}则不会。
- .v-html可以识别html结构。
-
注意:v-html有安全性问题
- 在网站上动态渲染任意HTML是非常危险的,容易导致 XSS 攻击。
- 一定要在可信的内容上使用 v-html ,永不要用在用户提交的内容上!
v-cloak:
- 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性
- 使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题,即加载速度过慢。
v-once:
- v-once 所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
v-pre:
- 跳过其所在节点的编译过程。
- 可利用它跳过没有使用指令语法、没有使用插值语法的节点,会加快编译。
自定义指令
-
定义语法:
(1).局部指令:
new Vue({ directives:{ 指令名:配置对象 } })
new Vue({ directives{ 指令名:回调函数 } })
(2).全局指令:
Vue.directive ( 指令名,配置对象 ) 、Vue.directive ( 指令名,回调函数 )
- 配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
- 备注:
- 指令定义时不加v-,但使用时要加v-;
- 指令名如果是多个单词,要使用连字符命名方式,不要用小驼峰命名。
三、计算属性和侦听属性
(1)、 计算属性
写法:
- computedName() { }
- computedName:{ get(){ }, set(){ } }
**原理:**底层运用了 Object.defineProperty 方法定义 getter/settet
执行时机:
- 初始读取计算属性时执行一次
- 依赖数据发生改变时执行
优点: 与 methods 相比,内部有缓存机制,当模板上多次复用时,效率更高
**注意:**如果计算属性要被修改,那必须编写 setter 去响应修改,且 set 中要引起计算时依赖的数据发生改变。
(2)、侦听属性
写法:
- watchedPropertyName(){ }
- watchedPropertyName: { handler(){ }, immediate, deep }
**通过api 定义:**vm.$watch(watchedPropertyName, function | option)
执行时机:
被侦听的属性变化时, 回调函数自动调用;如果 immediate 属性值为 true ,则在初次解析模板时也会调用
注意: 被侦听的属性必须存在,才能进行侦听 ( 编写时注意属性名 ) ;
如果侦听一个对象中的某个属性 ( 即侦听多级结构中某个属性的变化 ),采用 ‘对象名.属性名’ 的格式;
计算属性能实现的,用侦听属性也一定能实现;
watch 能完成的功能,computed 不一定能完成。例如:watch 可以进行异步操作,而 computed 不能异步操作,
因为 computed 必须有返回值,并不能实现等待 500ms 再返回这样的操作,因为返回的值并不是在最外层的函数返回。
四、生命周期
又名:生命周期回调函数、生命周期函数、生命周期钩子。
常用的生命周期钩子:
1.mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助 Vue 开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。
五、非单文件组件与单文件组件
- 组件注册三大步骤;
- 创建组件
- 注册组件(注册前需引入)
- 使用组件
非单文件组件
通常来说,在一个 html 文件中创建多个组件并引入,这就是一个非单文件组件。
创建组件:
如果需要用命令式的方法创建组件可以通过 Vue 提供的一个 API 创建组件 Vue.extend(options)
也可以直接编写一个配置对象,在 Vue 解析模板时会自动调用 Vue.extend 并自动 new 实例
Vue.extend
/**
* extend 会生成一个子组件的构造函数,并且每次调用时生成的构造函数都是不同的
*/
Vue.extend = function (extendOptions: Object): Function {
......
const Sub = function VueComponent (options) { // 构造子组件的构造函数
this._init(options) // 一旦构造实例,就开始进行像根实例root一样的初始化流程
}
// 子组件继承 Vue,因此子组件能够访问到 Vue原型 上的属性和方法
// 即 Sub.prototype.__proto__ === Vue.prototype
// console.log(Sub.prototype.__proto__ === Vue.prototype) // false
Sub.prototype = Object.create(Super.prototype)
// console.log(Sub.prototype.__proto__ === Vue.prototype) // false
Sub.prototype.constructor = Sub // 让 constructor 指向构造函数
Sub.cid = cid++ // 构造函数的编号
Sub.options = mergeOptions( // 合并父子组件的选项
Super.options,
extendOptions
)
Sub['super'] = Super // 标记子组件的父亲
......
return Sub
}
从上述代码可以发现一个重要的内置关系:VueComponent.prototype._proto_ === Vue.prototype
这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
VueComponent:
1. 通过 Vue.extend 创建的组件,本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是Vue.extend 生成的。
2. 我们只需要写<school/>
或<school></school>
,Vue 解析时会帮我们创建各个组件的实例对象, 即 Vue 帮我们执行 new VueComponent(options) 这条语句 。
3. 特别注意:每次调用 Vue.extend ,返回的都是一个全新的 VueComponent
4.this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5. VueComponent 的实例对象,简称vc(也可称之为:组件实例对象)。
6.如果只是在实例上注册组件,但是在模板中并未使用,则该实例所管理的组件中,并不会存在仅注册未使用的组件
new Vue(options) 和 Vue.extend(options) 的 options 区别:
1、el不要写 —— 最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器。
2、data必须写成函数 ——— 避免组件被复用时,数据存在引用关系。
注册组件:
在注册前需要通过 import 引入组件
- 局部注册:在 options 中配置 components 属性,components: { 组件 }
- 全局注册:Vue.component(‘组件名’, 组件)
注意事项:
-
组件名
- 一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
-
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
-
注意点:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以在配置对象中使用 name属性指定组件在开发者工具中呈现的名字。
单文件组件
每一个 .vue 文件都是一个单文件组件;
在实际开发中,往往会创建一个名为 App 的组件来管理所有的其他组件,该组件仅下属于 Root 组件;并且其他子组件都有 App 组件管理
一个单文件组件包括三大部分: template (模板)、script (脚本) 、 style (样式)
// Demo,一般来说单文件组件的组件名采用大驼峰是更为直观
<template>
<div>
// html 结构
</div>
</template>
<script>
// 这里使用默认导出,在导入的使用便可以直接通过 import 组件名 from 'path' 的方法,这样的方式比分别导出和统一导出都需要多加一个 {} 更为麻烦,不过也可行
// 我们通过默认导出,导出一个配置对象, Vue 在底层会将这个 option 传入 Vue.extend(opitons) 得到一个 VueCompoment 构造函数,并在解析模板时自动调用创建组件实例
// 当然也可以直接默认导出一个构造函数 export default Vue.extend(options)
export default {
name: 'demo', // 通过这个名字能控制 Vue 工具中显示的名字
}
</script>
<style>
// 样式
</style>
六、原理
1. 数据代理
定义:通过一个对象代理对另一个对象中属性的(读/写)操作
- Vue中的数据代理:
通过 vm 对象来代理 data 对象中属性的(读/写)操作
- Vue中数据代理的好处:
更加方便的操作 data 中的数据
- 基本原理:
通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上。
为每一个添加到 vm 上的属性,都指定一个 getter/setter , getter/setter 其实在定义响应式数据时就已创建。
Vue 内部代码中的数据代理只是对已创建的 getter/setter 的包装和定义。
Object.defineProterty
// Object.defineProterty(targetObj, 'protertyName', descriptor)
// 具体应用
let student = {}
Object.defineProterty(student, 'age', {
value: 18,
enumerable: true, // 属性是否可迭代(for of 遍历,keys取键),注意:该值为默认值(false)时在浏览器中会比其他属性颜色浅(chrome为浅粉色),表明不可迭代
writable: true, // 属性是否可修改
configurable: true, // 属性是否可删除
})
// Object.defineProterty(targetObj, 'protertyName', descriptor)
// 具体应用
let number = 19
let student = {}
Object.defineProterty(student, 'age', {
get() { // student.age 时调用(获取属性值)
return number
},
set(v) { // student.age = 18修改属性值时调用
console.log(`age修改${v}`)
number = v // 注意这里修改的是number而不是age,age并不存在,age属性只是代理了number
}
})
console(student.age) // 19
student.age = 20
console(studen.age, number) // 20,20
通过Object.defineDescriptor定义属性的getter和setter进行数据拦截,Vue2的响应式数据代理底层就是通过这个方法进行拦截。
proxy
/**
* 实现代理的函数,包装(调用已有的 getter/setter 方法)属性的 getter/setter 方法
* 通过 proxy 用户可以直接通过 this.属性名 的方式
* 获取到在 data/props 定义的相应属性的属性值
* 在目标对象上代理属性
* @param {*} target
* @param {*} sourceKey
* @param {*} key
*/
export function proxy (target: Object, sourceKey: string, key: string) {
// 添加 getter 和 setter,实质上这里的 getter 是复用了创建响应式数据时的 getter 和 setter
sharedPropertyDefinition.get = function proxyGetter () {
// initProps 时传入的是 _props ,则这条语句相当于target['_props'][key]
// initData 时传入的是 _data ,则这条语句相当于target['_data'][key]
// 通过这样的方式调用已经创建的 getter
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
// 通过这样的方式调用已经创建的 setter
this[sourceKey][key] = val
}
// 定义该属性到 vue 实例上
Object.defineProperty(target, key, sharedPropertyDefinition)
}
2.数据监测
- vue会监视data中所有层次的数据。
- 如何监测对象中的数据?
通过 setter 实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue 默认不做响应式处理
也就是用户直接通过 Object.defineProperty 创建的属性
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 、vm.$set(target,propertyName/index,value)
defineReactive
function defineReactive (obj: Object, key: string,val: any, customSetter?: Function) {
// 创建一个新的依赖项
const dep = new Dep()
// Object.getOwnPropertyDescriptor获取一个对象上某个属性的描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// configurable为true表明该属性描述符可改变,并且该属性可删除
// 如果该属性不能配置(删除等)则退出
if (property && property.configurable === false) {
return
}
// 拿到属性上的 getter/setter
const getter = property && property.get
const setter = property && property.set
// 创建一个属性值的观察者实例(只有在属性值为 {}、[] 时会为该属性创建 Observer)
// 实质上这个地方就完成了嵌套'对象'类型的递归创建观察者操作
// 例如:data:{student:{name,age}}
// 最外层的 data 会先被传入 observe,并且构造 Observer
// 在构造 Observer 时会调用 walk 方法,然后遍历 data 上的所有属性,即 student
// student 又会被传入 observer ,并创建 Observer,如果 student 中还有值为对象类型的属性,便会一层一层递归包装
// 在每次 observe 时,只要值是'对象'类型都会有为该'对象' new Observer 的操作
// 因为引用类型的地址都不相同,且这个对象之前并未观察过,所以只要该值是'对象'就一定会创建新的观察者
// 需要注意这里的'对象'的范围
let childOb = observe(val)
// 通过 Object.defineProperty 实现了数据劫持
// 在每次获取、修改数据时都会进行依赖的收集或更新
// 定义属性,并为该属性添加 getter 和 setter 用于数据劫持
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 属性名描述符可改变,属性可删除
get: function reactiveGetter () { // 响应式的 getter
// 有 getter 方法就调用 getter 方法,不然直接赋值咯
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 有目标程序监视器,收集新的依赖
dep.depend()
if (childOb) {
// 有子观察者,收集子观察者自身这个依赖
childOb.dep.depend()
}
if (Array.isArray(value)) {
// 如果属性的值是一个数组,特殊处理值为数组的依赖
// 收集该数组上所有的依赖
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) { // 响应式的 setter
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */ // 禁止下面语句 eslint 的报错
if (newVal === value || (newVal !== newVal && value !== value)) {
// 新旧值相同,新旧值都为NaN,直接返回不允许设置
return
}
......
if (setter) {
// 有 setter 方法调用
setter.call(obj, newVal)
} else {
// 没有 setter 方法直接赋值
val = newVal
}
// 更新该属性的观察者
childOb = observe(newVal)
dep.notify() // 通知该依赖的所有申请者更新
}
})
}
-
在Vue修改数组中的某个元素一定要用如下方法:
-
使用这些 API : push()、pop()、shift()、unshift()、splice()、sort()、reverse()
-
Vue.set() 或 vm.$set()
注意:修改数组如果通过 arr[0] = value 的方法, Vue 并不会监测数据发送改变,并重新收集依赖渲染
vue 底层对上述的几个 API 进行了重新包装
特别注意: Vue.set() 和 vm.$set() 不能给 vm 、 vm的根数据对象 添加属性!!!
-
Array Rewritten
['push','pop', 'shift','unshift', 'splice', 'sort', 'reverse']
.forEach(function (method) {
const original = arrayProto[method] // 拿到原生的方法
def(arrayMethods, method, function mutator () { // 包装原来的数组方法
// 参数处理
......
// 对列表的方法调用结果进行拦截
const result = original.apply(this, args) // 传入参数调用原生的数组方法,拿到结果
const ob = this.__ob__ // 拿到实例上的观察者
let inserted
switch (method) { // 拿到插入方法中新插入的值
case 'push': // 尾部添加
inserted = args
break
case 'unshift': // 头部添加
inserted = args
break
case 'splice': // 内部插入
inserted = args.slice(2) // 拿到下标从2开始的切片,也就是 splice 中替换原数组的实参
break
}
// 如果存在列表的插入类型操作,需要观察新插入的值
// 删除、修改这些操作无需重新观察,因为这些值原本初始化时就创建了观察者
if (inserted) ob.observeArray(inserted)
// 通知该依赖的所有申请者更新,通过这一步各个申请者收集更新完依赖,便会重新渲染
ob.dep.notify()
return result
})
})
七、面试重点
react、vue中的key有什么作用?(key的内部原理)
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。