MVVM原理的理解
model-view-viewModel的简写
model:数据模型层,用来处理业务逻辑与数据库交互
view:视图层,DOM
viewModel:视图模型层,用来处理model层和view层的交互
view通过viewModel的DOM Listeners将事件绑定到model上,而Model则通过Data Bindings来管理
view中的数据,viewModel从中祈祷一个链接的作用
vue中viewModel实现原理:
响应式:Object.defineProperty()来做数据劫持和响应式
模板解析:Vue中的render函数,来将模板转换成虚拟DOM
将虚拟DOM渲染成html,通过updateComponent方法实现
优点:
a、低耦合:视图可以独立于Model变化和修改,一个viewModel可以绑定到不同的view上,当view变化的
时候model可以不变,反之亦然
b、可重用性,可以将视图逻辑放到一个viewModel里面,让很多view重用这段视图逻辑
c、独立开发,
d、可测试
响应式数据的原理
- 数据响应式:指的是数据变了,视图也跟着变
- 原理的话就采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的setter,getter,
在数据变动时发布消息给订阅者,触发相应的监听回调。
- 在数据初始化的时候把data里面的数据进行转换,因为Object.defineProperty里面有一个get和set方法,
- get可以返回被劫持的属性和结果,而set可以修改被劫持的属性和结果;
- 每个数据创建一个dep被观察者,用到数据的地方就被称为观察者watcher
- 对模板进行编译compiler提取里面所有的需要数据的地方变成watcher,把watcher加入到对应的dep的观察者列表中
当data的数据发生改变的时候,由于被劫持了,所以vue的内部是知道数据改变了,然后就调用对应的dep去
通知对应的观察者列表中的所有观察者,观察者得到通知后立即去更新视图
- 为什么数据的索引不是响应式的,为什么要用this.$set来处理?
因为vue的官方是出于性能考虑,所以没对数组进行转换,但是提供了操作方法$set去实现响应式变化
- Objet.defineProperty的缺陷
如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为Object,defineProperty
不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是vue内部通过重写
函数的方式解决不了这个问题
vue中如何检测数组的变化
使用函数劫持的方式,重写了数组的方法
vue将data中的数组,惊醒原型链重写,指向了自己定义的数组原型方法,这样相当于调用数组api时,
可以通知依赖更新,如果数组中包含引用类型,会对数组中的引用类型再次进行监控
数组方法有:push;pop;shift;unshift;splice;sort;reverse
异步渲染
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以未了性能考虑,
vue会在本轮数据更新后,再去异步更新视图
核心方法:nextTick()
nextTick实现原理
nextTick作用:在下次DOM更新循环结束之后执行的延迟回调
主要使用了宏任务和微任务,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个
异步方法清空当前队列,所以这个方法时异步方法在执行的过程中,微任务会高于宏任务
computed的特点
默认computed也是一个watcher是具备缓存的,只要当依赖的属性发生变化时才会更新视图
computed 和 watch 区别
computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。
watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
所以一般来说需要依赖别的属性来动态获得值的时候可以使用 computed,对于监听到值的变化需要做一些
复杂业务逻辑的情况可以使用 watch。
watch中的deep:true是如何实现的
当用户指定了watch中的deep属性为true时,如果当前监控的值是数组类型,会对对象中的每一项进行求值,
此时会将当前watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新
keep-alive 组件
- 如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
- 对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived
钩子函数。
Vue常用的修饰符
表单修饰符:
- lazy:在change事件之后再进行信息同步
- trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤
- number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值
事件修饰符:
- stop:阻止了事件冒泡,相当于调用了event.stopPropagation方法
- prevent:阻止了事件的默认行为,相当于调用了event.preventDefault方法
- self:只当在 event.target 是当前元素自身时触发处理函数
- once:绑定了事件以后只能触发一次,第二次就不会触发
- capture:使事件触发从包含这个元素的顶层开始往下触发
- passive:在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页卡,因此我们使用这个修饰符的
时候,相当于给onscroll事件整了一个.lazy修饰符
- native:让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件
鼠标按钮修饰符:
- left 左键点击
- right 右键点击
- middle 中键点击
键盘修饰符:
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的,有如下:
keyCode存在很多,但vue为我们提供了别名,分为以下两种:
- 普通键(enter、tab、delete、space、esc、up...)
- 系统修饰键(ctrl、alt、meta、shift...)
v-bind修饰符:主要是为属性进行操作
- async:能对props进行一个双向绑定
- 使用sync的时候,子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致
- 注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用
- 将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的
- prop:设置自定义标签属性,避免暴露数据,防止污染HTML结构
- camel:将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox
vue组件的生命周期
要掌握每个生命周期什么时候被调用
beforeCreate在实例初始化之后,数据观测之前调用
created实例已经创建完成之后被调用,在这一步,实例已经完成这些配置:数据观测,属性和方法的运算,watch/event事件回调,这里没有$el
beforeMount在挂载开始之前被调用,相关的render函数首次被调用
mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子
beforeUpdate数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前
updated由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
beforeDestroy实例销毁之前调用,在这一步,实例仍然完全可用
destroyed Vue实例销毁后调用,vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,
所有的子实例也会被销毁,该钩子在服务器渲染期间不被调用
要掌握每个生命周期内部可以做什么事
created实例已经创建完成,因为它事最早触发的原因,可以进行一些数据、资源的请求
mounted实例已经挂载完成,可以进行一些DOM操作
beforeUpdate可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated可以执行依赖于DOM的操作,然而在大多数情况下,应该避免在此期间更改状态,因为这个可能会
导致更新无限循环,该钩子在服务器端渲染期间不被调用
destroyed可以执行一些优化操作,清空定时器,解除绑定事件
ajax请求放在哪个生命周期中
在created的时候,视图中的dom并没有渲染出来,所以此时如果直接去操作dom节点,无法找到相关的元素
在mounted中,由于此时dom已经渲染出来了,所以可以直接操作dom节点一般情况下都放在mounted中,
保证逻辑的统一性,因为生命周期事同步执行的,ajax是异步执行的
何时需要使用beforeDestroy
可能在当前页面使用了$on方法,那需要在组件销毁前解绑清除自己定义的定时器
解除事件的绑定scroll mousemove....
v-if和v-show的区别
v-if如果条件不成立不会渲染当前指令所在节点的dom元素
v-show只是切换当前dom的显示或者隐藏
`v-show` 只是在 `display: none` 和 `display: block` 之间切换。无论初始条件是什么都会被渲染
出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 `v-show` 在初始渲染时有更高的
开销,但是切换开销很小,更适合于频繁切换的场景。
`v-if` 的话就得说到 Vue 底层的编译了。当属性初始为 `false` 时,组件就不会被渲染,直到条件
为 `true`,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,
更适合不经常切换的场景。
并且基于 `v-if` 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。
为什么v-for和v-if不能连用
v-for会比v-if的优先级高一些,如果连用的话会把v-if给每个元素都添加一下,会造成性能问题
描述组件渲染和更新过程
渲染组件时,会通过vue.extend方法构建子组件的构造函数,并进行实例化,最终手动调用$mount()
进行挂载,更新组件时会进行patchVnoed流程,核心就是diff算法
组件中的data为什么是一个函数?
同一个组件被复用多次,会创建多个实例,这些实例用的是同一个构造函数,如果data是一个对象的话,
那么所有组件都共享了同一个对象,为了保证组件的数据独立性要求每个组件必须通过
data函数返回一个对象作为组件的状态
vue中事件绑定
vue的事件绑定分为两种,一种是原生的事件绑定,还有一种是组件的事件绑定
原生dom事件的绑定采用的是addEventListener实现
组件绑定事件采用的是$on方法
v-html会导致哪些问题
可能会导致xss攻击
v-html会替换掉标签内部的子元素
组件之间的通信方式
通过 props 传递
- 适用场景:父组件传递数据给子组件
- 子组件设置props属性,定义接收父组件传递过来的参数
- 父组件在使用子组件标签中通过字面量来传递值
通过 $emit 触发自定义事件
- 适用场景:子组件传递数据给父组件
- 子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
- 父组件绑定监听器获取到子组件传递过来的参数
使用 ref
- 父组件在使用子组件的时候设置ref
- 父组件通过设置子组件ref来获取数据
EventBus
- 使用场景:兄弟组件传值
- 创建一个中央事件总线EventBus
- 兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
- 另一个兄弟组件通过$on监听自定义事件
$parent 或$root
- 通过共同祖辈$parent或者$root搭建通信桥连
- 兄弟组件
- this.$parent.on('add',this.add)
- 另一个兄弟组件
- this.$parent.emit('add')
attrs 与 listeners
- 适用场景:祖先传递数据给子孙
- 设置批量向下传属性$attrs和 $listeners
- 包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。
- 可以通过 v-bind="$attrs" 传⼊内部组件
Provide 与 Inject
- 在祖先组件定义provide属性,返回传递的值
- 在后代组件通过inject接收组件传递过来的值
Vuex
- state用来存放共享变量的地方
- getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
- mutations用来存放修改state的方法。
- actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作
父子组件生命周期调用顺序
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父
组件的销毁操作是先父后子,销毁完成的顺序是先子后父
SPA首屏加载速度慢
在页面渲染的过程,导致加载速度慢的因素可能如下:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
常见的几种SPA首屏优化方式
- 减小入口文件积:
常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加
- 静态资源本地缓存:合理利用localStorage
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR服务端渲染
常见的性能优化
编码优化:
不要将所有的数据都放在data中,data中的数据都会增加getter和setter,会收集对应的watcher
vue在v-for的时候给每项元素绑定事件需要用事件代理
SPA页面采用keep-alive缓存组件
拆分组件(提高复用性,增加代码的可维护性,减少不必要的渲染)
v-if当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
key保证唯一性(默认vue会采用就地复用策略)
object.freeze冻结数据
合理使用路由懒加载、异步组件
尽量采用runtime运行时版本
数据持久化的问题(防抖、节流)
加载性能优化:
第三方模块按需导入
滚动到可视区域动态加载
图片懒加载
用户体验:
app-skeleton骨架屏
app-shell app壳
pwa
SEO优化:
预渲染插件
服务端渲染
打包优化:
使用cdn的方式加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap生成
缓存、压缩
客户端缓存、服务端缓存
服务端gzip压缩
vue-router中导航守卫有哪些?
完整的导航解析流程
1、导航被触发
2、在失活的组件中调用离开守卫
3、调用全局的beforeEach守卫
4、在重用的组件里调用BeforeRouteUpdate
5、在路由配置里调用beforeEnter
6、解析异步路由组件
7、在被激活的组件里调用beforeRouteEnter
8、调用全局的beforeResolve守卫
9、导航被确认
10、调用全局的afterEach钩子
11、触发DOM更新
12、用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数