1.谈谈你对MVVM开发模式的理解?
1.MVVM由Model、View、ViewModel组成。
2.Model表示提供数据,View用来展示数据,
3.ViewModel用来监听model中的数据变化,控制视图更新,处理用户操作
Model 和 ViewModel 进行双向数据绑定。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中用户交互操作,改变的数据也会在 Model 中同步。
为什么使用MVVM:低耦合,可复用,独立开发,可测试
2.v-if 和 v-show 有什么区别?
理解
- v-if是动态的向DOM树内添加或者删除DOM元素;
- v-show是通过设置DOM元素的display样式属性控制显示隐藏;
在编译阶段
- v-if,只有条件为真,才会开始编译;
- v-show不管条件真假,都会编译,然后被缓存,而且DOM元素保留;
性能消耗
- v-if有更高的切换消耗;
- v-show有更高的初始渲染消耗
- v-if指令可以应用于template包装元素上,v-show不支持
一般来说,节点要是经此显示隐藏 就用v-show
3.route和router区别
$route 是“路由信息对象”,拥有path,params,hash,query,fullPath,matched,name 等路由信息参数
$router 是“路由实例”,可以在页面之间来回切换,通过push、replace、go、back等方法,来实现页面间的跳转
4.vue自定义指令
vue2
在vue2中,如果是局部注册,在directives中创建一个自定义指令名,他是一个对象,对象中拥有多个生命周期
- bind:只调用一次,指令第一次绑到元素调用,用于初始化
- inserted:被绑定元素插入父节点时调用
- update:所在组件vnode更新调用
- componentUpdate:指令在组件的vnode及子组件的vnode全部更新完调用
- ubind:只调用一侧,指令解绑
每个生命周期都可以获取节点跟节点上的信息,el,binding
directives: {
'focus': {
bind(el, binding, vnode) {
el.focus()
}
}
}
全局注册的话,在main.js中使用Vue实力创建Vue.directives(),第一个参数为'指令名',第二个是个对象,对象可以写生命周期
Vue.directives('focus',{
bind(el, binding, vnode) {
el.focus()
}
})
vue3
局部注册:添加一个 directives 属性,内部可以写多个对象,内部的对象名字为自定义指令名字,可以给指令添加生命周期,例如
- created 元素初始化的时候
- beforeMount 指令绑定到元素后调用 只调用一次
- mounted 元素插入父级dom调用
- beforeUpdate 元素被更新之前调用
- update 这个周期方法被移除 改用updated
- beforeUnmount 在元素被移除前调用
- unmounted 指令被移除后调用 只调用一次
每个生命周期都可以获取节点跟节点上的信息,el,binding
// MyComponent.vue
<template>
<input v-focus />
</template>
<script>
export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
</script>
全局注册
在main.js文件中,在 Vue 应用实例上调用 directive 方法,传入指令名称和指令对象作为参数
// main.js 或其他全局文件
import { createApp } from 'vue'
import App from './App.vue'
// 自定义指令 v-focus
const focusDirective = {
mounted(el) {
el.focus()
}
}
// 创建一个 Vue 应用实例
const app = createApp(App)
// 全局注册 v-focus 指令
app.directive('focus', focusDirective)
// 挂载应用实例
app.mount('#app')
5.vue项目优化
vue项目优化分3个层面:代码层,基础的web技术优化,webpack优化
代码层面优化:
- Object.freeze 方法来冻结一个对象,
- 图片懒加载
- 路由懒加载
- keep-alive组件缓存
- v-if,v-for不要同时使用,for循环中要添加key值
- 频繁显示隐藏节点,使用v-show
- input防抖节流
基础的web技术优化
- 使用浏览器缓存,减少http请求
- 使用CDN加速
- 延迟加载或者异步加载
- 压缩文件,减少不必要的打印注释
webpack优化
6.vue模板如何编译
Vue的模板编译就是将HTML翻译为render函数的过程,可以分为三个阶段
1.解析阶段:将“HTML”模板解析成AST语法树
核心 parseHTML( template ,{}) 会根据正则匹配生成AST语法树
2.优化阶段:在AST语法树中找出静态子树并进行标记(被标记的静态子树在虚拟dom比对时会被忽略,提高性能)
生成虚拟dom时候,如果不是首次渲染,并且当前的节点被标记,就会去复制存在的静态子树
在对比虚拟dom时候,发现当前节点是静态子树则不会进行对比
(只有当前节点及其子节点是静态的才会被标记)
3.代码生成阶段:通过AST生成代码字符串,并最终生成render函数。
7.vue2响应式原理
vue 采用了几个核心部件 : Observer ,Dep, Watcher ,Scheduler
首先vue会通过object.defineProperty 将对象的每个属性转化为带有 getter 和 setter 的属性,
将普通的 JavaScript 对象转换为响应式对象
dep会记录谁用了它,并在更新的时候通知别人更新
创建Watcher对象:当依赖的数据发生变化时,执行相应的回调函数以更新视图。
因为Vue 的更新操作是异步的,所以Scheduler用来协调 Watcher 的更新,确保它们以合适的顺序和时机执行
Scheduler 不会立即执行更新,通过nexttick异步更新
8.vue3响应式原理
通过Proxy(代理): 拦截对象中任意属性的变化,包括:属性值的读写,属性的增加,属性的删除等。
通过Reffect(反射): 对源对象的属性进行操作, Reflect不是一个函数对象,因此它是不可构造的。
- Vue 3 在创建组件实例时,会使用 Proxy 来代理组件的 data 对象。
- Proxy 允许 Vue 3 拦截对目标对象的各种操作,如属性读取、属性设置、属性删除等
- 当响应式属性发生变化时,Vue 3 会触发一个更新过程,通知所有依赖该属性的人更新状态
- Vue 3 在使用 Proxy 拦截属性操作时,会使用 Reflect API 来执行相应的默认操作。
- 例如,当拦截到属性读取时,Vue 3 会使用 Reflect.get() 来读取属性值;当拦截到属性设置时,Vue 3 会使用 Reflect.set() 来设置属性值
9.刷新浏览器后,Vuex的数据是否存在?如何解决?
不存在
原因: 因为 store 里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值初始化。
我们有两种方法解决该问题:
- 使用 vuex-along
- 使用 localStorage 或者 sessionStroage
10.vue和react共同点?区别
共同点:
- 数据驱动视图
- 组件化开发
- 都使用 Virtual DOM来提高性能,减少dom,加快页面渲染速度
- 都支持服务器端渲染
不同点:
核心思想
- vue定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。这就有了vue的主要特点:灵活易用的渐进式框架,进行数据拦截/代理,它关注数据的变化,通过数据驱动视图,并提供了许多易于理解和使用的API。
- React的核心思想是提出UI开发的新思路,通过组件化来构建用户界面。,数据不可变以及单向数据流,当然需要双向的地方也可以手动实现, 比如借助onChange和setState来实现。
组件写法
- Vue:Vue的组件写法更接近于传统的HTML和JavaScript的组合。Vue的模板语法直接写在HTML文件中
例如:你可以在一个 .vue 文件中直接编写模板(template)、脚本(script)和样式(style)
- React:React推荐的做法是JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中
function MyComponent(props) {
return (
<div>
<h1>{props.message}</h1>
<button onClick={() => props.increment()}>Increment</button>
</div>
);
}
///
function MyComponent(props) {
const style = {
color: 'blue',
fontSize: '20px'
};
return (
<div style={style}>
<h1>{props.message}</h1>
</div>
);
}
diff算法
- React的Diff算法通过比较新旧Virtual DOM的差异,仅更新有变化的部分,以提高渲染效率。
- Vue的Diff算法旨在找出新旧VNode之间的差异,并将这些差异应用到实际的DOM上,以最小化DOM操作并提高性能。 就类似2个dom树进行比较,找出差异,重新渲染新的dom
响应式原理
- vue2采用object.defineProperty ,vue3采用proxy,reflect
- React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state。
11.vue双向数据绑定原理
- 数据劫持:
- Vue在实例初始化时,会遍历
data
选项中的数据,并使用Object.defineProperty()
方法将其转换为getter/setter。 - 每个组件实例都有一个对应的watcher实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
- Vue在实例初始化时,会遍历
- 发布订阅模式:
- 当数据变化时,setter方法被调用,此时会通知订阅了该数据的所有watcher,watcher接收到通知后,会调用对应的回调函数(通常是组件的更新函数),进行视图更新。
- 视图层也会监听DOM事件,如input输入框的输入事件,当事件触发时,会通过Vue的指令(如
v-model
)来更新数据,从而触发数据的setter,进而更新视图。
<div id="app">
<input type="text" v-model="message">
<p>{{ message }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
12.computed和watch作用,区别
computed(计算属性)
定义与用途:
computed
是Vue中的一个内置函数,用于声明计算属性。这些属性基于其响应式依赖进行缓存,只有当依赖发生变化时才会重新计算。- 它常用于根据已有的响应式数据派生出新的数据,使得模板中可以直接使用这些计算后的数据,而无需在模板中编写复杂的逻辑。
特点:
- 自动缓存:
computed
属性会缓存其计算结果,只有在其依赖的响应式数据发生变化时才会重新计算。这有助于提升性能,避免不必要的重复计算。 - 同步计算:
computed
属性的计算是同步的,即它会立即返回计算结果。 - 依赖关系管理:
computed
可以更清晰地管理数据之间的依赖关系,而不需要手动跟踪它们的变化。
使用场景:
- 过滤和排序列表。
- 格式化数据,如日期、货币等。
- 根据条件动态设置元素的样式或类。
watch(侦听器)
定义与用途:
watch
是Vue中用于侦听数据变化并执行异步操作或复杂逻辑的功能。- 它允许你执行数据变化时的副作用(side effects),如发送网络请求、执行动画等。
特点:
- 无缓存:
watch
不会缓存旧值或计算结果,每次侦听到数据变化时都会执行回调函数。 - 异步支持:
watch
的回调函数可以执行异步操作,如发送网络请求或执行定时器等。 - 复杂逻辑处理:适用于在数据变化时执行复杂逻辑或副作用操作。
使用场景:
- 需要在数据变化时执行异步操作,如发送网络请求。
- 需要在数据变化时执行复杂的逻辑判断或计算。
- 需要在数据变化时触发一些副作用,如更新DOM、改变其他数据等。
区别
13.Vuex
Vuex是一种状态管理模式,可用来共享不同组件之间的联系。
主要包括以下几个模块:
- State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- this.$store.state.yourProperty
- Getter => Getter允许我们从Store中的state计算派生出一些状态,可以认为是store的计算属性。
this.$store.getters.yourGetter 来调用
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
});
- Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。
this.$store.commit('mutationName', payload) 来调用
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
});
- Action => Action类似于Mutation,但是它是异步的。Action可以提交mutation,而不是直接变更状态。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
});
- Module => Module允许我们将单一的Store拆分为多个模块(module),每个模块拥有自己的state、mutation、action、getter。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
14.vuex辅助函数
mapState, mapMutations, mapActions, mapGetters
通过辅助函数mapGetters、mapState、mapActions、mapMutations,把vuex.store中的属性映射到vue实例身上,这样在vue实例中就能访问vuex.store中的属性了,对于操作vuex.store就变得非常方便。
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
// 将 state 中的 userInfo 映射为 this.userInfo
userInfo: state => state.userInfo,
// 将 state 中的 orderList 映射为 this.orderList
orderList: state => state.orderList,
// ... 其他状态映射
}),
},
};
使用 mapState 可以将 Vuex 中的状态映射到组件的计算属性中,
通过直接访问这些计算属性(如 this.userInfo),我们可以获取到 Vuex 中的状态数据。
mapMutations
- mapMutations 是 Vuex 的辅助函数,用于将 Vuex 中的 mutations 映射到组件的方法中,使得组件可以直接调用 mutations 来修改 state 的值。
import { mapMutations } from 'vuex';
export default {
methods: {
...mapMutations([
// 映射 mutations 中的 increment 到组件的方法 increment
'increment',
// ... 其他 mutations 映射
]),
},
};
使用 mapMutations 可以将 Vuex 中的 mutations 映射到组件的方法中,
这样我们就可以在组件中直接调用这些
方法(如 this.increment())来触发对应的 mutations,从而修改 Vuex 中的状态。
mapActions
- mapActions 是 Vuex 提供的辅助函数,用于将 Vuex 中的 actions 映射到组件的方法中,方便在组件中调用和触发 Vuex 中定义的 actions。
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions([
// 映射 actions 中的 fetchData 到组件的方法 fetchData
'fetchData',
// ... 其他 actions 映射
]),
},
};
使用 mapActions 可以将 Vuex 中的 actions 映射到组件的方法中,
这样我们就可以在组件中直接调用这些方法(如 this.fetchData())来触发对应的 actions,
这些 actions 通常包含异步操作,如 API 请求等。
通过 actions,我们可以执行异步操作并在完成后提交 mutations 来修改状态。
mapGetters
- mapGetters 是 Vuex 提供的辅助函数,用于将 Vuex 中的 getters 映射到组件的计算属性中。
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters([
// 映射 getters 中的 doneTodosCount 到组件的计算属性 doneTodosCount
'doneTodosCount',
// ... 其他 getters 映射
]),
},
};
使用 mapGetters 可以将 Vuex 中的 getters 映射到组件的计算属性中,
使得我们可以在组件中直接访问这些 getters 的计算结果。
15.vuex模块化使用(module)
在Vuex中,随着应用复杂性的增长,将所有状态、getters、mutations和actions都放在一个单独的store对象中可能会变得难以管理。为了解决这个问题,Vuex允许我们将store分割成模块(modules)。每个模块都拥有自己的state、mutations、actions、getters,甚至是嵌套子模块。
前提:创建两份js文件,含有属性与vuex写法相同,需要通过 namespaced:true开启命名空间store/index.js:在modules中引入文件
我们可以在另一个js文件中写好这些数据模板
// 假设我们有一个名为user的模块
const userModule = {
state: {
name: 'John Doe',
age: 30
},
mutations: {
setName(state, newName) {
state.name = newName;
},
setAge(state, newAge) {
state.age = newAge;
}
},
actions: {
updateName({ commit }, newName) {
commit('setName', newName);
},
updateAge({ commit }, newAge) {
commit('setAge', newAge);
}
},
getters: {
fullName(state) {
return `${state.name} (${state.age} years old)`;
}
}
};
然后直接注册到store里面的模块对象
import Vue from 'vue';
import Vuex from 'vuex';
import userModule from './modules/user'; // 假设userModule定义在./modules/user.js文件中
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
user: userModule // 注册名为'user'的模块
}
});
后续访问就直接从store中引入模块名
印射到vue中就需要在,注册那个变量或者函数之前添加个/,/前面写模块名
因为他原本接收参数是一个数组,也可以让他接收2个参数,第一个是模块名,第二个就正常变量,这样也可以印射
this.$store.getters['user/fullName']; // 'John Doe (30 years old)'
// 或者使用mapGetters辅助函数
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['user/fullName'])
...mapGetters('user',['fullName'])
}
};
16.vue中mixin
Mixin 主要有以下几个用途:
复用代码:如果你有一些组件有相同的逻辑或功能,你可以将这些逻辑或功能提取到一个 mixin 中,然后在多个组件中引入这个 mixin。
需要注意的是,如果 mixin 和组件有同名的选项(如 methods、components 等),那么这些选项将会以某种方式合并。对于大多数选项,如 methods、components 和 directives,Vue 将以组件自身的选项优先,也就是说组件自身的选项会覆盖 mixin 的同名选项。但是,对于某些选项(如生命周期钩子、created、mounted 等),它们将会合并为一个数组,因此所有的钩子函数都会被调用。
let mixin = {
created() {
console.log('我是mixin中的');
},
methods: {
hellow() {
console.log('你好');
},
},
}
export default mixin
import mixin from "./mixins";
export default {
mixins: [mixin],
mounted() {
this.hellow();//你好
},
};
import { createApp } from 'vue'
import App from './App.vue'
import mixins from "./mixins";
const app = createApp(App)
app.mixin(mixins)
app.mount('#app')
17.Vue中给对象添加新属性时,界面不刷新怎么办?
原因:vue2响应式采用object.defineProperty进行劫持,那个添加新属性时,新的属性不会具有get和set方法,不是一个响应式所以界面不刷新
解决:this.$set(要响应的内容)向响应式对象中添加一个property,并确保这个新 property 同样是响应式的
vue3通过proxy劫持和reflect映射实现响应式,不会有这个问题
18.vue组件通讯方式
通过 props 传递
-
- props校验:name:{type:String,required:true,default:默认值} required是否必要
- 可以在子组件内部添加属性传递数据,让子组件通过props接收
通过 $emit 触发自定义事件
- 子组件中可以使用this.$emit传递内容,第一个参数为('事件名'),第二个为传递的内容,然后父组件在子组件上监听这个事件
使用 ref
- 在子组件中添加ref属性添加实例名,然后在父组件通过this.$refs.实例名来获取子组件内部的实例
EventBus
import Vue from 'vue';
export const EventBus = new Vue();
EventBus.$emit('some-event', 'data to send');
EventBus.$on('some-event', (data) => {
console.log(data); // 输出: data to send
});
Provide 与 Inject
祖先组件---------------------------------
<script>
export default {
provide() {
return {
themeColor: 'blue'
};
}
}
</script>
后代组件-----------------------------
<script>
export default {
inject: ['themeColor'],
mounted() {
console.log(this.themeColor); // 输出: blue
}
}
</script>
Vuex
19.vue3setup的父传子怎么去写?
第一种:使用vue2写法通过props和$emit
第二种:setup函数写法
- setup(props,context),通过props接收数据直接结构出来,通过context.emit(‘调用父组件方法’,传递参数)
第三种:script中setup
- vue3自带defineProps,defineEmits
const emits = defineEmits(["changeNumber"]);
// 也可以不赋值,取值通过{{num}}获取
const props = defineProps({
num: {
type: Number,
default: () => [],
},
list: {
type: Array,
},
});
const changeNum = function () {
emits("changeNumber", 888);
// console.log(11111111111);
};
20.setup可不可以直接写async和await?
如果是script中setup写法
setup 语法糖中可直接使用 await,不需要写 async , setup 会自动变成 async setup
<script setup>
import Api from '../api/Api'
const data = await Api.getData()
console.log(data)
</script>