1.前后端渲染、前后端路由及前后端分离的概念
(1)后端路由、后端路由阶段
后端渲染:在服务器端直接生成渲染好的HTML页面并返回到客户端展示
后端路由:服务端根据不同的URL地址分发不同的资源文件
缺点:整个页面的模块都是由后端人员开发和维护、HTML标签,数据和对应逻辑混合在一起难以编写和维护
(2)前后端分离阶段
随着ajax的出现,后端只需要提供api来返回数据,前端通过ajax技术获取到数据然后使用javascript将数据渲染到页面中
(3)前端渲染、前端路由阶段
前端渲染:前端拿到后端提供的数据通过javascript在页面中渲染数据
前端路由:当用户在浏览器中输入URL地址时,会从静态资源服务器获取到所有的HTML+CSS+JAVASCRIPT资源,然后根据不同的URL地址获取到对应的资源,后端提供获取数据的api,前端通过ajax获取到数据并使用javascript渲染到页面中
vue的服务端渲染SSR
概念:vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程
优点:
- 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
- 更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
缺点:
- 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
- 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
2.vue2、vue3响应式原理
vue2:通过数据劫持,发布订阅者模式实现
-
首先在data中定义变量时,VUE内部会创建一个dep对象(dependency:依赖),每一个变量对应一个依赖,在依赖里面会通过对象里面的defineProperty函数重新获取和设置对应的值
//1.每定义一个变量,就会创建一个对应的依赖 const dep = new Dep() //2.通过对象里面的defineProperty方法劫持数据 Object.defineProperty(data,key,{ //3.劫持数据的读取 get(){ if(Dep.target){ dep.addSub(Dep.target) } return val }, //4.劫持数据的修改 当监听到数据改变时会调用dep中定义的notify方法通知watcher修改数据 set(newValue){ if(newValue === bal){ return }else{ val = newValue dep.notify() } } }) -
发布订阅者模式:定义一个dep类,将页面中用到的data中的数据的区域生成订阅,并添加到订阅者数组中
class Dep(){ constroutor(){ this.subs = []//订阅的数据 } addSub(sub){ this.subs.push(sub)//添加订阅的数据(template中使用的data中的数据) } notify(){//遍历订阅数组 修改数据 this.subs.forEach(sub=>{ sub.upodate() }) } }原理图解:
面试回答范例:
- 实现了一个数据监听器Observer,能够对数据对象的所有属性进行监听,如果有变动可拿到最新的值并通知订阅者
- 实现了一个指令监听器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现了一个监听器Watcher,作为连接Observer和Complie的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
在new Vue的时候,在Observer中通过Object.defineProperty()劫持数据,代理所有数据的getter和setter属性,每次触发setter会通过Dep来通知Watcher,Watcher作为Observer和Complie之间的桥梁,当Observer监听到数据发生改变时会通过update来通知Complie更新视图。而Complie通过watcher订阅对应数据,绑定更新函数,通过Dep来添加订阅者,达到双向绑定。
vue3:
通过Proxy(代理):拦截对data任意属性的任意操作,包括属性值的读写,属性的添加,属性的删除等...
通过Reflect(反射):动态的对被代理对象的相应属性进行特定操作
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
3.vue-router两种传参方式
route(当前页面的路由)
- 通过在router.js文件中配置path(path:"/index./:id") 动态传递参数,组件中通过this.$route.params.id获取参数
- 在router-link标签中传递参数==><router-link to="{path:"",params:{id:0}}"/> 通过this.$route.params获取
4.v-if和v-for在一起使用的弊端及解决方法
由于v-for比v-if优先级高,所以每循环一次,都会执行一次v-if,而v-if是通过创建和销毁dom元素来控制元素的显示与隐藏的,所以就会不停的创建和I销毁元素,进而可能造成页面的卡顿,降低性能。
解决方法:在v-for外层或内层包裹一个元素来使用v-if
5.vue组件间传值的方式
父子组件通信:
-
prop/$emit -
ref和$parent/$childrenref:如果在普通dom元素上使用,引用指向的就是dom元素,如果用在子组件上引用就指向组件实例
$parent/$children:访问父/子实例
-
EventBus($emit / $on)通过一个空的VUE实例作为中央事件总线(事件中心)用来触发事件和监听事件
-
vuex
隔代组件通信:
-
EventBus($emit / $on) -
$attr/$listeners$attr:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过v-bind="$attrs"传入内部组件。通常配合 inheritAttrs 选项一起使用。$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"传入内部组件 -
provide/inject祖先组件中通过provider来提供变量。然后在子孙组件中通过inject来注入变量。 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
-
vuex
兄弟组件通信:
EventBus($emit / $on)- vuex
6.父组件如何获取子组件的属性和方法
VUE中通过在子组件上定义ref引用来获取子组件的属性和方法
// 这里是父组件
<templete>
<child ref="child"/>
</templete>
<script>
method: {
getChild () {
this.$refs.child.属性名(方法名)
}
}
7.watch和computed的区别
computed:计算属性,依赖其他属性值,并且computed的值有缓存,只有当他依赖的属性值发生改变时,下一次获取computed的值时才会重新计算
watch:监听器,监听数据,并通过回调函数操作数据,当数据发生改变时会触发回调
当需要进行数值计算,并且依赖于其他数据时使用computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算
当需要在数据变化时执行异步回调或开销较大的操作是时使用watch。使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
8.vue中的事件修饰符主要有哪些,作用分别是
.stop:阻止事件冒泡
.native:绑定原生事件(如对子组件的点击事件)
.once:事件只执行一次
.self:将事件绑定到自己身上,相当于阻止事件冒泡
.pervent:阻止默认事件
.caption:用于事件捕获
9.你对SPA单页应用的理解,它的优缺点
概念:SPA(single-page application)仅在web页面初始化时加载相应的HRTML,CSS和JAVASCRIPT。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转,转取而代之的是利用路由机制实现HTML内容的变换,避免页面的重新加载
优点:
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- 基于第一点,SPA相对于服务器压力小
- 前后端分离,架构清晰,前端进行交互逻辑,后端负责数据的处理
缺点:
- 初次加载耗时久:为实现单页web应用功能及其显示效果,需要在加载页面的时候将javascript,css统一加载,部分按需加载
- 前进后退路由管理:由于单页应用在一个页面中显示所有内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
- SEO难度大:由于所有内容都在一个页面中动态替换显示,所以seo劣势
10.v-show和v-if区别
v-if:正真的条件渲染,因为他会确保在切换过程中条件快内的事件监听器和子组件适当的被销毁和重建。
v-show:不管初始条件是什么,元素都会被渲染,只是不显示
所以v-if适用于在运行时很少改变条件,不需要频繁切换条件的场景,v-show适用于需要频繁切换条件的场景
11.class和style如何动态绑定
class:
-
对象语法
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div> data: { isActive: true, hasError: false } -
数组语法
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div> data: { activeClass: 'active', errorClass: 'text-danger' }
style:
-
对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> data: { activeColor: 'red', fontSize: 30 } -
数组语法
<div v-bind:style="[styleColor, styleSize]"></div> data: { styleColor: { color: 'red' }, styleSize:{ fontSize:'23px' } }
12.直接给一个数组项赋值,vue能否检测到
由于javascript的限制,Vue不能检测到以下数组的变化
- 利用索引直接设置一个数组项时,vm.items[indexOfItem] = newValue
- 修改数组的长度,例如:
vm.items.length = newLength
为解决第一个问题
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
为解决第二个问题
// Array.prototype.splice
vm.items.splice(newLength)
13.谈谈你对Vue生命周期的理解
概念:Vue实例有一个完整的生命周期,从开始创建、初始化数据、编译模板、挂载dom ->渲染,更新 -> 渲染,卸载等一些列过程,我们称这是VUE的生命周期。VUE的所有功能都是围绕着它的生命周期进行的,在生命周期的不同阶段对应的钩子函数可以实现组件数据管理和dom渲染两大功能,生命周期中有多个钩子函数,在控制整个vue实例的过程中更容易形成良好的逻辑
各生命周期的作用:
| 生命周期 | 描述 |
|---|---|
| beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
| created | 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用 |
| beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
| mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
| beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
| update | 组件数据更新之后 |
| activited | keep-alive 专属,组件被激活时调用 |
| deactivated | keep-alive 专属,组件被销毁时调用 |
| beforeDestory | 组件销毁前调用 |
| destoryed | 组件销毁后调用 |
14.vue父子组件生命周期顺序
-
渲染过程:
父组件beforeCreate() -> 父组件created() -> 父组件beforeMount() -> 子组件beforeCreate() ->子组件created() -> 子组件beforeMount() -> 子组件mounted() -> 父组件mounted()
-
父组件更新过程:
父组件beforeUpdate() -> 父组件updated()
-
子组件更新过程:
父组件beforeUpdate() -> 子组件beforeUpdate() -> 子组件updated() -> 父组件updated()
-
销毁过程:
父组件beforeDestroy() -> 子组件beforeDestroy() -> 子组件destroyed() -> 父组件destroyed()
15.父组件能监听到子组件的生命周期吗
父组件可以通过@hook:生命周期函数名 来监听子组件的生命周期
<template>
<child
@hook:mounted="getChildMounted"
/>
</template>
<script>
method: {
getChildMounted () {
// 这里可以获取到子组件mounted的信息
}
}
16.在什么阶段才能访问操作dom
在钩子函数mounted被调用前,VUE已经将编译好的模板挂载到页面上,所以在mounted中可以访问操作dom。
VUE各生命周期具体适应场景:
- beforeCreate:创建前,此阶段为实例初始化之后,this指向创建的实例,此时的数据观察事件机制都未形成,不能获得DOM节点。 data,computed,watch,methods 上的方法和数据均不能访问。 可以在这加个loading事件。
- created:创建后,此阶段为实例已经创建,完成数据(data、props、computed)的初始化导入依赖项。 可访问 data computed watch methods 上的方法和数据。 初始化完成时的事件写在这里,异步请求也适宜在这里调用(请求不宜过多,避免白屏时间太长)。 可以在这里结束loading事件,还做一些初始化,实现函数自执行。 未挂载DOM,若在此阶段进行DOM操作一定要放在Vue.nextTick()的回调函数中。
- beforeMount:挂载前,虽然得不到具体的DOM元素,但vue挂载的根节点已经创建,下面vue对DOM的操作将围绕这个根元素继续进行。 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:销毁后,当前组件已被删除,销毁监听事件,组件、事件、子实例也被销毁。 这时组件已经没有了,无法操作里面的任何东西了。
17.谈谈你对keep-alive的理解
keep-alive是VUE内置的一个组件,可以使被包裹的组件保留状态,避免重新渲染
特性:
- 一般结合路由和动态组件使用,用于缓存组件
- 提供include和exclude属性,两者都支持字符串和正则表达式,include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高
- 对应两个钩子函数activated和deactivated,当组件被激活时触发钩子函数activated,当组件被移除时,触发钩子函数deactivated
18.组件中的data为什么是一个函数
组件中的data为什么必须是一个函数,并且返回一个对象,new Vue实例里data可以直接是一个对象
// data
data() {
return {
message: "子组件",
childName:this.name
}
}
// new Vue
new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})
因为组件是用来复用的,且js里对象是引用关系,如果组件中data是一个对象,那么这样作用域没有隔离,子组件中的data属性会相互影响,如果组件中data选项是一个函数,那么每个实例都可以维护一份被返回对象的独立的拷贝,组件实例之间的data属性值不会相互影响。而new Vue实例是不会被复用的,因此不存在引用对象的问题
19.v-model的原理
在VUE项目中主要使用v-model指令在表单input、textarea、select等元素上创建双向数据绑定。v-moded在内部为不同的输入元素使用不同的属性并抛出不同的事件
- text和textarea元素使用value属性和input事件
- checkbox和radio使用checked属性和change事件
- select字段将value作为prop并将change作为事件
以input表单元素为例
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model默认会利用名为value的prop和名为input的事件
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
20.谈谈你对vuex的理解
定义:vuex是vue的状态管理工具。每一个vuex应用的核心就是store(仓库)。store作为一个容器包含应用中大部分状态(state)
特性:
- vuex的存储状态时响应式的。当vue组件从store中读取状态的时候,若store中的状态发生改变那么相应的组件也会得到高效更新
- 改变store中状态的唯一途径就是显示提交mutiation(commit).这样使得我们可以方便的追踪每一个状态的变化
模块:
- state:定义了应用状态的数据结构,可以在这里设置默认的初始化状态
- getters:允许组件从store中获取数据,mapGetters辅助函数将store中的getter映射到局部的计算属性
- mutation:唯一可以改变store中状态的方法,且必须是同步函数
- actions:用于当修改state的操作比较复杂、修改多个状态时处理这些逻辑再提交mutation
- module:允许将单一的store拆分为多个store且同时保存在单一的状态树中
21.vue-router路由模式分类
- hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
- history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
- abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
22.hash和history路由模式的实现原理
-
hash模式实现原理
location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':www.word.com*#search*
hash模式实现特性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
- hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
- 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
- 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
-
history模式实现原理
HTML5 提供了 History API 来实现 URL 的变化。history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录
history模式实现特性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
- 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
23.什么是MVVM
Model–View–ViewModel (MVVM) 是一个软件架构设计模式,是一种简化用户界面的事件驱动编程方式。
view:view层是视图层,也就是用户界面。前端主要由HTML和CSS来构建
model:model层是数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对前端来说就是后端提供的api接口
view-model:view-model层是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
24.Proxy和Object.defineProperty优劣对比
Proxy优势:
- Proxy可以直接监听整个对象而不是属性
- Proxy可以直接监听数组的变化
- Proxy有多达13种拦截方法,不限于apply,deleteProperty等是Object.defineProperty不具备的
- Proxy返回的是一个新对象,我们可以只操作新的对象。而Object.defineProperty只能遍历对象属性直接修改
- Proxy作为新标准受到浏览器厂商重点持续的性能优化
Object.defineProperty优势:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平
25.虚拟dom的优缺点
优点:
- 保证性能下限:框架的虚拟dom需要适配任何上层API可能产生的操作,它的一些dom操作的实现必须是普适的,所以它的性能并不是最优的。但比起粗暴的直接操作dom要好很多,因此框架的虚拟dom至少可以保证你不需要手动优化的情况下,依然可以提供不错的性能,即保证性能的下限
- 无需手动操作dom:我们不需要手动操作dom,只需要写好view-model的代码逻辑,框架会根据虚拟dom和数据的双向绑定帮我们以可预期的方式更新视图,极大的提高开发效率
- 跨平台:虚拟dom本质是javascript对象,而dom与平台强相关,相比之下虚拟dom可以进行更加方便的跨平台操作,例如服务器渲染,weex开发等等
缺点:
无法进行极致优化:虽然虚拟dom+合理的优化,足以应对大部分应用的性能需求,但在一些性能要求极高的应用中虚拟dom无法进行针对性的极致优化
26.虚拟dom的实现原理
- 用javascript对象模拟真实的dom树,对真实dom进行抽象
- diff算法(比较两颗虚拟dom树的差异)
- pach算法(将两个虚拟dom对象的差异应用到真正的dom树中)
27.vue中key的作用
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速**「更准确」:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。「更快速」**:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
28.你对vue项目进行过哪些优化
- 代码层面
- v-if和v-show区分使用场景
- computed和watch区分使用场景
- v-for遍历必须为item添加key,且避免同时使用v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 第三方插件按需引入
- 优化无限列表功能
- 服务端渲染SSR、预渲染
- webpackc层面
- webpack对图片进行压缩
- 减少ES6转化为ES5的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的css
- 优化 SourceMap
- 构建结果输出分析
- Vue项目的编译优化
- 基于web技术的优化
- 开启gizp压缩
- 浏览器缓存
- CDN的使用
29.你对vue3的有什么了解吗
Vue3支持Vue2的大部分特性并且更好的支持 typescript
Vue3中设计了一套强大的 Composition API (组合api)代替Vue2中的 option API(选项api),复用性更强
Vue3中使用Proxy配合Reflect代替defineProperty实现数据的响应式
Vue3中重写了虚拟dom的实现和Tree-Shaking
Vue3的新能进一步提升:打包大小减少41%,初次渲染快55%,更新渲染快133%,内存减少54%
新的响应式api:ref、reactive……
新的生命周期
新的脚手架工具:Vite
新的组件:
- Fragment - 文档碎片
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的loading界面
……