MVVM框架
- Model层关注数据处理,从服务端获取数据
- View层渲染页面
- VM连接view层和model层,通过数据响应式来监听数据更改,以触发视图更新。通过双向绑定来同步视图和数据。让我们不需要过多关注dom操作,更专注于业务逻辑。同时用jQuery的话会觉得这2者在写的时候方式很不同
组件化思想
每个组件都是一个Vue实例,由Vue组件去拼装成页面。每个组件可以包含自己的数据、视图、逻辑
1. 刷新页面的几种方式
this.$foreceUpdate()
location.reload()
this.$router.go(0)
- 用变量控制组件是否渲染(v-if),要刷新时先将变量置为false,再在$nextTick里面置为true
2. computed和watch
2.1 computed - 计算属性
有缓存,不支持异步
① 有缓存什么意思?
当视图更新的时候,写在模板里的方法调用或者表达式都会重新执行,但如果使用的是computed计算出的值,只有它依赖的值发生改变时才会重新计算
② 怎么实现的缓存?
在依赖的值发生改变以后,计算属性的watcher的dirty属性会置为true,只有dirty为true才会重新计算结果,计算完之后又会置为false。
③ 自动计算的属性,是否支持主动修改?
可以。我们定义的函数实际上定义的是这个值的get函数,如果需要修改,还需要定义set。(但不建议这么做)
④ watch有深度监听,为什么computed没有deep: true?
因为computed依赖的值是确定的,用到了谁就把谁加入依赖。
而watch是指定的,需要用deep来区分是监听“指向修改”还是“任意修改”
2.2 watch - 数据监听
支持异步
① 为什么deep设置true能设置深度监听?
默认情况下,Object.defineProperty就是只能监听到对象指向的变化。如果需要监听内部属性,则指定deep为true,以让Vue对这个对象进行深度遍历,给每个属性都加上监听器
3. $nextTick
① 为什么需要$nextTick?
当数据修改驱动视图更新,我们立即想要操作更新后的dom,此时无法立即获取到更新后的视图,需要使用 this.$nextTick(() => {})
。因为视图更新是异步的,$nextTick的回调会在视图更新之后尽快执行。
② 为什么vue是异步渲染的?
为了性能,否则每次数据修改都要重新渲染,所以一轮数据修改完后再渲染,减少渲染次数
4. keep-alive
它能够把不活动的组件实例保存在内存中,而不是直接将其销毁
① 为什么组件需要缓存?
主要是想在组件切换时能保持上一次的状态,使用<keep-alive></keep-alive>
,使组件被缓存在内存中,防止重复渲染,减少加载时间,重新激活时能保持上次状态
② 如何实现的缓存?
vm.$vnode.parent.componentInstance.cache
③ 用v-show也能保持上次状态,为什么不用v-show?
因为v-show一开始为false的时候也会进行渲染
④ 如果遇到二级路由
5. diff算法
① 虚拟节点
用对象的方式来描述真实dom的结构和内容
vNode = {
tag: 'div',
attrs: {
id: 'app',
},
children: [
{ tag: 'h2' }
]
}
② 虚拟dom怎么创建为真实dom?
// 极度简易版-噗
function createNode(vnode) {
if (typeof vnode === 'number') {
vnode = String(vnode);
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
// 创建
const n = document.createElement(vnode.tag);
// 设置属性
if (vnode.attrs) {
let attrs = vnode.attrs;
for (let attr in attrs) {
n.setAttribute(attr, attrs[attr]);
}
}
// 添加子元素
if (Array.isArray(vnode.children)) {
vnode.children.forEach(childNode => {
n.appendChild(createNode(childNode));
});
}
return n;
}
③ diff 算法
三棵树:旧的虚拟dom树、新的虚拟dom树、实际的dom树。
数据更新需要重新渲染视图时,为了最小范围更新dom,需要把新生成的虚拟dom和原虚拟dom对象进行比较,计算出变更,再根据变更去操作真实dom
function patch
function patchVnode
function updateChildren
// 同级比对
// 如果新节点为空,记录dom要被销毁。老节点为空,记录dom需要新建
// 如果新旧都是文本节点,再比较内容,不同则记录新的文本
// 如果新旧的节点类型或者key不相同,记录dom需要替换
// 如果认定为同节点,再比较新旧的各个属性,记录要修改的和要新增的属性值
// 同节点情况下,对比子节点:
// 首尾指针法 oldStart oldEnd newStart newEnd
// 比对:
// oldStart - newStart
// oldEnd - newEnd
// oldStart - newEnd
// oldEnd - newStart
// 这四种有匹配到的情况,则如果是start,则指针右移,是end则指针左移,进行下一轮比较
// 都没匹配到,则在old节点中查找与newStart节点相同的,有则复用,无则创建newStart节点,然后newStart指针向右移动
// 结束比对的节点是end小于start
6. SPA(single page application)
① 什么是单页面应用?
所有的活动在一个Web页面中进行。由一个外壳页面和多个页面片段组成,页面局部刷新,页面片段间的切换快,用户体验更好。
② 单页面的优缺点?
优点 | 缺点 |
---|---|
快(页面刷新不需要重新加载整个页面、路由切换不用去请求其他html资源) | 首次加载速度较慢、不利于seo |
③ 如何给SPA做SEO?
如果需要单页面应用有更好的SEO,那么通常需要使用SSR(Server-Side Rendering)服务端渲染,将组件或页面通过服务器生成html,再返回给浏览器。
SSR的优点:利于SEO,利于首屏加载
服务端渲染是先向后端服务器请求数据,然后生成完整首屏html返回给浏览器;
而客户端渲染是等js代码下载、加载、解析完成后再请求数据渲染,等待的过程页面是什么都没有的,就是用户看到的白屏。
就是服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完整数据的首屏页面。
7. Vue开发中的性能优化
路由懒加载
const page = () => import("@/views/test/index");
,只有当路由被访问时才会加载对应的组件-
组件懒加载(异步组件)
v-show/v-if(v-if在显示隐藏过程中有DOM的添加和删除,v-show就简单多了,只是操作css)
v-for不和v-if同时使用,v-for要加唯一的key
能用computed的就用computed(有缓存)
watch监听对象属性时,指定属性
vue2中,data里面数据层级不要太深
有些数据仅展示不会改变,不需要响应式处理,可以使用Object.freeze进行冻结(因为vue判断 configurable 为 false 的对象直接返回不做响应式处理,而冻结的对象的 configurable 为 false)
长列表-分页和虚拟加载(只渲染可视部分的内容)
图片压缩,查看时再下载原图
对于一些纯展示,没有响应式数据,没有状态管理,也不用生命周期钩子函数的组件,就可以设置成函数式组件(函数式组件并不会创建vue对象)
<!-- 在模板中通过props来访问 -->
<template functional>
<h1>{{ props.msg }}</h1>
</template>
Vue.component('component-name', {
functional: true
});
- 保存多次引用的变量,因为响应式对象每次读取都会触发getter,然后去走收集依赖的逻辑
8. 样式穿透
style标签的scoped属性实现了组件样式的私有化,防止组件之间的样式污染,如果需要样式穿透:
① 创建全局css文件并在main.js中引入
② 在单页面使用/deep/
9. 为什么vue组件data是一个函数
避免共享同一数据造成的数据污染。因为组件可能被用来创建多个实例,如果data直接声明为对象,当组件复用时,这些实例用的同一个构造函数,造成data公用的情况
10. 优先级
props\methods\data\computed\watch
11. key
① for 循环为什么要加 key?
增加key可以标识组件的唯一性,为了在更新视图的时候尽可能地复用dom,而不是创建节点
② 没有现成的key
如果数组中没有唯一的 key值可用,且数组更新时不是全量更新而是采用类似push,splice来插入或者移除数据时,可以考虑对其添加一个 key 字段,值为 Symbol() 即可保证唯一
③ 加上key就一定性能最优吗?
dom节点的移动操作相比简单node的text修改,前者开销更大。key更多的情况下与v-for一起使用;在数据量少的简单情况下,就地更新更快
④ key为什么不用index?
加key就是为了识别,index完全依靠列表顺序,不具备识别的作用
12. v-for 和 v-if 为什么不同时在一个元素上使用
v-for的优先级大于v-if,也就是先循环再判断,如果最后判断为false,不需要渲染,那开始的循环就是无谓的开销了。
13. vue中操作dom
13.1 ref
在标签上加属性ref,可以使用vm.$refs拿到对应的dom
<h1 ref='title'></h1>
this.$refs.title // node h1
13.2 ref标记dom的原理
不是“选择”来的,不需要query,而是标记的。一开始就用ref告诉vue我们要用哪些dom,它直接帮我们存在$refs里
14. 扩展组件的方式
mixin
代码复用上很方便,但是只适合小范围使用。
mixin多了可能出现命名冲突,而且维护mixin的内容会变得困难,因为影响范围的内部是不确定的;来源不明:组件内部调用的时候难以判定来源是哪个mixin(这时候需要在命名上加以区分,可以加上mx_[modulename]_
前缀)插槽
容器型的组件
15. 父子组件挂载过程
父组件 :beforeCreate => created => beforeMount => render
子组件挂载完毕: beforeCreate => created => beforeMount => Mounted => render
父组件挂载:mounted
16. component可以递归使用自己来进行渲染
<!--MenuKJ组件-递归渲染多级菜单-->
<template>
<ul v-if="menu.length">
<li v-for="m in menu" :key="m.name">
{{ m.name }}
<menuKJ v-if="m.children" :menu="m.children"></menuKJ>
</li>
</ul>
</template>
<script>
export default {
name: 'MenuKJ',
props: {
menu: {
type: Array,
default: () => []
}
}
};
</script>
17. v-model原理
v-bind="value"
@input="value=$event.target.value"