vue相关问题
简述MVVM
视图模型双向绑定,是Model-View-ViewModel的缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。以前是操作DOM结构更新视图,现在是数据驱动视图。
MVVM的优点
- 1、低耦合,视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;
- 2、可重用性,你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑
- 3、独立开发
- 4、可测试
Vue2.x双向绑定数据绑定
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
vue的双向绑定是指,数据变化更新视图变化,视图变化更新数据变化。
view变化更新Data可以通过事件监听的方式来实现,所以vue的数据双向绑定的工作主要是如何根据data变化更新view
主要通过以下4个步骤来实现数据双向绑定:
- 1、实现一个监听器Observer:对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对这个属性都加上setter 和getter。当给对象的某个属性赋值的时候就会触发setter,监听到数据变化(在getter收集依赖,在setter通知依赖更新)
- 2、实现一个解析器Complie: 解析Vue模版指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- 3、实现一个订阅者Watcher: Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
- 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
谈谈对vue2生命周期的理解?
每个vue实例在创建的时候会经过一系列的初始化过程,vue的生命周期钩子,就是在达到某个阶段或条件的时候出发的函数,目的是为了完成一些动作或事件。
- create阶段:vue实例被创建
beforeCreate: 创建前,此时data和methods中的数据都还没有初始化
created: 创建完毕,data中有值,未挂载 - mount阶段: vue实例被挂载到真实DOM节点
beforeMount:可以发起服务端请求,去数据
mounted: 此时可以操作DOM - update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染
beforeUpdate :更新前
updated:更新后 - destroy阶段:vue实例被销毁
beforeDestroy:实例被销毁前,此时可以手动销毁一些方法
destroyed:销毁后
组件生命周期
生命周期(父子组件) 父组件beforeCreate –> 父组件created –> 父组件beforeMount –> 子组件beforeCreate –> 子组件created –> 子组件beforeMount –> 子组件 mounted –> 父组件mounted –>父组件beforeUpdate –>子组件beforeDestroy–> 子组件destroyed –> 父组件updated
computed与watch
能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。
- watch属性监听: 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用
- computed计算属性: 属性的结果会被缓存,当computed中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 computed中的函数必须用return返回最终的结果 computed更高效,优先使用。data 不改变,computed 不更新。
使用场景 computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 watch:当一条数据影响多条数据的时候使用,例:搜索数据
组件中的data为什么是一个函数?
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。 2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
为什么v-for和v-if不建议用在一起
当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费,建议使用computed,先对数据进行过滤。
3.x 版本中 v-if 总是优先于 v-for 生效
React/Vue 项目中 key 的作用
- key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度,更高效的更新虚拟DOM;
- 为了在数据变化时强制更新组件,以避免“就地复用”带来的副作用
当 Vue.js 用 v-for 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误
vue组件的通信方式
- props/$emit 父子组件之间通信
- provide/inject 父->子孙组件之间
- ref/refs,获取子组件实例,并调用其方法和访问数据
- eventBus ,又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。 缺点:难以维护。
1 |
|
导出EventBus,通过$emit发送事件,通过$on接收事件,通过$off移除事件监听者
- Vuex: Vue.js 应用程序开发的状态管理模式(state、getters、mutations、actions、mudules)
- $attrs与 $listeners ,父->子,如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false, $listeners绑定父组件的所有事件
总结
常见使用场景可以分为三类: - 父子通信: 父向子传递数据是通过 props,子向父是通过 events( $emit);通过父链 / 子链也可以通信( $parent / $children);ref 也可以访问组件实例; provide/inject、$attrs/$listeners
- 兄弟通讯: eventBus; VueX
- 跨级通讯:eventBus、VueX、provide/inject、$attrs/$listeners
nextTick的实现
- nextTick是Vue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM
- Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
- 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。
nextTick的实现原理是什么?
在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
匿名插槽、具名插槽、作用域插槽
子组件:
父之间:
旧版本:
新版本:
keep-alive的实现
作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染
Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。
mixin(混入)
mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
多个组件有相同的逻辑,抽离出来
mixin并不是完美的解决方案,会有一些问题
vue3提出的Composition API旨在解决这些问题
劣势:
1.变量来源不明确,不利于阅读
2.多mixin可能会造成命名冲突
3.mixin和组件可能出现多对多的关系,使得项目复杂度变高
vue3.0 中为什么要使用 Proxy,它相比以前的实现方式有什么改进
1、在vue2.x中通过Object.defineProperty为每个对象添加getter、setter方法来监听数据的变化,vue3.x通过Proxy代理目标对象,且一开始只代理最外层,嵌套对象惰性监听(lazy by default),性能更好。
2、支持数组索引修改,对象属性的增加、删除
vue 数组中 key 的作用是什么
diff算法需求对比虚拟dom的修改,然后异步的渲染到页面中,当出现大量的相同标签的时候,vnode会首先判断key和标签名是否一致,如果一致再判断子节点一致,使用key可以帮助diff算法提升判断的速度,在页面重新渲染时更快、消耗更少。
vue 中 v-if 和 v-show 的区别是什么
v-show 总是会进行编译和渲染的工作 - 它只是简单的在元素上添加了 display: none;
的样式。v-show 具有较高的初始化性能成本上的消耗,但是使得转换状态变得很容易。 相比之下,v-if 才是真正「有条件」的:它的加载是惰性的,因此,若它的初始条件是 false,它就不会做任何事情。这对于初始加载时间来说是有益的,当条件为 true 时,v-if 才会编译并渲染其内容。切换 v-if 下的块儿内容实际上时销毁了其内部的所有元素,比如说处于 v-if 下的组件实际上在切换状态时会被销毁并重新生成,因此,切换一个较大 v-if 块儿时会比 v-show 消耗的性能多。
vue中computed的原理是什么
computed的实现是基于响应式原理中Watcher对象的实现,在初始化data和computed的时候会涉及到以下几个对象:1、Observer对象(现在数据监听)、2、Dep对象(收集依赖,触发watcher数据更新)3、Watcher对象(执行更新函数,更新依赖变化)。
computed有两种定义方式,一种是方法、一种是get和set属性,其内部监听的对象必须是已经定义响应式的属性。
vue在创建computed属性的时候,会循环所有的计算属性,每个计算属性都会创建一个watch,通过defineProperty定义监听,在get中收集依赖,在set中重新执行计算方法,computed是懒执行,在第一次初始化后,不会执行计算,下一次变更时在set中执行计算。
vue-loader 的实现原理是什么
vue-loader会把sfc(单文件组件规范)中的内容拆分为template、script、style三个虚拟模块,然后分别匹配webpack配置中的对应rules,比如script模块会匹配所有跟处理javascript和typescript相关的loader。
template中的内容会通过vue compiler转换为render函数合并到script虚拟模块中。
scoped style会经过vue-loader/style-post-loader的处理,成为只匹配特定元素的私有样式。
vue实现一个messageAPI
可以先写好一个 render 函数,作用是把某一 HTML 片段挂载到 #root 下 / 从 #root 删除该片段。然后写一个 Vue 插件,就是一个暴露了包含 install 方法的模块,install 方法中将 设置 Vue.prototype.$message = message 对象。最后使用 Vue.use 全局注册这个插件即可。
如果使用 SSR,可以在 created/componentWillMount 中访问 localStorage 吗
不可以,created/componentWillMount 时,还未挂载,代码仍然在服务器中执行,此时没有浏览器环境,因此此时访问 localStorage 将会报错
在 vue 中数组是否可以以在数组中的次序为 key
不可,key 应为唯一标识,在数组变更时插入或删除后,index 无法确保始终指向对应的序列
vue中的router实现原理
前端路由实现的本质是监听url的变化,实现方式有2种,Hash模式和History模式,无需刷新页面就能重新加载相应的页面。通过location.hash 跳转路由,通过hashchange监听路由变化。History url 通过history.pushState 和 history.replaceState改变url,通过popstate 事件监听路由变化。
两种模式的区别:
- hash 只能改变#后的值,而history模式可以随意设置同源url
- hash 只能添加字符串类的数据,而history可以通过API添加多种类型的数据
- hash 的历史记录只显示之前的
www.a.com
而不会显示 hash 值,而 history 的每条记录都会进入到历史记录; - hash 无需后端配置且兼容性好,而 history 需要配置
index.html
用于匹配不到资源的情况。
vue实例挂载的过程中发生了什么?
1、new Vue 的时候调用了_init方法
- 定义$set、$get、$delete、$watch 等方法
- 定义$on $off $emit 等事件
- 定义_update $forceUpdate $destory 生命周期
2、调用$mount 进行页面的挂载,挂载的时候主要通过mountComponent方法
3、定义updateComponent更新函数
4、执行render函数生成虚拟dom
5、_update将虚拟dom生成真实dom结构,渲染到页面中
在created和mounted这两个生命周期中请求数据有什么区别呢?
created后vue实力化完成,可以获取到属性和方法等,无法获取dom,而在mounted的时候dom已经挂载了,因此可以获取到dom,如果在mounted请求数据,页面可能会闪动一下。放在created中则不会。
SPA首屏加载速度慢的怎么解决?
减少入口文件体积(路由懒加载)
静态资源本地缓存
后端返回资源:
1、采用HTTP缓存,强缓存:Cache-Control, 协商缓存:Last-Modified, Etag
2、Service Worker离线缓存
客户端合理运用localStorage
UI框架按需加载
图片资源的压缩(压缩图片,使用icon,精灵图)
组件重复打包(在
webpack
的config
文件中,修改CommonsChunkPlugin
的配置)开启GZip压缩
1、安装
compression-webpack-plugin
,在vue.congig.js
中引入并修改webpack
配置1
2
3
4
5
6
7
8
9
10
11
12
13
14const CompressionPlugin = require('compression-webpack-plugin')
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
config.mode = 'production'
return {
plugins: [new CompressionPlugin({
test: /\.js$|\.html$|\.css/, //匹配文件名
threshold: 10240, //对超过10k的数据进行压缩
deleteOriginalAssets: false //是否删除原文件
})]
}
}2、服务端设置
使用SSR
Nuxt.js
小结:减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
为什么组件data属性是一个函数而不是一个对象?
- 根实例对象
data
可以是对象也可以是函数(根实例是单例),不会产生数据污染情况 - 组件实例对象
data
必须为函数,目的是为了防止多个组件实例对象之间共用一个data
,产生数据污染。采用函数的形式,initData
时会将其作为工厂函数都会返回全新data
对象
Vue中给对象添加新的属性,界面不刷新?
因为新加的属性不是响应式的数据
- 如果为对象添加少量的新属性,可以直接采用
Vue.set()
- 如果需要为新对象添加大量的新属性,则通过
Object.assign()
创建新对象 - 如果你实在不知道怎么操作时,可采取
$forceUpdate()
进行强制刷新 (不建议)
vue3
是用过proxy
实现数据响应式的,直接动态添加新属性仍可以实现数据响应式
Vue组件与插件的区别
组件 (Component)
是用来构成你的 App
的业务模块,它的目标是 App.vue
插件 (Plugin)
是用来增强你的技术栈的功能模块,它的目标是 Vue
本身
简单来说,插件就是指对Vue
的功能的增强或补充
vue的mixin的理解,有什么应用场景
- 替换型策略有
props
、methods
、inject
、computed
,就是将新的同名参数替代旧的参数 - 合并型策略是
data
, 通过set
方法进行合并和重新赋值 - 队列型策略有生命周期函数和
watch
,原理是将函数存入一个数组,然后正序遍历依次执行 - 叠加型有
component
、directives
、filters
,通过原型链进行层层的叠加
不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立
这时,可以通过Vue
的mixin
功能将相同或者相似的代码提出来
Vue.observable你有了解过吗?
Vue.observable
,让一个对象变成响应式数据。Vue
内部会用它来处理 data
函数返回的对象
在 Vue 2.x
中,被传入的对象会直接被 Vue.observable
变更,它和被返回的对象是同一个对象
在 Vue 3.x
中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的
在非父子组件通信时,可以使用通常的bus
或者使用vuex
,但是实现的功能不是太复杂,而使用上面两个又有点繁琐。这时,observable
就是一个很好的选择
自定义指令的应用场景
自定义指令也像组件那样存在钩子函数:
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)update
:所在组件的VNode
更新时调用,但是可能发生在其子VNode
更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新componentUpdated
:指令所在组件的VNode
及其子VNode
全部更新后调用unbind
:只调用一次,指令与元素解绑时调用
所有的钩子函数的参数都有以下
- el
- binding
- vnode
- oldVnode
应用场景
- 防抖
- 图片懒加载
- 一键copy功能
- 权限校验
vue 中diff算法的作用
用于虚拟dom渲染成真实dom的新旧VNode节点比较
特点:
- 比较只会在同层级进行,不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较
vue项目中有封装过axios吗?主要封装哪方面?
axios是基于Promise的Http网络请求库
封装:对超时时间、设置请求头、根据项目环境使用请求地址、错误处理等。
通过请求拦截器和响应拦截器实现
1 |
|
vue的SSR(Nuxt)
1、Vue SSR 是在SPA上进行改良的服务端渲染
2、通过Vue SSR渲染的页面,需要在客户端激活才能实现交互
3、Vue SRR将包含两部分:服务端渲染首屏,包含交互的SPA
SSR主要解决两个问题:
- 1、SEO
- 2、首屏呈现渲染
vue3做了哪些优化?
源码
1、源码管理(
monorepo
的方式维护)2、TS
性能
1、体积优化(tree-shanking)
2、编译优化(优化diff算法)
3、数据劫持优化(Proxy)
语法API(Composition API)
1、优化逻辑组织
2、优化逻辑复用
vue3性能提升主要通过哪几个方面体现
1、编译阶段
diff算法的优化
增加静态标记,在dff的过程中不会比较
静态提升
对不参与更新的元素,会做静态提升,只会被创建一次,在渲染的时直接复用,避免重复创建,优化运行时的内存占用
事件监听缓存
SSR优化
当静态内容大到一定量级时候,会用
createStaticVNode
方法在客户端去生成一个static node,这些静态node
,会被直接innerHtml
,就不需要创建对象,然后根据对象渲染
2、源码体积
移除了不常用的一些API,使用Tree-shanking,把没有用到的模块摇掉,打包的整体体积变小。
3、响应式系统
采用Proxy重写了响应式系统,因为Proxy可以对整个对象进行监听,所以不需要深度遍历。
为什么要用Proxy API代替defineProperty API
Object.defineProperty需要遍历属性进行监听,如果嵌套对象,需要深层监听,造成性能问题,不能监听对象属性的添加和删除,不能监听数组的变化(vue2通过重写数组方法实现监听)。
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的,可以监听数据的变化,但它不兼容ie
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!