1. MVVM 模型
● Model(模型):表示应用程序中的数据模型。它代表着应用程序中的业务逻辑和状态。
● View(视图):表示应用程序的用户界面。它是用户与应用程序交互的方式。
● ViewModel(视图模型):是一个桥梁,将模型与视图连接在一起。它提供了视图所需的数据和命令,并将用户的输入转换为模型的操作。
作用:数据(Model)和视图(View)是不能直接通讯的,而是需要通过ViewModel来实现双方的通讯。当数据(Model)发生变化时,ViewModel能够监听到变化,并及时通知View视图做出修改。同样地,当页面有事件触发的时候,ViewModel也能监听到事件,并通知数据(Model)进行响应。所以ViewModel相当于一个观察者,监控着双方的动作,并及时通知进行响应的操作。
2. v-model
vue在初始化时,Object.defineProperty
只能劫持对象的属性,然后深层遍历data中的每个属性,通过object.defineProperty为每个属性添加getter,setter。通过Object.defineProperty
劫持数据发生的改变,如果数据发生改变了(在set中进行赋值时),触发update方法进行更新节点内容,从而实现数据双向绑定。
Vue3 是通过Proxy实现的数据双向绑定,劫持的是整个对象,能够监听动态新增的属性,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
实现双向数据的绑定
<template>
<input v-model="message" />
</template>
等价于:
<template>
<input type="text" :value="msg" @input="msg = $event.target.value" />
</template>
3. Class 与 Style 绑定
<template>
<div :class="{ box: true }" :style="{ backgroundColor: true ? 'green' : 'red' }"></div>
</template>
4. v-if 与 v-show 的区别
作用:添加渲染,切换显示与隐藏
- v-if是通过控制dom来控制元素的显示与隐藏
- v-show是通过css属性display控制元素显示
不同:v-if会移除dom或组件树,v-show则只是通过样式隐藏
使用场景:
- v-if:切换不频繁、敏感数据的隐藏(如权限按钮)
- v-show:切换频繁的场景,避免大量dom操作,提高性能
5. v-for 与 v-if 为什么不能同时使用
在Vue2中,v-for优先级高于v-if,如果二者放在同一级标签里面,每次都要先循环,再判断,消耗很多性能。对于同一组数据来说,如果我们要先判断再渲染,可以在外层包装一个div,使用v-if做一次判断即可。Vue3解决了这个问题,将v-if的优先级调整为高于v-for了。
<div class="info" v-if="false">
<div v-for="item in info" :key="item">{{ item.name }} -- {{ item.age }}</div>
</div>
6. computed 计算属性
计算属性的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题。
computed是用来计算出来一个值的,这个值调用的时候不需要加括号,会根据依赖进行缓存,依赖不变,computed的值不会重新计算(只执行一次)。
<template>
<div>{{ fullName }}</div>
<div>{{ fullName }}</div>
</template>
<script>
export default {
data() {
return {
firstName: '克里斯蒂亚诺',
lastName: '罗纳尔多',
}
},
computed: {
fullName() {
console.log(1) // 只打印一次
return this.firstName + ' ' + this.lastName
},
},
}
</script>
7. watch 侦听器
监听data中的数据变化,可在异步请求的场景使用
- immediate:表示是否要在第一次渲染的时候执行这个函数
- deep:如果我们监听一个对象,那么我们是否要看这个对象里面属性的变化
如果某个属性变化了,就去执行一个函数
<template>
<div class="about">
<div>{{ name }}</div>
<button @click="updateName">更新</button>
</div>
</template>
<script>
export default {
data() {
return {
name: '克里斯蒂亚诺罗纳尔多',
}
},
watch: {
name(newVal) {
console.log('changed:', newVal)
// 一个值变化后去做异步操作或者大开销操作
setTimeout(() => {
this.name = 'C罗'
}, 3000)
},
},
methods: {
updateName() {
this.name = '内马尔'
},
},
}
</script>
8. Vue生命周期
Vue中的生命周期本质上就是按顺序固定执行一个个的钩子函数,我们开发者可以在每个函数中写入特定代码来实现我们需要的功能
例如我们常用的ajax请求通常就放在created或者mounted中
vue2的生命周期从分类上来看有如下几种情况:
- 组件创建和挂载相关的钩子函数有
a. beforeCreate(组件实例未创建)
b. created(组件初始化完成,各种数据可以使用,可以使用Ajax发起请求)
c. beforeMount(未执行渲染,虚拟DOM完成)
d. mounted(初始化阶段结束,真实DOM已经创建,可以发送请求,也可以访问DOM元素) - 组件更新相关的钩子函数有
a. beforeUpdate(更新前,可用于获取更新前的各种状态数据)
b. updated(更新后执行,数据更新完毕) - 组件销毁相关的钩子函数有
a. beforeDestroy(销毁前,可用于清除定时器之类的事件)
b. destroyed(组件已经销毁,事件监听器被移除,所有子实例也会被销毁) - 还有一个组件缓存激活相关的钩子函数是:activated和deactivated,这两个要配合keep-alive 缓存的组件一起使用
- vue3总体来说什么周期执行顺序是一样的,不同点在于beforeCreate和created都被setup函数替代了
9. 常用内置指令
- v-text : 更新元素的 textContent
- v-html : 更新元素的 innerHTML
- v-if : 如果为 true, 当前标签才会输出到页面
- v-else: 如果为 false, 当前标签才会输出到页面
- v-show : 通过控制 display 样式来控制显示/隐藏
- v-for : 遍历数组/对象
- v-on : 绑定事件监听, 一般简写为@
- v-bind : 绑定解析表达式, 可以省略 v-bind
- v-model : 双向数据绑定
- v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none }
- v-once:所在节点在初次动态渲染后,就变为静态内容,不会再更新
- v-pre:可以跳过其所在节点的编译过程
10. 自定义指令
// 注册全局指令(v-my-directive)
Vue.directive('my-directive', (el, binding) => {
el.innerHTML = binding.value.toUpperCase()
})
// 注册局部指令
directives: {
'my-directive': {
// 指令与元素成功绑定时(一上来)
bind(el, binding) {
el.innerHTML = binding.value
},
// 指令所在元素被插入页面时
inserted(el, binding) {
el.innerHTML = binding.value.toUpperCase()
},
// 指令所在的模板被重新解析时
update(el, binding) {
el.innerHTML = binding.value
el.innerHTML = binding.value.toUpperCase()
},
},
},
11. vue组件中data为什么必须是一个函数
在 Vue.js 中,组件的 data 选项必须是一个函数,这是因为每个组件实例都应该有自己的状态,如果 data 不是一个函数,那么所有实例将共享同一个数据对象,这会导致组件之间的状态混乱。
12. SPA 单页面应用
单页面应用(Single-Page Application,简称 SPA)是一种架构方式,它在一个单独的网页中加载所有的资源,并使用 JavaScript 在客户端渲染用户界面,而不是通过服务器进行渲染。这意味着,当用户在 SPA 应用中导航到不同的页面时,不会发生实际的页面跳转,而是在当前页面内使用 JavaScript 更新内容。这使得 SPA 应用的体验非常流畅,因为不会有页面加载的延迟。
与传统的多页面应用相比,单页面应用具有许多优势,包括:
● 快速响应:由于不需要重新加载页面,因此 SPA 应用可以快速响应用户的操作。
● 减少服务器负载:由于大部分的处理都是在客户端进行的,因此 SPA 应用可以减少服务器的负载。
● 更好的用户体验:由于 SPA 应用的页面切换非常流畅,因此它可以提供更好的用户体验。
然而,单页面应用也有一些缺点,包括:
● 较难调试:由于 SPA 应用的代码都在客户端运行,因此调试可能会更加困难。
● SEO 难度较大:对于单页面应用,搜索引擎爬虫可能无法正常抓取页面内容,因此 SPA 应用的 SEO 较为困难。为了解决这个问题,可以使用服务端渲染(Server-Side Rendering,简称 SSR)技术,在服务器端渲染 SPA 应用的内容,使得爬虫可以正常抓取页面内容。
总的来说,单页面应用是一种有效的架构方式,它可以提供流畅的用户体验,但在 SEO 和离线使用方面存在一定的困难。
13. ref 与 vm.$refs
ref
作用:用于给节点打标识
读取方式:this.$refs.xxx
vm.$refs
就是所有标记了ref
的组件的实例对象
14. Vue组件通信
1. 父子组件通信
props(父给子传参)
在父组件内,在子组件标签内绑定属性,在子组件内使用props接收
<template>
<div class="home">
<son-one :msg="msg"></son-one>
</div>
</template>
<script>
import SonOne from '@/components/SonOne.vue'
export default {
name: 'HomeView',
components: {
SonOne,
},
data() {
return {
msg: 'Hello my son!',
}
},
}
</script>
子组件接收
<template>
<div class="son">
<h3>我是子组件</h3>
<div>父组件给我传了{{ msg }}</div>
</div>
</template>
<script>
export default {
name: 'SonOne',
// props接收数据三种方式
// 1. 数组形式
// props: ['msg'],
// 2. 对象形式
// props: {
// msg: String,
// },
// 3. 对象形式+类型限制+默认值
props: {
msg: {
type: String,
required: true, // 表示必须传的参数
default: 'hello', // 默认值
},
},
data() {
return {}
},
}
</script>
子给父传参
通过在父组件的子组件标签中自定义事件,子组件通过$emit
接收
<template>
<div class="home">
<h3>我是父组件</h3>
<div>{{ count }}</div>
<son-one @changeCount="changeCount($event)"></son-one>
</div>
</template>
<script>
import SonOne from '@/components/SonOne.vue'
export default {
name: 'HomeView',
components: {
SonOne,
},
data() {
return {
count: 0,
}
},
methods: {
changeCount(value) {
this.count = value
},
},
}
</script>
<template>
<div class="son">
<h3>我是子组件</h3>
<button @click="$emit('changeCount')">修改父组件的count</button>
<button @click="change">修改父组件的count</button>
</div>
</template>
<script>
export default {
name: 'SonOne',
data() {
return {}
},
methods: {
change() {
this.$emit('changeCount', 100)
},
},
}
</script>
2. 跨组件通信(事件总线)
main.js 中注册全局总线,在组件1用$bus.$emit
调用事件将数据传给组件2, 在组件2中用$bus.$on
监听事件并接收组件1传来的参数,$bus.$off
关闭监听
new Vue({
beforeCreate() {
// 安装全局总线
Vue.prototype.$bus = this
},
render: (h) => h(App),
}).$mount('#app')
组件1:
<template>
<div class="son">
<h3>我是子组件1</h3>
<button @click="$bus.$emit('receiveParams', obj)">传值给组件2</button>
</div>
</template>
<script>
export default {
name: 'SonOne',
data() {
return {
obj: {
name: 'fg',
age: 18,
},
},
},
}
</script>
组件2:
<template>
<div class="son">
<h3>我是子组件2</h3>
<div>{{ person.name }} -- {{ person.age }}</div>
</div>
</template>
<script>
export default {
name: 'SonTwo',
data() {
return {
person: {},
}
},
methods: {
receiveParams(params) {
console.log('子组件2接收的参数', params)
this.person = params
},
},
mounted() {
this.$bus.$on('receiveParams', this.receiveParams)
console.log('监听receiveParams事件')
},
beforeDestroy() {
this.$bus.$off('receiveParams')
console.log('取消监听receiveParams事件')
},
}
</script>
3. 祖传孙 provide 与 inject
provide理解成广播数据,inject用来接收数据
注意:广播只广播一次,当数据改变时,后代组件当中接受不到更新的信息(包含基本数据类型和引用数据类型的地址),在使用引用数据类型的时候,对象的属性发生改变,后代组件可以接收到的。
祖先:
<template>
<div class="home">
<h3>我是父组件</h3>
<div>content1: {{ content1 }}</div>
<div>content2: {{ content2 }}</div>
<button @click="changeContent1">修改Content1</button>
<button @click="changeContent2">修改Content2</button>
<son-two></son-two>
</div>
</template>
<script>
import SonTwo from '@/components/SonTwo.vue'
export default {
name: 'HomeView',
components: {
SonTwo,
},
// 广播数据
provide() {
return {
content1: this.content1,
content2: this.content2,
changeContent1: this.changeContent1,
changeContent2: this.changeContent2,
}
},
data() {
return {
content1: 'fg',
content2: {
name: 'tom',
},
}
},
methods: {
changeContent1() {
this.content1 = 'fgh'
},
changeContent2() {
this.content2.name = 'jerry'
},
},
}
</script>
孙组件:
<template>
<div class="smallSon">
<h4>我是孙组件</h4>
<div>祖先传来的content1: {{ content1 }}</div>
<div>祖先传来的content2: {{ content2 }}</div>
<button @click="changeContent1">调用祖先Content1</button>
<button @click="changeContent2">调用祖先Content2</button>
</div>
</template>
<script>
export default {
name: 'SonTwo',
// 接收数据
inject: ['content1', 'content2', 'changeContent1', 'changeContent2'],
}
</script>
4. 父子组件间的数据同步
表单元素使用v-model
(上面第二道已经解释)
非表单元素使用 .sync
来绑定数据,使用@update: xxx
作为自定义事件
父组件:
<template>
<div class="home">
<h3>我是父组件</h3>
<div>{{ msg }}</div>
<son-one :msg.sync="msg"></son-one>
</div>
</template>
<script>
import SonOne from '@/components/SonOne.vue'
export default {
name: 'HomeView',
components: {
SonOne,
},
data() {
return {
msg: '我爱你',
}
},
}
</script>
子组件:
<template>
<div class="son">
<h3>我是子组件1</h3>
<h4>父组件传来的msg:{{ msg }}</h4>
<button @click="changeMsg">修改父组件的msg</button>
</div>
</template>
<script>
export default {
name: 'SonOne',
props: ['msg'],
methods: {
changeMsg() {
this.$emit('update:msg', '子组件修改后的msg')
},
},
}
</script>
15. 插槽(slot)
在Vue2中,插槽(Slot)是一种将父组件中的内容传递给子组件的机制。通过插槽,父组件可以向子组件传递HTML代码、组件实例或其他任意内容,并在子组件中使用。
插槽分为两种类型:具名插槽和默认插槽。
具名插槽可以让父组件传递多个插槽到子组件中,并在子组件中通过插槽名称进行访问。
默认插槽是没有指定名称的插槽,当父组件没有传递具名插槽时,子组件就会使用默认插槽中的内容。
通过使用插槽,Vue2中的组件可以更加灵活地处理内容,可以在子组件中动态渲染内容,实现更加复杂的UI效果。
1. 默认插槽
<template>
<div class="slotView">
<slot-list title="美女">
<img src="@/assets/imgs/7.jpg" alt="" />
</slot-list>
<slot-list title="电影"></slot-list>
<slot-list title="美食">
<img src="@/assets/imgs/4.jpg" alt="" />
</slot-list>
</div>
</template>
<script>
import SlotList from '@/components/list.vue'
export default {
name: 'SlotView',
components: {
SlotList,
},
}
</script>
<template>
<div class="list">
<h4>{{ title }}</h4>
<!-- 插槽 -->
<slot>我是默认值</slot>
</div>
</template>
<script>
export default {
name: 'SlotList',
props: ['title'],
}
</script>
2. 具名插槽
<template>
<div class="slotView">
<slot-list title="美女">
<img slot="center" src="@/assets/imgs/7.jpg" alt="" />
<h4 slot="footer">耶耶耶</h4>
</slot-list>
<slot-list title="电影">
<img slot="center" src="@/assets/imgs/3.jpg" alt="" />
</slot-list>
<slot-list title="美食">
<img slot="footer" src="@/assets/imgs/4.jpg" alt="" />
</slot-list>
</div>
</template>
<template>
<div class="list">
<h4>{{ title }}</h4>
<!-- 插槽 -->
<slot name="center">我是中间默认值</slot>
<slot name="footer">我是底部默认值</slot>
</div>
</template>
16. Vue 组件化的理解
组件是可复用的 Vue 实例,准确讲它是 VueComponent 的实例,继承于 Vue。
- 通用型组件:实现最基本的功能,具有通用性、复用性。例如 按钮组件、输入框组件、布局组件等。
- 业务型组件:用于完成具体的业务,具有一定的复用性。例如 登录组件、轮播图组件。
- 页面组件:组织应用各部分独立内容,需要在不同页面组件间切换。例如 商品列表页、详情页组件。
组件的本质过程:组件配置 => VueComponent
实例 => render()
=> Virtual DOM
=> DOM
(产生虚拟DOM)
17. Vue.set() 和 Vue.delete() 方法
Vue.set(target, propertyName, value)
给响应式对象中添加一个属性,并确保这个属性是响应式的,触发视图更新
Vue.delete(target, propertyName)
用于删除对象属性,如果对象是响应式的,删除能触发更新页面
<script>
import Vue from 'vue'
export default {
data() {
return {
users: [
{ name: '张三', age: 18 },
{ name: '李四', age: 20 },
],
}
},
beforeUpdate() {
this.users.forEach((item) => {
// 添加属性且是响应式的
Vue.set(item, 'hobby', '打飞机')
}),
this.users.forEach((item) => {
Vue.delete(item, 'age')
}),
},
}
</script>
18. $nextTick
nextTick
是 Vue.js 中一个用来在下一个事件循环中调用回调函数的方法。这个方法通常用在等待数据或 DOM 元素更新之后执行一些代码的情况下。
this.$nextTick(() => {
// 异步执行的代码
})
19. Vue-Router
20. Object.defineProperty 的使用
Object.defineProperty 方法有三个参数:
- obj: 要在其上定义属性的对象。
- prop: 要定义或修改的属性的名称。
- descriptor: 将被定义或修改的属性描述符。
descriptor 可以是以下属性的对象:
● value: 属性的值。可以是任何有效的 JavaScript 值(数字,对象,函数等)。默认值为 undefined。
● writable: 如果为 true,则可以通过赋值运算符更改属性的值。默认值为 false。
● enumerable: 如果为 true,则可以枚举该属性。默认值为 false。
● configurable: 如果为 true,则可以通过 delete 运算符删除该属性,以及修改属性的特性。默认值为 false。
const obj = {
age: 18
}
Object.defineProperty(obj, 'name', {
value: 'fg',
writable: true, // 表示可以修改
enumerable: true,
configurable: true
})
for (let k in obj) {
console.log(k); // name age
}
delete obj.name
console.log(obj) // { age: 18 }
let obj = {
name: '小明',
_age: 18,
}
Object.defineProperty(obj, 'age', {
get() {
return this._age
},
set(newAge) {
this._age = newAge
console.log(this.name + '现在' + newAge + '岁')
},
})
// 赋值
obj.age = 19
console.log(obj) // { name: '小明', _age: 19 }
注意,如果同时定义了 value、writable 和 get、set 函数,则会抛出错误。你只能选择定义属性的值或访问器函数。
21. Vuex(单向数据流)
用户通过dispatch
去触发actions
,通过commit
去触发提交mutations
,改变的state
会重新渲染页面。 actions
是异步,mutations
是同步。
这里使用了vue-presistedState
持久化Vuex数据
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import cart from './cart'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
cart,
},
plugins: [createPersistedState()],
})
cart/index.js
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
export default {
state,
getters,
actions,
mutations,
}
cart/state.js
export default {
list: [
{
id: 1,
name: '短裙黑丝',
image: 'http://www.kangliuyong.com:10002/assets/coffeeshop_userbg005917464711530541710920050214.png',
price: 200,
num: 0,
},
{
id: 2,
name: '长腿黑丝',
image: 'http://www.kangliuyong.com:10002/assets/coffeeshop_userbg00093682045514165151710920077123.png',
price: 100,
num: 0,
},
{
id: 3,
name: '吊带黑丝',
image: 'http://www.kangliuyong.com:10002/assets/coffeeshop_userbg315698298185935761710920105124.png',
price: 300,
num: 0,
},
],
totalNum: 0,
totalPrice: 0,
}
cart/getters.js
// 对state的数据进行计算处理
export default {
goods: (state) => {
return state.list
},
goodsById: (state) => (id) => {
return state.list.find((item) => item.id === id)
},
}
cart/actions.js
import { ADD, MINUS } from './mutation-types'
// 触发mutations payload载荷
export default {
increment({ commit }, payload) {
commit(ADD, payload)
},
decrement({ commit }, payload) {
commit(MINUS, payload)
},
}
cart/mutation-types.js
const ADD = 'ADD'
const MINUS = 'MINUS'
export { ADD, MINUS }
cart/mutations.js
import { ADD, MINUS } from './mutation-types'
// 改变更新state
export default {
[ADD](state, { index }) {
state.list[index].num++
state.totalNum++
state.totalPrice += state.list[index].price
},
[MINUS](state, { index }) {
if (state.list[index].num > 0) {
state.list[index].num--
state.totalNum--
state.totalPrice -= state.list[index].price
}
},
}
22. Vue3 与 Vue2 的区别
- 在vue2中,我们只要定义在data()方法中的数据就是响应式数据,在vue3中我们可以使用
ref
和reactive
定义的响应式数据 。 - 组合式api:为了让相关代码更紧凑vue3提出了组合式api,组合式api能将同一个逻辑关注点相关代码收集在一起。
- 组合式api的入口就是setup方法。在 vue2 中template不支持多根节点组件,vue3支持了多根节点的组件。
vue2的响应式使用的是Object.defineProperty()实现的,Vue3使用的Proxy实现的。
23. keep-alive
就是一个缓存组件,用于提升性能
如果加入keep-alive
,第二次或者第n次进入组件只会执行activated
这个周期。
场景:首页进入详情页,如果用户在首页每次点击都是相同的,那么详情页就不用再次发起请求,直接用缓存的,如果不同,就要重新请求。
24. scoped
让样式在本组件中生效,不影响其他组件
给节点新增自定义属性,然后CSS根据属性选择其添加样式
25. Vue怎么做样式穿透
使用scss:npm install sass-loader node-sass --save
<style lang='scss' scoped>
scss样式穿透:父元素 /deep/ 子元素
或者 ::v-deep()
使用less:npm install less less-loader --save
vue样式穿透
26. props 和 data 优先级谁高
props => methods => data => computed => watch
27. Vue设置代理
devServer.proxy 可以是指向开发 API 服务器的字符串:
在vue.config.js中配置
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}