Vue特点
- 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;
- 双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
- 组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;
- 数据驱动:通过特殊的语法将数据与页面元素绑定,无需手动操作dom,只要改变数据就能使dom同步更新
- 遵循MVVM(Model–view–viewmodel)模型的框架
- 代码简洁体积小,运行效率高,适合移动PC端开发;
详细说说MVVM
全称: Model-View-ViewModel
- Model代表数据模型
- View代表UI组件
- ViewModel将Model和View关联起来
MVVM
:数据绑定到viewModel层并自动将数据渲染到页面中,视图变化时通知viewModel层更新数据
MVC
:Controller负责将model的数据用View显示出来
Vue是如何实现响应式数据的呢?(响应式数据原理)❗
-
2.x版本通过
Object.defineProperty
数据劫持和发布订阅模式实现- 当创建Vue实例时,Vue会遍历data选项的属性,通过
Object.defineProperty()
为属性添加getter/setter对数据的读取进行劫持,并在内部追踪相关依赖,在属性被访问和修改时通知变化
- 当创建Vue实例时,Vue会遍历data选项的属性,通过
-
3.x版本通过Proxy代理对象实现
为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
-
Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
-
proxy可以劫持整个对象并返回一个新的对象。proxy不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性
Vue的事件绑定原理
- 原生事件绑定通过addEventListener绑定给真实元素
- 原生
DOM
的绑定:Vue在创建真实DOM时会调用createElm
,默认会调用invokeCreateHooks
。会遍历当前平台下相对的属性处理代码,其中就有updateDOMListeners
方法,内部会传入add()
方法
- 原生
- 组件事件绑定是通过Vue自定义的$on方法实现的
v-model中的实现原理及如何自定义v-model
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
- v-bind绑定一个value属性;
- v-on指令给当前元素绑定input事件。
自定义:自己写 model
属性,里面放上 prop
和 event
为什么Vue采用异步渲染呢?
Vue
是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue
会在本轮数据更新后,在异步更新视图。核心思想 nextTick
。
Vue渲染过程:
调用new watcher函数,监听数据变化;当数据变化时,Render函数执行生成VNode对象;调用patch()方法,对比新旧VNode对象;通过DOM diff算法添加、修改、删除真正的DOM元素。
虚拟 DOM 实现原理?
虚拟 DOM 的实现原理主要包括以下 3 部分:
-
用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
-
diff 算法——比较两棵虚拟 DOM 树的差异;
-
pach 算法——将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
了解nextTick吗?
异步方法,异步渲染最后一步,与JS事件循环联系紧密。主要使用了宏任务微任务(setTimeout
、promise
那些),定义了一个异步方法,多次调用nextTick
会将方法存入队列,通过异步方法清空当前队列。
JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件,哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
Vue生命周期
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM-渲染、更新-渲染、卸载等一系列的过程,我们称这是 Vue 的生命周期。
在生命周期的不同阶段调用对应的钩子函数可以实现组件数据管理和DOM渲染两大重要功能。
-
beforeCreate:创建前,此阶段为实例初始化之后,this指向创建的实例,此时的数据观察事件机制都未形成,不能获得DOM节点。data,computed,watch,methods 上的方法和数据均不能访问。
可以在这加个loading事件。 -
created:创建后,此阶段为实例已经创建,完成数据(data、props、computed)的初始化导入依赖项。
可访问 data computed watch methods 上的方法和数据。
初始化完成时的事件写在这里,异步请求也适宜在这里调用(请求不宜过多,避免白屏时间太长)。
可以在这里结束loading事件,还做一些初始化,实现函数自执行。
未挂载DOM,若在此阶段进行DOM操作一定要放在Vue.nextTick()的回调函数中。 -
beforeMount:在挂载之前调用,相关
render
函数首次被调用
beforeMount这个阶段是过渡性的,一般一个项目只能用到一两次。 -
mounted:挂载,完成创建vm.$el,和双向绑定
完成挂载DOM和渲染,可在mounted钩子函数中对挂载的DOM进行操作。可在这发起后端请求,拿回数据,配合路由钩子做一些事情。
-
beforeUpdate:数据更新前,数据驱动DOM。
在数据更新后虽然没有立即更新数据,但是DOM中的数据会改变,这是vue双向数据绑定的作用。可以在这个钩子中进一步的更改状态,不会触发重渲染。可在更新前访问现有的DOM,如手动移出添加的事件监听器。
-
updated:数据更新后,完成虚拟DOM的重新渲染和打补丁。
组件DOM已完成更新,可执行依赖的DOM操作。
注意:不要在此函数中操作数据(修改属性),会陷入死循环。 -
activated:在使用vue-router时有时需要使用
<keep-alive></keep-alive>
来缓存组件状态,这个时候created钩子就不会被重复调用了。
如果我们的子组件需要在每次加载的时候进行某些操作,可以使用activated钩子触发。 -
deactivated:
<keep-alive></keep-alive>
组件被移除时使用。 -
beforeDestroy:销毁前,在实例销毁之前调用。实例仍然完全可用。
我们可以在这时进行善后收尾工作,比如清除计时器。可做一些删除提示,如:您确定删除xx吗? -
destroyed:在实例销毁之后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
v-if
和 v-show
区别
v-if
是“真正”的条件渲染,在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。- 使用
v-show
元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 - v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。
- 需要非常频繁地切换,则使用 v-show 较好
- 在运行时条件很少改变,则使用 v-if 较好
v-for
和 v-if
为什么不能连用,如何解决?
v-for
会比 v-if
的优先级更高,所以导致每循环一次就会去v-if一次,而v-if是通过创建和销毁dom元素来控制元素的显示与隐藏,所以就会不停的去创建和销毁元素,造成页面卡顿,性能下降。
使用computed解决
注:Vue3中 v-if
的优先级更高
组件中的data为什么是一个函数?
避免组件中数据互相影响。组件是可复用的,data若为对象,则所有复用的组件都是引用同一个数据;data若为函数,每次函数都会创建一个新的数据,从而使每个组件数据相互独立
Vue组件通信
-
父子组件通信
-
父->子props,子->父
$on
、$emit
-
获取父子组件实例
$parent
、$children
-
Ref
获取实例的方式调用组件的属性或者方法 -
Provide
、inject
官方不推荐使用,但是写组件库时很常用
-
-
兄弟组件通信
-
Event Bus 实现跨组件通信
Vue.prototype.$bus = new Vue
-
Vuex
-
-
跨级组件通信
-
Vuex
-
$attrs
、$listeners
-
Provide
、inject
-
插槽与作用域插槽的区别
-
创建组件虚拟节点时,会将组件儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类
{a:[vnode],b[vnode]}
-
渲染组件时会拿对应的
slot
属性的节点进行替换操作。(插槽的作用域为父组件) -
作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。
-
普通插槽渲染的作用域是父组件,作用域插槽的渲染作用域是当前子组件
vue-router 有哪几种导航钩子?
第一种:是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件
怎么定义 vue-router 的动态路由? 怎么获取传过来的值?
在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id。
vue-router的两种模式
- hash模式
- 即地址栏 URL 中的 # 符号
- 当#后面的hash发生变化,不会导致浏览器向服务器发出请求,浏览器不发出请求就不会刷新页面,会触发hasChange这个事件,通过监听hash值的变化来实现更新页面部分内容的操作
- history模式
- 主要使用HTML5的pushState()和replaceState()这两个api来实现的
- history.pushState(state, title[, url]):向当前浏览器会话的历史堆栈中添加一个状态(state)记录,不会造成 hashchange 事件调用,即使新的URL和之前的URL只是锚的数据不同
- history.replaceState():把当前页面在浏览器历史中的记录修改
- 浏览器地址栏会变成你传的地址,而页面并不会重新载入或跳转
vue-router实现路由懒加载( 动态加载路由 )
- vue异步组件技术 ==== 异步加载,vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 .但是,这种情况下一个组件生成一个js文件。
- 路由懒加载(使用import)。
- webpack提供的require.ensure(),vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
action
和 mutation
的区别
mutation
是同步更新,$watch
严格模式下会报错action
是异步操作,可以获取数据后调用mutation
提交最终数据
在vuex中为什么要去区分action 和 mutation?
为了能用 devtools
追踪状态变化
什么是异步组件,什么是动态组件?
- 动态组件就是 component 组件 ,是指不同组件之间进行动态切换。
- 所谓的异步组件就是通过import或者require导入的vue组件。引入一些大的比如图表什么的会用到。在大型应用中,可能需要将应用分割为小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,vue运行以一个工厂函数的方式定义组件,这个工厂函数会异步解析组件。vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存供未来渲染。
为什么要使用异步组件?
- 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。
- 核心就是包组件定义变成一个函数,依赖
import()
语法,可以实现文件的分割加载
谈谈对keep-alive的了解
keep-alive
是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
keep-alive
可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。
常用的2个属性 include/exclude
,2个生命周期 activated
, deactivated
vue2 和 vue3 的区别,做了哪些更改?
-
vue2和vue3响应式原理发生了改变
-
Vue3支持碎片(Fragments),就是说在组件可以拥有多个根节点。
-
数据和方法的定义
- Vue2使⽤的是选项类型API(Options API)
- Vue3使⽤的是组合式API(Composition API)
-
生命周期钩子
vue2 vue3 说明 beforeCreate setup() 组件创建之前 created setup() 组件创建完成 beforeMount onBeforeMount 组件挂载之前 mounted onMounted 组件挂载完成 beforeUpdate onBeforeUpdate 数据更新,虚拟DOM打补丁之前 updated onUpdated 数据更新,虚拟DOM渲染完成 beforeDestroy onBeforeUnmount 组件销毁之前 destroyed onUnmounted 组件销毁后 若组件被
<keep-alive>
包含,则多出下面两个钩子函数:- onActivated():被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
- onDeactivated():比如从 A组件,切换到 B 组件,A 组件消失时执行。
-
父子通信
Vue2 常见的缺陷:
- 从开发维护的角度考虑:Vue2 使用 Flow.js 做类型校验,但是现在 Flow.js 已经停止维护;并且 Option API 在组织代码较多组件时不易维护。
- 从社区的二次开发难度:Vue2 内部运行时直接执行浏览器 API,这会给 Vue2 的跨端带来问题。只能在 Vue 源码中进行相应的处理(例如 Vue2 中存在 Weex 的文件夹)
- 从日常使用的角度:Vue2 的响应式并不是真正意义上的代理,而是基于 Object.defineProperty() 实现的。这个 API 是对某个属性进行拦截,因此有很多缺陷(例如无法监听删除数据)
Vue3 的新特性
- Vue3 中使用 Proxy API 对数据进行代理(这个 API 是真正的代理,不过存在一些兼容性问题)
- Vue3 使用最近流行的 monorepo 管理方式,把响应式、编译和运行时全部独立了(Vue2 内部所有模块都糅合在一起)
- Vue3 全部模块使用 TypeScript 重构
- Vue3 使用 Composition API(组合式 API)
- Vue3 内置了 Fragment、Teleport、Suspense 三个新组件
vue3如何创建响应式数据?
- 在 Vue2 中,我们只需要把数据放入 data 函数,Vue2 会遍历 data 中的所有属性,使用 Object.defineProperty 把每个 property 全部转为 getter/setter,getter 用来收集依赖,setter 用来执行 notify,发布更新事件。Vue2 对每个属性创建一个 Dep 对象,作为订阅发布模式的中间机构来收集依赖。Vue 追踪这些依赖,在其被访问和修改时通知变更。
- Vue3 中引入了 ref,reactive 来创建响应式数据
- ref 的作用就是将一个原始数据类型转换成一个响应式数据,原始数据类型共有 7 个,分别 是:String、Number、BigInt、Boolean、Symbol、Undefined、Null
- 当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加(如以上代码中在 template 中可以直接使用 title,在 setup 函数中使用 title.value 来修改其值)
- reactive 的作用就是将一个对象转换成一个响应式对象
- toRefs 的作用是将一个响应式对象转化为一个普通对象,把其中每一个属性转化为响应式数据
- 为什么需要 toRefs ?
reactive 转化的响应式对象在销毁或展开(如解构赋值)的时候,响应式特征就会消失。为了在展开的同时保持其属性的响应式特征,我们可以使用 toRefs 。
说说你对 SPA 单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
-
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理; -
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一> 加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所> 有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
使用过 Vue SSR 吗?说说 SSR?
-
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
-
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
谈谈你对webpack的理解?
是一个对资源进行模块化和打包的工具,处理每个模块的 import 和 export
追问:资源是指什么?
回答:Js,css,png图片等
追问:如果有个二进制文件,它是资源么?webpack怎么使它模块化?
回答:是。需要有一个对应的loader来处理(我是想到了vue-loader等)