- 响应式原理
- 概念: 数据发生改变的时候,视图会重新渲染,匹配为最新的值
- 三个问题:
- vue是怎么知道数据改变的?
- 每个数据都有get和set方法
- vue在数据改变时,怎么知道通知哪些视图更新?
- 哪些视图依赖了这些数据的,就需要更新
- vue在数据改变时,视图怎么知道什么时候更新?
- 依赖更新
- vue是怎么知道数据改变的?
- 三个回答:
- Object.defineProperty
- 为对象中的每一个属性设置get和set方法
- get: 一个函数, 当属性被访问时,会触发
- set: 一个函数, 当属性被赋值时,会触发
- 回答第一个问题, 当数据改变的时候,触发属性的set方法,vue就知道数据有改变
- 依赖收集
- data中声明的每个属性,都拥有一个数组,保存着依赖其的对象
- 例如 data:{name:'张三'}, 页面A引用了name: < div>{{name}}< /div>, 那么就把页面A存在它的后宫中(这个页面依赖我),当然出除了页面,还有computed/watch等等,这里统一用页面替代
- 它知道谁依赖它之后,就可以在发生改变的时候,通知 依赖它的页面, 从而让页面完成更新
- 总结:
- data中每个声明的属性, 都会有一个专属的依赖收集器subs
- 当页面使用到某个属性时,页面的watcher就会被放到依赖收集器subs中
- 数据是在什么时候进行依赖收集的呢?
- 当页面A读取了name时,会触发name的get函数,此时name就会保存页面A的watcher了
- 即: data中每个属性会有一个依赖收集器,当页面使用到某个属性时, 即会触发此属性的get函数, 页面的watcher就被记录到属性的依赖收集器中
- 依赖更新
- 概念: 通知所有的依赖进行更新
- Object.defineProperty - set: 当name改变的时候,name会遍历自己的依赖收集器subs,逐个通知保存的watcher,让watcher完成更新(这里的name会通知页面A,页面A重新读取新的name,然后完成渲染)
- 回答: 在数据变化触发set函数的时候,通知视图,视图开始更新
- Object.defineProperty
- 简单总结:
- Object.defineProperty - get, 用于依赖收集
- Object.defineProperty - set, 用于依赖更新
- 每个data声明的属性,都拥有一个专属的依赖收集器
- 依赖收集器subs保存的依赖是watcher
- watcher可用于进行视图更新
- 生命周期
- beforeCreate(创建前)
- 数据观测和初始化事件还没有开始,访问不到data,computed,watch,methods上的方法和数据
- created(创建后)
- 实例创建完成,实例上配置的options包括data,computed,watch,methods等都配置完成,但组件还没有挂载即渲染的节点还未挂载到DOM,所以不能访问到$el属性也看不到.
- beforeMount(挂载前)
- 在挂载开始之前被调用,相关的render函数首次被调用.已经完成了模板编译,但还没有挂载到html到页面上, 简言之在这步创建虚拟DOM
- mounted(挂载后)
- 实例挂载完成,模板中的html渲染到html页面中,即虚拟DOM渲染成为真实的DOM
- 组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
- beforeUpdate(更新前)
- 响应式数据更新前调用,此时虽然数据更新了,但是真实DOM还没有渲染
- updated(更新后)
- 数据更新后,且DOM已经渲染了
- beforeDestroy(销毁前) destroyed(销毁后)
- 实例销毁前调用beforeDestroy, 然后进行一系列销毁操作时,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。
- keep-alive独有的生命周期(activated/deactivated)
- 用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数, 命中缓存渲染后会执行activated钩子函数.
- keep-alive是vue提供的一个内置组件,用来对组件进行缓存--在组件切换过程中将状态保留在内存中,防止重复渲染DOM
- 如果一个组件包裹了keep-alive,那么它会多出两个生命周期:activated/deactivated. 同时beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。
- 当组件被切换掉的时候,会被缓存到内存中, 触发deactivated生命周期;当组件被切换回来的时候,再去缓存里找这个组件,触发activated钩子函数
- beforeCreate(创建前)
- created和mounted的区别/一般在哪个生命周期请求异步数据
- 区别(参考就行)
- created在模板渲染成html前调用, 即通常初始化某些属性值, 然后再去渲染成为视图
- mounted在模板渲染成html后调用, 通常是初始化页面完成后, 再对html的dom节点进行一些需要的操作
- 生命周期请求
- 我们可以在钩子函数created,beforeMount,mounted中进行,因为在这三个钩子函数中,data已经创建,可以将服务端返回的数据进行赋值
- 推荐在created钩子函数中调用异步请求,因为在created钩子函数中调用异步能更快获取到服务端数据,减少页面加载时间,用户体验更好
- 区别(参考就行)
- v-model双向绑定的实现
-
概念:首先v-model是一个语法糖,由v-bind和v-on组合而成,既然如此就可以分为初始化绑定和双向更新
- 初始化绑定:即初始化时给表单元素绑定值,绑定事件,为双向更新做准备,例如这个表单的初始化绑定就是绑定了value值和input事件。
- 双向更新:即视图变化更新数据,数据变化更新视图
- 视图变化更新数据:视图变化举例即用户手动改变表单值,输入或者选择。此时通过各种事件来监听数据的变化,比如改变表单input里面的数据通过对input的事件监听就能获取input更新后的数据。即通过$event.target.value获取到输入的数据然后赋值给data
- 数据变化更新视图:这里就是响应式原理的内容了,v-bind:value绑定了data,data会收集到这个渲染组件的watcher到自己的依赖收集器subs中,那么当data变化时会触发其set方法,通知其收集器內该watcher更新,所以本组件会更新,渲染函数重新执行,重新从实例获取到最新的data值,然后赋值给绑定的该表单绑定的value,于是页面就更新了。
- 除了表单input之外还有textarea,checkbox(单选,复选[]),radio
-
- 状态管理(vuex)
- 概念: 是一个专为vue.js应用程序开发的状态管理工具
- vuex的状态储存是响应式的, 当vue组件从store中读取状态的时候,若store中的状态发生变化,若store中的状态发生变化, 那么相应的组件也会得到更新.
- 改变store中状态的唯一途径就是显式提交(commit)mutation.这样可以方便的跟踪每一个状态的变化.
- vue components生态圈
- vue components是vue组件,组件会触发(dispatch)一些事件或动作,也就是图中的actions
- 在组件中发出的动作,肯定是想获取或者改变数据的,但是在vue中,数据是集中管理的,不能直接去改变数据,所以会把整个动作提交(commit)到mutations中
- 然后mutations就去改变(mutate)state中的数据
- 当state中的数据被改变之后,就会重新渲染(render)到vue components中去,组件展示更新后的数据,完成一个流程.
- 各模块在核心流程中的主要功能:
- vue components:vue组件, html页面上负责接收用户操作等交互行为.执行dispatch方法触发对应action进行回应
- dispatch:操作行为触发方法: 是唯一能执行action的方法
- actions: 操作行为处理模块, 负责处理vue components接收到的所有交互行为. 包含同步/异步操作.支持多个同名方法, 按照注册的顺序依次触发. 向后台API请求的操作就在这个模块中进行, 包括触发其他action以及提交mutation的操作.
- commit: 状态改变提交操作方法. 对mutation进行提交, 是唯一能执行mutation的方法.
- mutations: 状态改变方法. 是vuex修改state的唯一推荐方法,其他修改方式在严格模式下会报错. 该方法只能进行同步操作, 且方法名只能全局唯一.
- state: 页面状态管理容器对象, 页面显示所需的数据从该对象中进行读取
- getters: 相当于一个计算属性
- 概念: 是一个专为vue.js应用程序开发的状态管理工具
- 组件通信
- 父子组件通信
- props/emit触发这个监听事件来向父组件发送数据。
- 依赖注入provide/inject,用于父子或者祖孙等,无需一层一层的传递。provide/inject是vue提供的两个钩子,和data,methods是同级的,并且provide的书写形式和data一样。provide钩子用来发送数据和方法。inject用来接收
- 在父组件中: provide(){ return { num: this.num } }
- 在子组件中 inject: 【'num'】
- ref/$refs, ref这个熟悉用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
0. 子组件中:data(){name:'aaa'}
- 父组件中:
- 父组件中:this.$refs.child.name //aaa
- 兄弟组件通信
- 使用eventBus(on)
- 创建事件中心管理组件之间的通信 const EventBus = new Vue()
- 发送事件,假设有两个组件A和B,在A中发送事件 add(){ EventBus.$emit('addition',{ num:this.num++ }) }
- 接收事件,在B中接收事件 mounted(){ EventBus.$on('addition',param => { console.log(param.num) }) }
- 相当于将num值存储值事件总线中,在其他组件中可以直接访问,事件总线就相当于一个桥梁,不用组件通过它来通信。
- children
- (在子组件中)使用$parent可以让组件访问父组件的实例(访问的上上一级父组件的属性和方法)
- (在父组件中)使用$children可以让组件访问子组件的实例,但不能保证顺序
- 使用eventBus(on)
- 任意组件通信
- 使用eventBus
- 多个公共数据-vuex
- 父子组件通信
- 虚拟dom 必要性是什么?diff算法?
- 虚拟dom
- 概念: 用js对象模拟的dom
- 存在必要性:
- 操作dom的开销很大而且很慢,相较于dom来说,操作js对象会快很多,我们可以通过js来模拟dom,
- 虚拟dom还能渲染到其他平台,比如实现ssr,跨端开发等
- 实现组件的高度抽象化
- diff算法
- 虚拟dom的对比更新是利用的diff算法,找到最小差异部分DOM,通过差异去局部更新真实dom,实现性能的最优化
- diff算法的判断步骤:
- diff算法的过程
- 首先, 对比节点本身, 判断是否为同一节点, 如果不为相同节点, 则删除该节点重新创建节点进行替换.
- 如果为相同节点, 再处理子节点. 先判断一方有子节点一方没有子节点的情况, 如果新的children没有子节点, 将旧的子节点删除
- 都有子节点, 则判断如何对这些新老节点的子节点进行操作(diff核心)
- 匹配时, 找到相同的子节点, 递归比较子节点
- diff核心比较的内核是节点复用,为了在(同层级的)新旧节点中找到相同的节点。比较处理流程如下:
- 先找到不需要移动的相同节点(可复用),消耗最小
- 再找到相同但是需要移动的节点(可复用),消耗第二小
- 最后找不到,才会去新建删除节点,保底处理
- 比较时候具体还是依靠key值来判断(引出为什么不能用index作为key值), 最好是使用唯一标识作为key值
- 举例:现在页面上有张三,李四两个人,突然插入了王五到最前面,导致张三,李四绑定的key发生了变化,从而使得页面三个人都必须要重新渲染,这就是key值的错误使用,原本不绑定index绑定其他值,在插入王五的时候是可以节省掉张三李四的渲染的。
- 总结:
- 用index作为key时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低
- 在开发中最好每条数据使用唯一标识固定的数据作为 key,比如后台返回的 ID,手机号,身份证号等唯一值
- diff算法的过程
- 虚拟dom
- computed/watch
- computed是计算属性,依赖其他属性计算值, 支持缓存,只有当计算值变化(会存在值变化但计算值未变的情况)了才会返回内容; watch是观察作用,不支持缓存,监听到值得变化就会触发相应的操作
- computed中有一个get方法和一个set方法,默认使用get方法,当数据发生变化时候会调用set方法
- watch监听的函数接收两个函数,第一个参数是最新的值,第二个是变化之前的值
- computed不支持异步监听,watch支持异步监听
- 总结:
- 一般来说需要依赖别的属性来动态获得值的时候可以使用 computed, 利用其缓存特性避免每次获取值都要重新计算,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch, 能支持在数据变化时执行异步或者开销较大的操作,这是computed无法做到的.
- watch补充:
- immediate: 组件加载立即触发回调函数
- deep:深度监听,发现数据内部的变化, 在复杂数据类型中使用, 例如数组中的对象发生变化. 需要注意的是, deep无法监听到数组和对象内部的变化.
- Vuemixin
- react hook
- vue与react的不同
- vue路由相关
-
路由hash模式和history模式
- 背景总结: 单页面应用是在移动互联时代诞生的, 它的目标是不刷新浏览器, 而通过感知地址栏中的变化来决定内容区域显示什么内容.要达成这个目标就用到前端路由计数, 具体来说有两种方式来实现.
- 原理不同: hash模式的实现原理是通过监听onhashchange事件来实现的, 前端js把当前hash地址对应的组件渲染到浏览器中. history模式是通过调用history.pushState(或者replaceState)并且监听popstate事件来实现的. history.pushState会追加历史记录, 并更换地址栏地址信息, 但是页面不会刷新, 需要手动调用地址变化之后的处理函数,并在处理函数内部决定跳转逻辑; 监听popstate是为了响应浏览器的前进后退(back(),forward(),go())功能.
- 表现不同: hash模式会在地址栏有#号, 而history没有; 同事由于history模式的实现原理用到HT的新特性,所以它对浏览器的兼容性有要求(IE>=10).
- history模式特点: history模式开发的spa项目, 需要服务端做额外的配置, 否则会出现刷新白屏(链接分享失效). 原因是页面刷新时, 浏览器真的发出对这个地址的请求,而这个文件资源又不存在, 所以就报404.处理方式就由后端做一个保底映射, 所有的请求全部拦截到index.html(路由重定向)
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1) } --------- const router = new VueRouter({ mode: 'history', routes: [...] })
-
- vue的nexttick
- 背景:vue采用了数据驱动的思想,但是在一些情况下,仍然需要操作DOM。
- 作用:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
- 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候即更新后的DOM的时候,这个操作就需要放在nextTick()的回调函数中。
- 在vue生命周期中,如果中created()钩子进行DOM操作,也一定要放在nextTick中。因为中create()钩子函数中,页面的DOM还未渲染,这个时候也没办法操作DOM,所以如此。
- 原理:
- vue异步执行dom更新。只要观察到数据变化,vue将开启一个队列,并推入在同一事件循环中发生的所有数据改变。
- 如果同一个watcher被多次触发,只会被推入到队列中一次。这种去重对于避免不必要的计算和dom操纵上非常重要。
- 然后,在下一个的事件循环'tick'中,vue刷新队列并执行实际(已去重的)工作。
- 例如,当你设置vm.data = 'new value'时,该组件不会立即渲染。当刷新队列时,组件会在事件循环队列清空时的下一个'tick'更新。所以要拿到更新后的DOM可以中数据变化之后立即使用nextTick(callback),这样回调函数在dom更新完成后就会调用。
- v-if和v-show,v-html的原理区别
- 原理:
- v-if会调用addifCondition的方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染.
- v-show会生成vnode,render的时候也会渲染成为真实节点,只是在render的过程中会在节点的属性中修改show属性值,也就是常说的display.
- v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值
- 区别:
- 手段: v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐.
- 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换.
- 编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留.
- 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗.
- 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换.
- 原理:
- vue的优化手段有哪些?
- 编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher.
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理.
- SPA页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if代替v-show
- key保证唯一
- 使用路由懒加载,异步组件
- 防抖,节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
- 预渲染或服务端渲染SSR
- wepack层面优化
- 压缩代码和图片
- 提取公共代码
- Tree shaking/Scope hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
- web技术的优化
- 服务端开启gzip压缩
- 浏览器缓存,服务端缓存
- 利用chrome performance查找性能瓶颈
- 编码阶段
- Vue与MVVM
- MVVM
- 概念:M即Model(数据),V即View(视图),VM即ViewModel,连接视图View和数据Model。Model更新触发View更新必须通过ViewModel,例如Vue实例中,set时触发依赖异步更新模板。而View变更后触发Model也必须通过ViewModel,例如利用v-model指令,input中用户手动输入监听@input世界,更改数据。
- Vue是按照这样设计的,但是Vue中添加了一个属性ref,通过ref可以拿到dom对象直接去操作视图(而不是通过修改数据去更新视图),这一点上违背了MVVM
- MVVM
- 谈谈你对 keep-alive 的了解?
- keep-alive 可以缓存组件及其的组件的状态(数据),避免了组件的频繁创建和销毁所带来的性能损耗。它有三个特性:
- 用于缓存组件,一般结合路由和动态组件一起使用。
- 提供 include 和 exclude 属性。两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
- 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
- keep-alive 可以缓存组件及其的组件的状态(数据),避免了组件的频繁创建和销毁所带来的性能损耗。它有三个特性:
- 前端性能优化