一、虚拟节点
<body>
<!-- 真实的节点 -->
<div id='app'>
<h3>Vue你好</h3>
</div>
</body>
虚拟节点 => 对于理解视图框架(Vue,React,小程序)
目前流行的视图框架都是基于虚拟节点技术开发的.
为什么jq落伍了 => jq的DOM操作性能不好.
视图框架 => 更新视图的最后异步是DOM操作. => 尽量以最低的代价进行DOM操作.
视图框架如何进行最低代价的DOM操作 => 虚拟节点 => 虚拟节点的更新 => diff算法.
虚拟节点 => 一个纯对象 => 描述真实节点.
#app的虚拟节点.
虚拟节点在描述一个标签时,都有3个最基本的属性 => 标签名,属性列表,子节点列表。
虚拟节点就是真实节点的js描述.(AST语法树)
<script src="js/vue.js"></script>
<script>
const Vnode = {
// 描述标签名
tag: 'div',
// 描述属性节点列表
attrs: [{id: 'app'}],
children: [{
tag: 'h3',
attrs: [],
children: ['Vue你好']
}]
}
</script>
更新后的虚拟节点
const Vnode = {
tag: 'div',
attrs: [{id: 'app'}],
children: [{
tag: 'h3',
attrs: [],
children: ['Vue你好1111111']
}]
}
真实节点更新 => 一定是DOM操作完成.
虚拟节点更新 => 修改纯对象.
如何根据更新后的虚拟节点,决定如何进行DOM操作去更新真实节点. => diff算法.
diff算法 => 比较更新前和更新后的虚拟节点一桶,最终决定如何进行DOM操作.
如何比较更新前后的虚拟节点异同?
比较出异同后,应不应该更新视图,更新那部分视图,如何更新视图?
二、Vue的虚拟节点
<body>
<div id='app'>
<h3>{{msg}}</h3>
</div>
</body>
<script src="js/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue你好'
}
});
// vm实例的虚拟节点
// console.log(vm._vnode);
</script>
三、生命周期
<body>
<div id='app'>
<h3 ref='wq'>{{msg}}</h3>
</div>
</body>
虚拟节点什么时候产生的?
- 虚拟节点什么时候转换成真实节点的?
- Vue的生命周期 => Vue实例从创建达到销毁过程中的特定阶段.
- 人 => 儿童期,青年期,中年期,老年期. => 正确的阶段做正确的事情.
- Vue实例 => 创建期,挂载期,更新期,销毁期. => 正确的函数内写正确的代码.
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
msg: ''
},
// 生命周期.
created() {
this.msg = '吴签';
// 报错.
// this.$refs.wq.style.backgroundColor = 'red';
// console.log(this.$refs.wq); // undefined
},
// 生命周期钩子函数
mounted() {
// $refs属性只能在mounted中获取.
this.$refs.wq.style.backgroundColor = 'red';
console.log(this.$refs.wq); // undefined
}
})
</script>
四、生命周期创建期
<body>
<div id='app'>
<h3 ref='wq'>{{msg}}</h3>
</div>
</body>
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
msg: ''
},
// 创建前 => 没什么用.
beforeCreate() {
console.log('创建前');
console.log('beforeCreate', this.$data); // undefined
},
// 创建后 => 一帮用于数据初始化。
// 在这个构造函数内只能操作数据,不能操作视图.
created() {
console.log('创建后');
console.log('created', this.$data);
this.msg = '吴签';
},
})
</script>
五、挂载期
<body>
<div id='app'>
<h3 ref='wq'>{{msg}}</h3>
</div>
</body>
挂载 => 新视图替换老视图.(组件template替换组件标签).
挂载前后 => 新视图出现前后.
这两个钩子也只触发一次.
mounted => 视图初始化.
创建前,创建后,挂载前,挂载后 => 都只触发一次. => 在Vue实例的实例化过程中触发的.
挂载都发生了什么事情?
1:编译template.(把字符串模板编译成虚拟节点).
2:编译遇到指令和插值表达式,都会进行求值.(收集依赖).
3:把虚拟节点转换为新视图,替换老视图.
created => 处理数据(默认处理一次)
mounted => 处理视图(默认处理一次)
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
msg: '按钮'
},
template: `<button>{{msg}}</button>`,
created() {
console.log('created');
},
// 新视图挂载前 (老视图最终会被删除,数据不会显示在老视图上);
beforeMount() {
console.log('挂载前');
},
// 新视图挂载后.(数据显示到了视图上);
mounted() {
console.log('挂载后');
}
})
</script>
六、生命周期图示
<body>
<div id='app'>
<button>按钮--{{msg}}</button>
</div>
</body>
你描述下Vue的生命周期.(Vue的钩子函数).
1:基本的回答 => 8个钩子函数的名字.(不懂英文说中午).
2:回答生命周期图示.(更详细).
new Vue实例化(组件实例化)
触发beforeCreate
通过Object.defineProperty给data数据设置数据劫持
触发created
判断有没有el选项.
如果有,继续判断有没有template选项
如果有template选项,则编译template
如果没有template选项,则编译el所在的标签
触发beforeMount
根据编译的虚拟节点生成真实节点vm.$el.并且用真实的新视图替换掉老视图
触发mounted.
如果没有el选项,则等待$mount方法触发,如果这个方法也没触发,则实例化失败.
$mount(选择器) === el: 选择器
<script src="js/vue.js"></script>
<script>
new Vue({
template: `<h3>新视图</h3>`,
data: {
msg: '你好'
}
}).$mount('#app');
</script>
七、更新阶段
<body>
<div id='app'>
<h3 ref='wq'>{{msg}}</h3>
</div>
</body>
视图更新前后 => 分别触发beforeUpdate和updated
默认不触发(Vue实例化阶段不触发).
Vue实例化完成之后,视图再更新,才会触发.
有可能触发很多次.
updated和watch的区别
updated => 视图更新就触发.不能知道本次更新是由于哪个数据变化导致的.具有响应式效果的数据变化,都触发updated.
watch => 数据变化就触发.监听哪个数据,就触发哪个方法.
nextTick => 单次触发的updated
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
msg: '按钮',
},
template: `<button @click='$forceUpdate()'>{{msg}}</button>`,
// 更新前
beforeUpdate() {
console.log('视图更新前')
},
// 更新后
updated() {
console.log('视图更新后')
}
});
</script>
八、视图更新过程
<body>
<div>
<h3>1111</h3>
<button key='a'>33333</button>
</div>
<div>
<h3>2222</h3>
<button key='b'>4444</button>
</div>
</body>
数据变化 => 导致视图更新 (响应式)
数据变化 => 先更新虚拟节点 => diff算法判断 => 最后更新真实节点。
更新真实节点
1:原地更新.(标签不增删,不移动,直接修改内容或者属性)
2:替换更新.(会增删,移动元素)
什么时候原地更新?什么时候替换更新?
1:比较更新前后的虚拟节点.(同级比较)
2:如果同级的tag一致,并且key一样,则表示更新前后是同一个标签,使用原地更新策略.
3:如果同级的tag或者key有一个不一样,则表示更新前后不是同一个标签,使用替换更新策略.
九、key
<body>
<div id='app'></div>
</body>
- 更新前后同级比较
- 如果tag和key一致,就原地更新.(更新前后是同一个标签.)
- 只要给上不一样的key,则使用替换更新策略.
<script src="js/vue.js"></script>
<script>
const App = {
template: `
<div>
<button @click='flag = !flag'>切换</button>
<div v-if='flag'>
<h3>普通用户</h3>
<input type='text' key='a' />
</div>
<div v-else>
<h3>Vip用户</h3>
<input type='text' key='b' />
</div>
</div>
`,
data() {
return { flag: true }
}
}
new Vue({
components: { App },
template: `<App />`
}).$mount('#app');
</script>
十、v-for的key
<body>
<div id='app'></div>
</body>
- 以后记得v-for都要加key.
- 数据的更新和视图的更新保持一致.(例如位置).需要加key
- 还得保证更新前后的key是不一样的。
<script src="js/vue.js"></script>
<script>
const App = {
template: `
<div>
<ul>
<li v-for='(d,i) in arr' :key='d.id'>
{{d.content}}
<input type='text' />
<button @click='up(i)'>上移</button>
</li>
</ul>
</div>
`,
methods: {
up(i) {
[this.arr[i], this.arr[i-1]] = [this.arr[i-1], this.arr[i]];
this.$forceUpdate();
}
},
data() {
return {
arr: [
{
content: 1111,
id: 1
},{
content: 2222,
id: 2
},{
content: 3333,
id: 3
}
],
}
}
}
new Vue({
components: { App },
template: `<App />`
}).$mount('#app');
</script>
十一、v-if和v-show的区别
<body>
<div id='app'>
<button v-show='flag'>按钮</button>
<h3 v-if='flag'>你好</h3>
</div>
</body>
v-if => 根据初始条件决定是否把对应的标签编译到虚拟节点中.
v-show => 不管初始条件是什么,都编译标签到虚拟节点中,渲染时,根据初始条件决定样式display的值.对于多次显示隐藏的标签,应该用v-show.
对于只有一次显示隐藏的情况,就应该用v-if.
<script src="js/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
flag: false
}
})
</script>
十二、v-if和v-for的优先级
<body>
<div id='app'>
<ul>
<li v-for='d in arr' v-if='d%2 === 0'>{{d}}</li>
</ul>
<ul>
<li v-for='d in arr.filter(item => item%2 === 0)'>{{d}}</li>
</ul>
<ul>
<li v-for='d in list'>{{d}}</li>
</ul>
</div>
</body>
v-for和v-if的优先级 => v-for优先级更高 => 不管有没有虚拟节点,都会循环 => 性能不好.不推荐v-for和v-if一起使用.
可以通过计算属性来过滤原始数组,只留下应该渲染的元素.
<script src="js/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
arr: [111, 222, 333, 444]
},
computed: {
list() {
return this.arr.filter(item => item%2 === 0)
}
}
})
</script>