一:Vue基础
1.Vue的基本原理
一个Vue实例创建 🔜 遍历data中的属性 🔜 通过defineProperty/proxy将其转为getter/setter 🔜
在内部追踪相关依赖 🔜 属性被访问和修改时通知变化。
每个组件实例都有响应的watcher程序实例 🔜 watcher会在组件渲染过程中把属性记为依赖 🔜 依赖被setter调用,watcher重新计算 🔜 相关联的组件更新。
2.双向数据绑定的原理
数据劫持 + 发布者-订阅者模式
(1)Observer对数据对象进行遍历 🔜 为其加上getter/setter 🔜 对象的某个值赋值会触发setter
🔜 监听到数据变化。
(2)compile解析模板指令,将变量替换成数据 🔜 初始化渲染页面视图 🔜 指令对应的节点绑定更新函数 🔜 添加监听数据订阅者 🔜 数据变动,收到通知,更新视图。
(3)Watcher是Observer和Compile之间通信的桥梁:
01:自身实例化时往属性订阅器(dep)中添加自己
02:自身有一个update方法
03:属性变动dep.notice()通知时,调用自身的update()方法,触发Compile中绑定的回调
(4) MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer监听自己的model数据变化 🔜 通过Compile来解析编译模板指令 🔜 最终利用Watcher搭起Observer和Compile之间通信的桥梁。
3、使用Object.defineProperty()来进行数据劫持有什么缺点
在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。
在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。
注解:通过下标方式修改数据或者给对象新增属性,都不能触发组件的重新渲染。
4、MVC和MVVM的区别
(1)MVC
通过分离Model、View和Controller的方式来组织代码结构。
View负责页面的显示逻辑 🔜 Model负责存储页面的业务数据,以及相应数据的操作🔜 Controller层是View层和Model层的纽带。
(2)MVVM
MVVM分为Model、View、ViewModel
01:Model代表数据模型,数据和业务逻辑都在Model层中定义;
02:View代表UI视图,负责数据的展示;
03:ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。
5、Computed和Watch的区别
(1)Computed:
01:支持缓存,只有依赖的数据发生了变化,才会重新计算
02:不支持异步,Computed中有异步操作,无法监听数据变化
03:值会默认走缓存,计算属性是基于响应式依赖进行缓存的,基于data或父组件传递过来的props中的数据进行计算。
04:一个属性依赖其余属性,一般会使用computed
05:computed属性的属性值是函数,默认使用get方法,函数的返回值就是属性的属性值。?
(2)Watch:
01:不支持缓存,数据变化时,就会触发相应的操作
02:支持异步监听
03:监听接收两个参数,一个是最新的值,一个是变化之前的值
04:监听数据必须是data或父组件传递过来的prop
05:immedate:组件立即触发回调函数 deep:深度监听(deep无法监听数组和对象内部的变化?)
6、Computed和Methods的区别?
可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
不同点:
- computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
- method 调用总会执行该函数。
7:slot是什么?有什么作用?原理是什么?
slot又名插槽 🔜 Vue的内容分发机制 🔜 组件内部的模板引擎使用slot元素作为承载分发内容的出口。
slot分为三类:默认插槽、具名插槽、作用域插槽:
(1)默认插槽:匿名插槽,slot没有指定name属性值时一个默认显示的插槽,一个组件内只有一个默认插槽。
(2)具名插槽:带有name属性的slot 🔜 一个组件可以出现多个具名插槽
(3) 作用域插槽:默认插槽和具名插槽的一个变体 🔜 其可以将子组件内部的数据传递给父组件。
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在 vm.$slot
中,默认插槽为 vm.$slot.default
,具名插槽为 vm.$slot.xxx
,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用 $slot
中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
8:如何保存页面当前的状态
(1)将状态存储在LocalStorage/SessionStorage中
(2)路由传值
(3)使用keep-alive来缓存页面?
9:常见的事件修饰符及其作用
(1).stop
:等同于 JavaScript 中的 event.stopPropagation()
,防止事件冒泡;
(2).prevent
:等同于 JavaScript 中的 event.preventDefault()
,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
(3).capture
:与事件冒泡的方向相反,事件捕获由外到内;
(4).self
:只会触发自己范围内的事件,不包含子元素;
(5).once
:只会触发一次。
10:v-show和v-if的原理及区别
(1)原理
v-if调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染。
v-show会生成v-node,render的时候也会渲染成真实节点 🔜 render过程中会在节点属性中修改show属性值,也就是常说的display
(2)区别
01:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐。
02:v-if有更高的切换消耗;v-show有更高的初始渲染消耗
03:v-if适合运营条件不大可能改变;v-show适合频繁切换。
11:data为什么是一个函数而不是对象
JavaScript中的对象是引用类型的数据 🔜 多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其余实例中的数据也会发生变化 🔜 会使得复用组件时,组件之间相互干扰。
数据以函数返回值的形式定义 🔜 每次复用组件时,就会返回一个新的data 🔜 每个组件都有自己的私有数据空间,其各自维护自己的数据,不会干扰其他组件的正常运行。
12:$nextTick原理及作用(具体引用?)
- 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在
nextTick()
的回调函数中。 - 在vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在
nextTick()
的回调函数中。
因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在 nextTick()
的回调函数中。
13:Vue data中某一个属性的值发生改变之后,视图会立即同步执行重新渲染吗?
不会立即同步执行重新渲染。
Vue实现响应式并不是数据发生变化时立即更新DOM 🔜 Vue更新DOM是异步执行的 🔜 侦听到数据变化,Vue将开启一个队列,并缓存在同一个事件循环中发生的所有数据变更。
同一个watcher被多次触发,只会被推入队列中一次 🔜 下一个事件的循环tick中,Vue刷新队列并执行实际(已去重)的工作。
14:自定义指令?
15:子组件可以直接改变父组件的数据吗?
不可以直接改变父组件的数据。
Vue提倡单向数据流,即父级props的更新会流向子组件,反过来则不行。这样可以防止意外改变父组件的状态,使得应用数据流变得难以理解。
只能通过$emit派发一个自定义事件,父组件接收到后,由父组件修改。
16:Vue的优点(背)
- 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十
kb
; - 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
- 双向数据绑定:在数据操作方面更为简单;
- 组件化:实现了
html
的封装和重用,在构建单页面应用方面有着独特的优势; - 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
- 虚拟DOM:
dom
操作是非常耗费性能的,不再使用原生的 dom
操作节点,极大解放 dom
操作,但具体操作的还是 dom
不过是换了另一种方式; - 运行速度更快:操作虚拟
dom
,就性能而言, vue
存在很大的优势。
17:assets和static的区别
相同点:
assets和static两个都是存放静态资源文件。项目中所需要的资源文件图片、字体图标、样式文件等都可以放在这两个文件下。
不同点:
assets中放的静态资源 🔜 npm run build 🔜 assets中静态资源会进行打包上传。
static中放置的静态资源 🔜 npm run build 🔜 不会走打包压缩格式化等流程。
建议:
template中的静态资源文件都可以放在assets中,走打包流程,减少体积。
引入的第三方资源文件可以放置在static中,因为第三方文件已经经过了处理。
18:什么是mixin(混入)
(1)mixin使我们能够为Vue组件编写可插拔和可重用的功能
(2)多个组件之间重用一组组件选项,例如生命周期hook,方法等,则可以将其编写为mixin,并在组件中简单引用它。
(3) 然后将mixin内容合并到组件中。mixin中定义的生命周期 🔜 执行时优先于组件自己的hook
19:Vue的模板编译原理
template无法被浏览器解析并渲染 🔜 需要将template转化成一个javaScript函数 🔜 浏览器执行这个函数渲染出对应的HTML元素 🔜 这个过程为模板编译。
三个阶段:解析parse 优化optimize 生成generate 🔜 最终生成可执行函数render
20:Vue的性能优化有哪些
(1)编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- SPA 页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
(2)SEO优化
- 预渲染
- 服务端渲染SSR
(3)打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
(4)用户体验
- 骨架屏
- PWA
- 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
21:mixin 和 mixins 区别
22:MVVM的优缺点
优点:
(1)分离视图(View)和模型(Model) 🔜 降低代码耦合 🔜 提高视图或者逻辑的重用性
(2)提高可测试性:ViewModel的存在可以帮助开发者更好地编写测试代码
(3)自动更新dom:双向绑定 🔜 数据更新后视图自动更新
缺点:
(1)Bug很难调试
(2)大的模块,model也会很大 🔜 长期持有,不释放内存 🔜 会花费更多的内存
(3)大型的图形应用程序,视图状态较多 🔜 ViewModel的构建和维护成本较高
二:Vue的生命周期
1:简单说一下Vue的生命周期
(1)beforeCreate(创建前):数据观测、初始化事件还未开始 🔜 data的响应式追踪、event\watcher还未被设置 🔜 不能访问data|computed|watch|methods中的数据。
(2)created(创建后):实例创建完成 🔜 实例上配置的options包括data、computed、watch、methods等都配置完成 🔜 渲染的节点还未挂载到DOM,所以不能访问$el属性。
(3)beforeMount(挂载前): 挂载开始之前被调用,相关的render函数首次被调用 🔜 实例完成以下的配置:编译模板 🔜 把data里面的数据和模板生成html 🔜 此时还未挂载html到页面上。
(4)mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。
实例完成以下配置:编译好的html内容替换el属性指向的dom对象 🔜 完成模板中的html渲染到html页面中 🔜 此过程进行ajax交互。
(5)beforeUpdate(更新前):响应式数据更新时调用。响应式数据更新 🔜 真实的DOM还没有被渲染。
(6)update(更新后):在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
(7)beforeDestroy(销毁前):实例销毁之前调用。实例仍然可用 🔜 this仍能获取到实例
(8) destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 activated
钩子函数。
2:Vue子组件和父组件执行的顺序
加载渲染过程:
1.父组件 beforeCreate
2.父组件 created
3.父组件 beforeMount
4.子组件 beforeCreate
5.子组件 created
6.子组件 beforeMount
7.子组件 mounted
8.父组件 mounted
更新过程:
1. 父组件 beforeUpdate
2.子组件 beforeUpdate
3.子组件 updated
4.父组件 updated
销毁过程:
1. 父组件 beforeDestroy
2.子组件 beforeDestroy
3.子组件 destroyed
4.父组件 destoryed
3:Created和mounted的区别
- created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
- mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
4:一般在哪个生命周期请求异步数据
可以在created、beforeMount、mounted中进行调用 🔜 三个钩子函数中,data已经创建 🔜 可以将服务端返回的数据进行赋值。
推荐在created钩子函数中调用异步请求:
(1)更快速获取到服务端数据,减少页面加载时间,用户体验更好。
(2)SSR不支持beforeMount、mounted钩子函数,放在created中有助于一致性。
5:keep-alive中的生命周期哪些
keep-alive 🔜 Vue提供的一个内置组件,用来对组件进行缓存 🔜 组件切换过程中,防止重复渲染dom
包裹了keep-alive的组件 🔜 多出两个生命周期:deactivated、activated 🔜 beforeDestroy 和destroyed 不会被触发,组件不会真正的销毁。
三:组件通信方式
1:props/$emit
父组件通过props向子组件传递数据,子组件通过$emit和父组件通信
(1)父组件向子组件传值,props使得父子组件之间形成了一个单向下行绑定,子组件的数据会随着父组件不断更新。
(2) 可以显示定义一个或一个以上的数据 🔜 接收的数据可以是各种数据类型 🔜 也可以传递一个函数。
(3)属性命名规则 🔜 驼峰形式
子组件向父组件传值
$emit绑定一个自定义事件 🔜 事件执行 🔜 参数传递给父组件 🔜 父组件通过v-on监听接收参数
2:eventBus事件总线($emit/$on)
eventBus事件总线适用于父子组件,非父子组件等之间的通信。
3:依赖注入(project/inject)
Vue中的依赖注入 🔜 层数很深的情况下进行传值,不用一层一层传递
project:钩子用来发送数据和方法
inject:钩子用来接收数据和方法
4:ref/$refs
实现父子组件之间的通信
ref:属性用于子组件 🔜 指向自组建的实例 🔜 通过实例访问子组件的数据和方法
5:$parent / $children
$parent 可以让组件访问父组件的实例
$children 可以让组件访问子组件的实例 🔜 不能保证顺序 🔜 数据不是响应式的
6:$attrs / $listeners
组件之间跨代通信
四:路由
1:Vue-Router的懒加载如何实现
const List = () => import('@/components/list.vue')
2:路由的hash和history模式的区别(?)
(1)hash模式
简介: 开发中的默认模式 🔜 URL中带着# 🔜 http://www.abc.com/#/vue 🔜 hash值是#/vue
特点:hash值出现在URL里 🔜 不会在HTTP请求中 🔜 改变hash值,不会重新加载页面
原理:hash模式的主要原理 🔜 onhashchange()事件
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
(2)history模式
简介:URL中没有# 🔜 传统的路由分发模式
特点:history模式需要后台支持 🔜 后台没有正确配置,返回404
API:其api分为两部分,切换历史状态和修改历史状态
3:如何获取页面的hash变化
(1)监听$route的变化
// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
(2)window.location.hash读取#值
window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。
4:$route和$router的区别
(1)$ route是“路由信息对象” 🔜 path、params、hash、query等路由信息参数
(2)$router是“路由实例”对象 🔜 包括了路由的跳转方法,钩子函数等
五:VueX
1:VueX的原理
状态管理模式 🔜 应用核心:store(库)🔜 store基本上就是一个容器,包含着应用中的大部分状态。
(1)状态存储是响应式
(2)显式的提交(commit)mutation 🔜 改变store中的状态
(1)核心流程中的主要功能
- 组件会触发(dispatch)一些事件或动作
- 组件发出动作,目的是获取/改变数据 🔜 VueX数据是集中管理的,不能直接改变数据 🔜需要(Commit)到Mutations中
- Mutation中更改state中的数据
- State中的数据改变 🔜 重新渲染(Render)到Vue Component中
(2)各模块在核心流程中的主要功能(Vuex有哪几种属性)
- Vue Components:Vue组件
- dispatch:操作行为触发防范,唯一能执行action的方法
- actions:操作行为处理模块,包含同步 / 异步操作,支持多个同名方法,按照注册的顺序依次触发。
- commit:状态改变提交操作方法。对mutation进行提交 🔜 唯一能执行mutation的方法
- mutations:状态改变操作方法。Vuex修改state的唯一推荐方法 🔜只能进行同步操作 🔜 方法名全局唯一。
- state:页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一。
- getters:state对象读取方法。
2:Vuex中action和mutation的区别
(1)mutation
1):一系列的同步函数,修改state中变量的状态。
2):需要通过commit来提交需要操作的内容。
(2)action
1):Action可以包含任意异步操作
2):Action提交的mutation,而不是直接变更状态
(3)不同点
1):Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求
2):必须同步执行;Action:可以异步,但不能直接操作State
3):在视图更新时,先触发actions,actions再触发mutation
4):mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters。
3:Vuex和localStorage的区别
(1)最重要的区别
- vuex存储在内存中
- localstorage则以文件的方式存储在本地 🔜 只能存储字符串类型的数据
- Vuex能做到数据的响应式,localstorage不能
(2)永久性
- 刷新页面时,Vuex存储的值会丢失,localstorage不会
4:Vuex和单纯的全局对象有什么区别