Vue专篇
今天浅整理一下vue专篇面试题
一、 什么是 MVVM?比之 MVC 有什么区别?
- MVC、MVVM 是两种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化我们的开发效率。
1.MYYM
-
MVVM即
Model-View-ViewModel
的简写。即模型-视图-视图模型 -
模型(
Model
)指的是后端传递的数据 -
视图(
View
)指的是所看到的页面 -
视图模型(
ViewModel
)是mvvm模式的核心,它是连接view和model的桥梁 – vue也充当这个角色分为以下两种方法:
-
将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面,实现的方式是:数据绑定
-
将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据,实现的方式是:DOM事件监听
-
这两个方向都实现的,我们称之为数据的双向绑定
-
2.MVC
- MVC是
Model-View-Controller
的简写。即模型-视图-控制器 - M和V指的意思和MVVM中的M和V意思一样
- C即Controller指的是页面业务逻辑,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新
- MVC 的思想:一句话描述就是
Controller
负责将Model
的数据用View
显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。 - 使用MVC的目的:将M和V的代码分离
- MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下
3.MVC和MVVM的区别
- MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念
- ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。
- MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性
- MVVM主要解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验
- MVVM 实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)
4.拓展:Vue 并没有完全遵循 MVVM 的思想
- 严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
二、说说你对vue的理解
-
vue是一个用于创建用户界面的开源的JavaScript框架,也是一个创建单页应用的web应用框架
-
vue是一套用于构建用户界面的渐进式MVVM框架
- 渐进式:强制主张最少
-
Vue.js 实现了一套声明式渲染引擎,并在runtime或者预编译时将声明式的模板编译成渲染函数,挂载在观察者 Watcher 中,在渲染函数中(touch),响应式系统使用响应式数据的getter方法对观察者进行依赖收集(Collect as Dependency),使用响应式数据的setter方法通知(notify)所有观察者进行更新,此时观察者 Watcher 会触发组件的渲染函数(Trigger re-render),组件执行的 render 函数,生成一个新的 Virtual DOM Tree,此时 Vue 会对新老 Virtual DOM Tree 进行 Diff,查找出需要操作的真实 DOM 并对其进行更新
三、什么是双向绑定(Vue2必会)
- Vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时* 发布消息给订阅者*,触发相应的监听回调
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5Ql4BuW-1661400413821)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a2da161e3eb54f8483ca0b0df506d47f~tplv-k3u1fbpfcp-zoom-1.image)]
-
简单分析一下流程:一个 JS 对象作为数据传给Vue 实例时,Vue 会遍历它的属性,并使用Object.defineProperty 将它们转化为 getter/setter,Vue在内部会对它们监听,在属性被访问或修改时及时进行通知
-
图上四个位置分别的作用
- Observer :数据监听器,对Vue的数据对象中所有属性进行监听,一旦属性发生改变,将最新值传给watcher(订阅者)
- Compile :指令编译器,对每个DOM元素节点指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- Dep :消息订阅器,内部有一个维护的数组用来收集订阅者(Watcher),如果数据发生变动,则触发notify 函数,然后调用wacher订阅者的 update 方法
- Watcher :订阅者,连接observer 与compile,获取属性变化的通知,并将数据及时更新到视图当中
-
详细步骤:
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情 是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发 Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者, 通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令, 最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视 图更新;视图交互变化(input)->数据model变更的双向绑定效果。
四、v-show和v-if的区别
跳过了简单的
-
v-show的转换不会触发组件生命周期,但是有更高的初始渲染消耗(适合非常频繁切换条件的场景)
-
v-if --有更高的切换消耗(适合不需要频繁切换的)
- 由FALSE变为true时,会触发beforecreate,create,beforeMount,mounted钩子
- 有true变为false时,会触发beforeDestory,destoryed方法
五、为什么不建议v-if和v-for一起使用
-
因为v-for的优先级大于v-if,每次渲染都会先循环再进行条件判断,会造成性能方面的浪费
-
解决方式: 可以在外面嵌套一层template,在这一层进行v-if判断,里面再进行v-for
六、为什么Vue中的data属性是一个函数而不是一个对象
1.实例和组件的区别
- vue实例的时候定义data可以是一个对象,也可以是一个函数
- 组件中定义data只能是一个函数,如果定义为对象则会报错
2.原因
-
根实例对象data可以是对象也可以是函数(根实例是单例),是因为其不会造成数据污染
-
组件实例对象的data必须为函数
- 防止多个组件实例对象之间共有同一个data,产生数据污染
- 采用函数的形式,
initDate
时会将其作为工厂函数都会返回全新的data对象,这样不会造成数据污染
七、Vue中给对象添加新属性时,页面不刷新怎么办?
1.原因
- vue2采用Object.defineProperty实现数据响应式,对新增的属性,并没有通过Object.defineProperty设置响应式数据
2.解决方案
-
Vue.set()–增加少量数据的时候推荐
Vue.set(target,propertyName/index,value)
-
Object.assign()—增加大量新属性的时候推荐
- 创建一个新的对象,合并原对象和混入对象的属性
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
八、Vue中的$nextTick有什么用?
- 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM数据
- 出现原因:因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM, 而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更; 这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数, 如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的;
- 作用:nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
2.分析
- vue在更新DOM时是异步执行的,当数据发生变化的时候,Vue将开启一个异步更新队列,视图需要等队列中的所有数据变化完成之后,再统一进行更新
- 如果一直修改相同的数据,异步操作队列还会进行去重
for(let i=0; i<100000; i++){
num = i
}
- 如果没有nextTick更新机制,每次num更新都会触发视图更新—太多了,有了nextTick机制后只需要更新一次—优化策略
3.使用
-
使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中
- 组件内使用
vm.$nextTick()
实例方法只需要通过this.$nextTick()
,并且回调函数中的this
将自动绑定到当前的Vue
实例上
- 组件内使用
// 修改数据
vm.message = '修改后的值'
// DOM 还没有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {
// DOM 更新了
console.log(vm.$el.textContent) // 修改后的值
})
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '修改后的值'
})
- ** vue2的基本使用**:
<div id="app">{{ msg }}</div>
new Vue({
el: '#app',
data(){
return {
msg: 'hello vue'
}
},
mounted() {
this.msg = 'mouche'
console.log(this.msg) // mouche
// 虽然msg更新了,但是直接这样获取到的还是hello vue
console.log(document.getElementById('app').innerText) // hello vue
// 如果是使用了$nextTick,获取到的是最新的值
this.$nextTick(() => {
console.log(document.getElementById('app').innerText) // mouche
})
}
- vue3 的基本使用:
import { nextTick } from 'vue'
//可以配合异步进行使用
setup() {
const message = ref('Hello!')
const changeMessage = async newMessage => {
message.value = newMessage
await nextTick()
console.log('Now DOM is updated')
}
}
//也可以直接使用
nextTick(()=>{
doSomething.....
})
九、说说你对Vue中Keep-alive的理解
1.是什么
-
keep-alive
是vue中的内置组件,能在组件切换的过程中将状态保留在内存中,防止重复渲染DOM -
keep-alive包裹动态组件的时候,会缓存不活动的组件实例,而不是销毁它们
-
Keep-alive可以设置以下props属性:
include
– 字符串或者正则表达式,只有名称匹配的组件会被缓存exclude
--字符串或者正则表达式,任何名称匹配的组件都不会被缓存max
–数字,最多可以缓存多少组件实例,如果超过了这个数字的话,在下一个新实例创建之前,就会将以缓存组件中最久没有被访问到的实例销毁掉(LRU)
-
用法:
<!--基本用法-->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
- 匹配首先检查组件自身的name选择,如果name选项不可用,则匹配它的局部注册名称;匿名组件不能被匹配
2.原理
- 获取
keep-alive
包裹着的第一个子组件对象及其组件名; 如果 keep-alive 存在多个子元
素,keep-alive
要求同时只有一个子元素被渲染。所以在开头会获取插槽内的子元素,调用 getFirstComponentChild
获取到第一个子元素的 VNode
。
根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode
),否则开启缓存策略。
根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys
中的位置(更新key的位置是实现LRU
置换策略的关键)。
如果不存在,则在this.cache
对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU
置换策略删除最近最久未使用的实例(即是下标为0的那个key)。最后将该组件实例的keepAlive
属性值设置为true
。
3.使用场景
-
原则:当我们在某些场景下不需要让页面重新加载时,可以使用KeepAlive
-
例如:从
首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
- 在路由设置KeepAlive属性判断是否需要缓存
{ path: 'list', name: 'itemList', // 列表页 component (resolve) { require(['@/pages/item/list'], resolve) }, meta: { keepAlive: true, title: '列表页' } }
- 使用
<div id="app" class='wrapper'> <keep-alive> <!-- 需要缓存的视图组件 --> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <!-- 不需要缓存的视图组件 --> <router-view v-if="!$route.meta.keepAlive"></router-view> </div>
4.缓存后如何获取数据
- beforeRouteEnter:每次组件渲染的时候都会执行
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
- actived
activated(){
this.getData() // 获取数据
},
十、Vue的修饰符有哪些?
1.表单修饰符
- lazy:填完消息,光标离开标签的时候,才会将值赋予给value
<input type="text" v-model.lazy="value">
<p>{{value}}</p>
- trim:自动过滤用户输入的首空格字符,而中间的空格不会省略
<input type="text" v-model.trim="value">
- number:自动将用户的输入值转为数值类型,但如果这个值无法被
parseFloat
解析,则会返回原来的值
<input v-model.number="age" type="number">
2.事件修饰符
- stop:阻止事件冒泡(重要),相当于
event.stopPropagation
<div @click="shout(2)">
<button @click.stop="shout(1)">ok</button>
</div>
//只输出1
- prevent:阻止事件默认行为,相当于
event.preventDefault
<form v-on:submit.prevent="onSubmit"></form>
- self: 只当在event.target是当前元素自身的时候触发处理函数
<div v-on:click.self="doThat">...</div>
// v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击
- once:绑定了事件以后只能触发一次
<button @click.once="shout(1)">ok</button>
- capture:使事件触发从包含该元素的顶层从下触发
- passive:在移动端,当我们监听元素滚动时候,会一直触发onscroll,使用passive相当于给onsrcoll整了个.lazy修饰符(重要)
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
3.鼠标按钮修饰符
- left 左键点击
- right 右键点击
- middle 中键点击
<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>
4.键盘修饰符
5.v-bind修饰符
-
sync:能对props进行一个双向绑定
- 实际上就是一个语法糖
- 子组件传递的事件名格式必须为
update:value
,value必须与子组件中的props中声明的完全一直 - 不可以用在一个字面量的对象上
//父组件
<comp :myMessage.sync="bar"></comp>
//子组件
this.$emit('update:myMessage',params);
相当于以下的简写
//父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){
this.bar = e;
}
//子组件js
func2(){
this.$emit('update:myMessage',params);
}
十一、Vue项目有封装过axios吗?怎么封装的?
1.安装
// 项目中安装
npm install axios --S
// cdn 引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2.导入
import axios from 'axios'
3.发送请求
- 普通请求
axios({
url:'',
method:'GET',
params:{
type:''.
page:1
}
}).then(res=>{})
- 并发请求
function getUsetAccount(){
return axios.get('/user/123')
}
function getUserPermission(){
return axios.get('/user/123/123')
}
axios.all([getUsetAccount,getUserPermission]).then(axios.spread(res1,res2)=>{
//两个请求都执行完成才会执行
})
4.封装
-
设置接口请求前缀
- 利用node环境变量来判断,用来区分开发,测试,生产环境
if(process.env.NODE_ENV === 'development'){ axios.defaults.baseURL = 'http://dev.xxx.com' }else if(process.env.NODE_ENV === 'production'){ axios.defaults.baseURL = 'http://prod.xxx.com' }
-
跨域
- 在vue.config.js文件中配置devServer实现代理转发
devServer:{ proxy:{ '/api':{ target:'http://dev.xxx.com', changeOrigin:true, pathRewrite:{ '/api':'' } } } }
-
设置请求头与超时时间
- 大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,这里将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
const service = axios.create({ ... timeout: 30000, // 请求 30s 超时 headers: { get: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来 }, post: { 'Content-Type': 'application/json;charset=utf-8' // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来 } }, })
-
封装请求方法
- 先引入封装好的方法,在要调用的接口重新封装一个方法暴露出去
// get 请求 export function httpGet({ url, params = {} }) { return new Promise((resolve, reject) => { axios.get(url, { params }).then((res) => { resolve(res.data) }).catch(err => { reject(err) }) }) } // post // post请求 export function httpPost({ url, data = {}, params = {} }) { return new Promise((resolve, reject) => { axios({ url, method: 'post', transformRequest: [function (data) { let ret = '' for (let it in data) { ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&' } return ret }], // 发送的数据 data, // •url参数 params }).then(res => { resolve(res.data) }) }) }
-
请求拦截器
axios.interceptors.request.use(config=>{
token && (config.header.Authrization = token);
return config
},
err=>{
return Promise.error(err)
}
)
- 响应拦截器
axios.interceptors.response.use(response = >{
if(response.status == 200){
if(response.data.code == 511){
//未授权
}else if(response.data.code == 510){
//未登录
}else{
return Promise.resolve(response)
}
else{
//不是200请求
return Promise.reject(response)
}
},
err=>{
// 我们可以在这里对异常状态作统一处理
if (error.response.status) {
// 处理请求失败的情况
// 对不同返回码对相应处理
return Promise.reject(error.response)
}
}
})
十二、大型项目中,Vue项目怎么划分结构和划分组件比较合理呢?
1.原则
- 文件夹和文件夹内部文件的语义一致性
- 单一入口/出口
- 就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
- 公共的文件应该以绝对路径的方式从根目录引用
/src
外的文件不应该被引入
2.目录结构
- 多页面目录结构
my-vue-test:.
│ .browserslistrc
│ .env.production
│ .eslintrc.js
│ .gitignore
│ babel.config.js
│ package-lock.json
│ package.json
│ README.md
│ vue.config.js
│ yarn-error.log
│ yarn.lock
│
├─public
│ favicon.ico
│ index.html
│
└─src
├─apis //接口文件根据页面或实例模块化
│ index.js
│ login.js
│
├─components //全局公共组件
│ └─header
│ index.less
│ index.vue
│
├─config //配置(环境变量配置不同passid等)
│ env.js
│ index.js
│
├─contant //常量
│ index.js
│
├─images //图片
│ logo.png
│
├─pages //多页面vue项目,不同的实例
│ ├─index //主实例
│ │ │ index.js
│ │ │ index.vue
│ │ │ main.js
│ │ │ router.js
│ │ │ store.js
│ │ │
│ │ ├─components //业务组件
│ │ └─pages //此实例中的各个路由
│ │ ├─amenu
│ │ │ index.vue
│ │ │
│ │ └─bmenu
│ │ index.vue
│ │
│ └─login //另一个实例
│ index.js
│ index.vue
│ main.js
│
├─scripts //包含各种常用配置,工具函数
│ │ map.js
│ │
│ └─utils
│ helper.js
│
├─store //vuex仓库
│ │ index.js
│ │
│ ├─index
│ │ actions.js
│ │ getters.js
│ │ index.js
│ │ mutation-types.js
│ │ mutations.js
│ │ state.js
│ │
│ └─user
│ actions.js
│ getters.js
│ index.js
│ mutation-types.js
│ mutations.js
│ state.js
│
└─styles //样式统一配置
│ components.less
│
├─animation
│ index.less
│ slide.less
│
├─base
│ index.less
│ style.less
│ var.less
│ widget.less
│
└─common
index.less
reset.less
style.less
transition.less
十三、Vue怎么实现权限管理?控制到按钮级别的权限怎么做?
1.接口权限
- 采用JWT的形式来验证,登录完拿到token,将token存起来,通过axios拦截器进行拦截,每次请求的时候头部携带token
axios.interceptors.request.use(config => {
//这里是存在cookie,请求的时候不设错
config.headers['token'] = cookie.get('token')
return config
})
axios.interceptors.response.use(res=>{},{response}=>{
//看返回
if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误
router.push('/login')
}
})
2.按钮权限
3.菜单权限
4.路由权限
-
方案一:初始化的时候就挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验(
- 全局路由守卫,每次路由跳转都要做权限判断
- 菜单信息写死在前端,要改个显示文字或者权限信息,需要重新编译
- 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
-
方案二:初始化的时候先挂载不需要权限控制的路由,比如登录页,404等错误页。如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制
十四、vue2 和vue3 的区别
1.绑定原理发生了改变
- vue2的双向数据绑定事利用ES5的一个API
Object.defineProperty()
对数据进行劫持,结合,发布,订阅来实现
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//拦截get,当我们访问data.key时会被这个方法拦截到
get: function getter () {
//我们在这里收集依赖
return obj[key];
},
//拦截set,当我们为data.key赋值时会被这个方法拦截到
set: function setter (newVal) {
//当数据变更时,通知依赖项变更UI
}
})
- vue3使用了es6的
proxy
API
- defineProperty只能监听某个属性,不能对全对象监听
- 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
- 可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化
2.Vue3支持碎片
- 在组件中可以拥有多个根节点
3.Composition API
- Vue2使用选项类型API(options API)
- Vue3使用合成型API(Composition API)
旧的选项型API在代码里分割了不同的属性: data,computed属性,methods,等等。新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,
这样代码会更加简便和整洁
。
4.生命周期
setup 执行时机是在 beforeCreate 之前执行
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount //组件挂载到节点上之前执行的函数
mounted -> onMounted //组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate //组件更新之前执行的函数
updated -> onUpdated //组件更新完成之后执行的函数
beforeDestroy -> onBeforeUnmount //组件卸载之前执行的函数
destroyed -> onUnmounted // 组件卸载完成后执行的函数
activated -> onActivated
deactivated -> onDeactivated
5.父子传参不同
- setup 函数时,它将接受两个参数:(props、context(包含attrs、slots、emit))
- 执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)
- 与模板一起使用:需要返回一个对象 (在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用)
- 使用渲染函数:可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态
- setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,你
不能使用 ES6 解构
,因为它会消除 prop 的响应性
十五、什么是Vue.extend
- Vue.extend是一个全局的API,实际是创建一个构造器,并将其挂载到HTML的元素上(创建一个template标签)。可以通过propsData传参
- 使用场景:
- 组件模板都是事先定义好的,如果我要从接口动态渲染组件怎么办? 所有内容都是在
#app
下渲染,注册组件都是在当前位置渲染。如果我要实现一个类似于window.alert()
提示组件要求像调用 JS 函数一样调用它,该怎么办? 这时候,Vue.extend + vm.$mount
组合就派上用场了。
<h1>Vue.extend</h1>
<hr>
<div id="element">
<h1>Hello!</h1>
</div>
<hr>
<author></author>
<hr>
<div id="transmit"></div>
var Profile1 = Vue.extend({
template: '<p>My name is {{Name}}</p>',
data: function () {
return {
Name: 'ElucidatorSky'
}
}
})
// 输出 Profile1 实例,在控制台输出为VueComponent{}
console.log(new Profile1());
// 创建 Profile1 实例,并挂载到一个元素上。
new Profile1().$mount('#element')
var Profile2 = Vue.extend({
template:"<p>{{Name}}</p>",
data:function(){
return{
Name:'ElucidatorSky'
}
}
});
// 创建 Profile2 实例,并挂载到一个元素上。
new Profile2().$mount('author');
var Profile3 = Vue.extend({
template: '<p>{{extendData}}</br>实例传入的数据为:{{propsExtend}}</p>',
data: function () {
return {
extendData: '这是extend扩展的数据',
}
},
props:['propsExtend']
})
// 创建 Profile 实例,并挂载到一个元素上。可以通过propsData传参.
new Profile3({propsData:{propsExtend:'我是实例传入的数据'}}).$mount('#transmit')
十六、什么是Vue.mixin和minxins
- Vue.mixin即是混入,mixin的作用是多个组件可以共享数据和方法
- 可以通过mixin抽离公共的业务逻辑
- 当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并” 。
// mixins示例
//全局混入
Vue.mixin({
created:function(){
console.log('我是全局被混入的');
}
})
var mixin = {
created: function () { console.log('我是外部被混入的') }
}
var app = new Vue({
// 局部混入
mixins: [mixin]
})
十七、说说Vue中的diff算法
-
在vue中,作用于虚拟Dom渲染成真实Dom的新旧VNode节点比较
-
diff整体策略:深度优先,同层比较
- 只会在同层级进行,不会跨层级比较
- 比较的过程中,循环从两边向中间靠拢
十八、Vue路由中,history和hash两种模式有什么区别
1、hash模式
- 把前端路由用井号
#
拼接在真实URL后面。当#后面的路劲发生变化时,浏览器不会重新发起请求,而是触发hashchange事件 - 浏览器兼容较好,但是路径在#后面比较丑
2、history模式
- 允许开发者直接更改前端路由,即更新浏览器的地址而不重新发起请求
- 是H5提供的新特性
- 路径比较正规,没有#
- 兼容性不如hash,需要服务端支持,否则一刷新页面就404
十九、Vue2.0为什么不能检查数组的变化,该怎么解决?
-
vue2的不足:
- 无法检测数组/对象的新增:因为在构造函数中就已经为所有的属性做了检测绑定操作
- 无法检测通过索引改变数组的操作:Object.definePropety是可以检测通过索引改变数组的操作,没有实现的原因是因为性能代价和用户体验收益不成正比
-
解决方案
- this.$set(array, index, data)
this.dataArr = this.originArr this.$set(this.dataArr,0,{data:'修改第一个元素'}) console.log(this.dataArr) console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
- 使用splice。splice会被监听,同时又可以做到增删改
- 利用临时变量进行中转
let tempArr = [...this.targetArr]; temArr[0] = {data:'text'} this.targetArr = tmp
二十、Vue组件通讯有哪几种方式
1、props
和$emit
父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
2、$parent
,$children
获取当前组件的父组件和当前组件的子组件
3、$attrs
和$listeners
A->B->C。Vue 2.4 开始提供了
a
t
t
r
s
和
attrs 和
attrs和listeners 来解决这个问题
4、父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量
4、$refs
获取组件实例
5、eventBus
兄弟组件数据传递 这种情况下可以使用事件总线的方式
6、vuex
状态管理
二十一、说说vue的内容指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SiVJ8Cn-1661400413823)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/97ad95a72d964b6f9f4a4b2a1ecb9280~tplv-k3u1fbpfcp-zoom-1.image)]
二十二、怎么理解Vue的单向数据流
- 数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据
注意:在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告
- 如果实在要改变父组件的 prop 值 可以再定义一个变量 并用 prop 的值初始化它 之后用$emit 通知父组件去修改
二十三、v-for为什么要加Key
- 如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法
- key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
- 更准确: a.key==b.key,通过判断key是否相等可以避免不同VNode复用的情况
// 判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用
function isSameVnode(oldVnode, newVnode) {
return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key;
}
- 更快速:利用key的唯一性生成map对象来获取对应节点,比起遍历快
// 根据key来创建v-for的index映射表 类似 {'a':0,'b':1} 代表key为'a'的节点在第一个位置 key为'b'的节点在第二个位置
function makeIndexByKey(children) {
let map = {};
children.forEach((item, index) => {
map[item.key] = index;
});
return map;
}
// 生成的映射表
let map = makeIndexByKey(oldCh);
二十四、 使用过 Vue SSR 吗?说说 SSR
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端。
优点:
SSR 有着更好的 SEO、并且首屏加载速度更快
缺点: 开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。
服务器会有更大的负载需求
二十五、vue 中使用了哪些设计模式
- 工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
- 单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
- 发布-订阅模式 (vue 事件机制)
- 观察者模式 (响应式数据原理)
- 装饰模式: (@装饰器的用法)
- 策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略