文章目录
- Vue 的核心是什么/请简述你对 vue 的理解
- 请简述 vue 的单向数据流
- 槽口请简述
- Vue 常用的修饰符有哪些
- 1. 普通修饰符
- 2. 事件修饰符
- 3. 键盘修饰符
- 4. 系统修饰符
- v-text 与{{}}与 v-html 区别
- v-on 可以绑定多个方法吗
- Vue 循环的 key 作用
- 什么是计算属性
- Vue 单页面的优缺点
- Vuex 是什么?怎么使用?在那种场景下使用
- 使用
- 使用场景
- Vue 中路由跳转方式(声明式/编程式)
- Vue 中如何进行动态路由设置?有哪些方式?怎么获取传递过来的数据?
- Vue 路由传参的两种方式,params 和 query方式与区别
- vue 跨域的解决方式
- Vue 的生命周期
- Vue 路由的实现
- 路由守卫
- Route 与 router 区别
- Vue 路由模式 hash 和 history,简单讲一下
- Vue 数据绑定的几种方式
- Vue 注册一个全局组件
- 常用库的常用组件以及属性
- Vue-cli 中如何自定义指令
- Vue 中指令有哪些
- v-if和v-show的区别
- 对 vue 中 keep-alive 的理解
- 如何让组件中的 css 在当前组件生效
- Mvvm 与 mvc 的区别
- Vue 组件中的 data 为什么是函数
- Vue 双向绑定的原理
- 什么是虚拟dom?
- 虚拟DOM在vue框架中是怎么工作的
- 介绍一下diff算法
- vue中涉及了哪些设计模式
- 如果一个组件在多个项目中使用怎么办
- Vue 首屏加载慢的原因,怎么解决的?
- 白屏时间怎么检测,怎么解决白屏问题?
- v-for 与 v-if 优先级
- vue3是如何变得更快的?
- a. Diff 方法优化
- b. 新增的静态标记(PatchFlag)
- c. 事件侦听器缓存
- Vue 如何定义一个过滤器
Vue 的核心是什么/请简述你对 vue 的理解
- Vue.js的核心是一个用于构建用户界面的渐进式MVVM框架。
- 它主要专注于视图层,提供了一套简洁的API和高效的数据绑定机制,使开发者能够更容易地构建交互式的单页面应用(SPA)和动态用户界面。
- “渐进式” 指的是你可以逐步采用Vue.js,根据你项目的需要引入其功能,而不必强制性地将整个框架应用到整个项目。
- MVVM:Model(模型, 代表应用程序的数据和业务逻辑)、View(视图, 代表用户界面)和ViewModel(视图模型,负责处理视图和模型之间的通信。)
- MVVM模式的一个关键特点是数据绑定,这使得视图和模型之间的同步变得自动化。
请简述 vue 的单向数据流
- Vue.js的单向数据流是指数据在应用中的流动方向是单一的,从父组件到子组件。父组件通过将数据作为props传递给子组件,子组件可以通过props来接收这些数据。
- 父向子(单向数据流):
//父组件
const message = ref("hi!son")
<childComponent :messagefromDad="message">
//子组件
声明式:
let {placeholder}=defineProps(['placeholder'])
<input :placeholder="placeholder">
选项式:
export default{
props:{
messagefromDad:String
}
}
- 子向父(通过事件)
//son
声明式(setup语法糖写法)
const instance = getCurrentInstance();
onMounted(()=>{
instance.emit('msg',"hello!dad!")
})
//dad
<son @msg="handleMsg"></son> //监听msg自定义事件
<div>search儿子传来的东西:{{ msgfromson }}</div>
let msgfromson=ref('...')
const handleMsg = (e)=>{
console.log("传过来的语句是:",e);
msgfromson=e
}
this.$emit
是 Vue.js 中用于触发自定义事件的方法
this.$emit('eventName', payload);
payload(载荷) 是可选的参数,用于传递额外的数据给父组件。
槽口请简述
子组件的插槽是为了留给父组件,允许父组件为不同插槽位置分配不同的内容。
子:<slot name=xxx>
父:<template v-slot:xxx>
<!-- 父组件:App.vue -->
<template>
<MyComponent>
<!-- 使用具名插槽 -->
<template v-slot:header>
<h2>Header Content</h2>
</template>
<template v-slot:footer>
<p>Footer Content</p>
</template>
</MyComponent>
</template>
<!-- 子组件:MyComponent.vue -->
<template>
<div>
<!-- 具名插槽位置 -->
<slot name="header"></slot>
<!-- 具名插槽位置 -->
<slot name="footer"></slot>
</div>
</template>
Vue 常用的修饰符有哪些
修饰符是一些附加在指令后面的特殊关键字,用于修改指令的行为。
1. 普通修饰符
.stop
: 阻止事件冒泡。.prevent
: 阻止默认行为。.capture
: 使用事件捕获模式。.self
: 只在事件是从触发事件的元素自身触发时触发。不会触发子孙冒泡的事件。.lazy
:<input v-model.lazy="message">
将会在 “change” 事件而不是 “input” 事件时更新数据,从而减少更新的频率,提高性能。
2. 事件修饰符
.once
: 只触发一次。事件处理函数会在第一次触发后被自动解绑
-.passive
: 指示浏览器不要等待preventDefault
的调用,用于改善移动设备上的滚动性能【没看懂】
3. 键盘修饰符
使用:<input @keyup.enter="handleEnterKey">
.enter
: 监听 Enter 键。.tab
: 监听 Tab 键。.delete
或.backspace
: 监听删除/退格键。.esc
: 监听 Esc 键。.space
: 监听空格键。.up
: 监听上箭头键。
4. 系统修饰符
.ctrl
: 用于监听 Ctrl 键。.alt
: 用于监听 Alt 键。.shift
: 用于监听 Shift 键。.meta
或.cmd
: 用于监听 Meta 键 (Command 键或 Windows 键)。
v-text 与{{}}与 v-html 区别
<div v-text="message"></div>
<div>{{ message }}</div>
<div v-html="htmlContent"></div>
- v-text 与{{}}效果一样,不会解析 HTML,会将表达式的结果作为纯文本插入到元素中。
- 用于将表达式的结果作为 HTML 解析并插入到元素中,它可以导致XSS攻击(跨站脚本攻击)
v-on 可以绑定多个方法吗
可以。
不同事件键值对<button v-on="{ click: method1, mouseover: method2 }">Click me</button>
相同事件逗号分割<button v-on:click="[method1, method2]">Click me</button>
Vue 循环的 key 作用
性能和正确渲染:使用key有助于Vue识别每个节点的唯一性,节点内容更新时,比较同一key节点的内容,Vue可以更快速和更准确地判断哪些节点需要被更新、删除或添加,从而降低渲染的成本。
什么是计算属性
- 计算属性是用来声明式的描述一个值依赖了其他的值,并且只有在依赖的属性发生变化时才会重新计算。
- 每个计算属性都包括一个 getter 和 setter 方法,它们分别负责在读取属性值和修改属性值时执行相应的逻辑。
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName;
},
set(value) {
const names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
}
Vue 单页面的优缺点
优点
- 快速响应和更流畅的用户体验:SPA 利用前端路由,只更新页面中的部分内容,不需要整页刷新,因此可以实现快速的用户体验。
- 减轻服务器负担: 由于不需要为每个页面请求都返回完整的 HTML 页面,服务器负担较轻。
- 代码复用: 可以通过组件化的方式将代码分割为可复用的组件,提高代码的可维护性。
缺点:
4. 首次加载较慢: SPA 首次加载需要下载这个应用必要的的 JavaScript 脚本(比如SPA的核心应用逻辑、路由配置和必要的资源),可能导致较长的首次加载时间。
5. SEO 难度较大: 搜索引擎优化对于 SPA 较为复杂,因为页面内容大部分是通过 JavaScript 动态生成的,而搜索引擎爬虫可能无法获取到这些内容。
搜索引擎优化(Search Engine Optimization)
Vuex 是什么?怎么使用?在那种场景下使用
vue中的状态管理库,主要用于管理应用程序中的共享状态(state)和对状态的操作
状态可以理解为应用中的共享的响应式数据
核心包括
- State(状态): 即应用程序的数据源,存储着应用程序中需要共享和管理的状态。
- Getter(获取器): 允许组件从Store中获取状态,类似于组件的计算属性。
- Mutation(突变): 通过提交 Mutation来改变状态,是唯一能够改变状态的地方。它们是同步的事务。
- Action(动作):类似于Mutation,但是可以包含异步操作。通过提交 Action 来触发 Mutation。
Action 负责异步逻辑,然后通过 commit 触发对应的 Mutation 来改变状态。这种分工的设计可以使代码更模块化、可维护
使用
很重要,务必能默写下来
// store.js----------------------------------------------------
import { createStore } from 'vuex';
const store = createStore({
state() {
return {
count: 0
};
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync(context) {
//Action 函数接收一个与 store 实例具有相同方法和属性的 context 对象
setTimeout(() => {
context.commit('increment');
}, 1000);
}
},
getters: {
doubleCount: state => state.count * 2
}
});
export default store;
//main.js引入store--------------------------------------------------
new Vue({
// 将 store 实例传递给 Vue 实例
store,
render: h => h(App)
}).$mount('#app');
//.vue中使用--------------------------------------------------------------
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
<script>
export default {
methods: {
increment() {
this.$store.commit('increment'); //commit(提交)用于触发同步的状态变更(Mutation)
},
incrementAsync() {
this.$store.dispatch('incrementAsync'); //dispatch(分发),分发一个 Action 来处理异步逻辑
}
}
}
</script>
$store
的$
表示store是 Vue 实例上的一个默认属性.
以 $ 开头的属性和方法都是 Vue 提供的一些特殊接口,用于访问和操作 Vue 实例
比如:
this.$el:当前 Vue 实例的根 DOM 元素。
this.$data:Vue 实例的数据对象
this.$props:包含了父组件传递给当前组件的属性
this.$emit('custom-event', 'Hello from child!');用于触发当前实例上的事件。
this.$router.push('/about'); Vue Router 的实例,用于在组件中进行路由导航。
this.$route 包含当前路由信息的对象,可以访问当前路由的参数、查询参数等。
使用场景
- 大型单页应用: 规模大,组件嵌套深,且有多个组件需要共享状态时,Vuex 的集中式状态管理能够更好地组织和管理应用的状态。
- 团队协作开发:使用 Vuex 能够使团队成员更容易理解和协作,因为状态是集中管理的,避免了状态散落在各个组件中导致的维护困难。
具体使用场景:登录状态,加入购物车,音乐播放
对比小程序开发中的原生状态管理
getApp().globalData.userInfo = userInfo;
Vue 中路由跳转方式(声明式/编程式)
-
声明式导航(Declarative Navigation):
<!-- 在模板中使用 router-link 进行声明式导航 --> <router-link to="/home">Home</router-link>
-
编程式导航(Programmatic Navigation):
// 在组件的方法中使用编程式导航 methods: { goToHome() { this.$router.push('/home'); } }
-
带参数的路由跳转:
<!-- 在声明式导航中传递参数 --> <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
// 在编程式导航中传递参数 this.$router.push({ name: 'user', params: { userId: 123 }});
-
带查询参数的路由跳转:
<!-- 在声明式导航中传递查询参数 --> <router-link :to="{ path: 'search', query: { keyword: 'vue' }}">Search</router-link>
// 在编程式导航中传递查询参数 this.$router.push({ path: 'search', query: { keyword: 'vue' }}); 使用this.$route.query.keyword;来接收
Vue 中如何进行动态路由设置?有哪些方式?怎么获取传递过来的数据?
动态路由也可以叫路由传参
动态路由有 query 和 prrams 两种方式传参
Vue 路由传参的两种方式,params 和 query方式与区别
形式的区别
- /user/:id 冒号
- ?key=value 问号
用途的区别
- Params适合传递对应资源的标识符,如用户ID。
- Query适合传递一些非关键性的、可选的参数,如页面过滤条件等。
如果是传递密码,使用POST请求将敏感信息包含在HTTP的请求体中,使用HTTPS协议加密
vue 跨域的解决方式
配置代理
配置代理的目的是让前端的请求先经过开发环境的服务器,再由该服务器向后端服务器发起请求。这样,对于浏览器而言,所有请求都是发往同一个域名,就不会触发同源策略的限制,从而解决了跨域问题。
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};
'/api'
是匹配请求路径的规则,表示所有以/api
开头的请求都会被代理。target: 'http://api.example.com'
表示代理到的目标地址,即后端的真实地址。changeOrigin: true
表示改变请求源,允许跨域。pathRewrite: {'^/api': ''}
表示将请求路径中的/api
替换为空字符串,确保最终的请求路径是正确的。
这样配置后,比如前端发起的请求是/api/data
,实际上会被代理到http://api.example.com/data
,从而避免了浏览器的同源策略问题。- 这种方式仅在开发环境中使用,因为在生产环境中应该由后端服务正确配置 CORS 头信息以解决跨域问题。
Vue 的生命周期
生命周期(Lifecycle)是指一个对象从被创建到被销毁所经历的各个阶段。给了用户在不同阶段添加自己的代码的机会。
创建、挂载、更新、销毁
- setup:
- 替换了v2中的beforeCreate和created
- 组件的入口,在组件实例创建之前执行,初始化组件的状态和行为
- onBeforeMount 和 onMounted
- 前者 未挂载到DOM上
- 后者已经挂载了
- onBeforeUpdate 和 onUpdated
- 组件更新之前和之后执行逻辑
- 监控数据的变化
- onBeforeUnmount 和 onUnmounted
- 替代了destroyed
-
数据的请求一般放在created 和 mounted 这两个生命周期中,他们的区别是:
-
1.如果数据请求不依赖于 DOM 的状态,而是在
组件实例创建时就需要获取,那么使用 created 是合适的。(例如,创建组件之前,就要请求服务器返回组件的颜色)
2.如果需要在组件被挂载到页面后再进行数据请求,以确保 DOM 的可用性,那么使用 mounted 是更合适的选择。(例如 请求服务器数据然后放在某个dom组件上,需要挂载后才能获得这个dom组件)
Vue 路由的实现
//创建一个路由实例,配置路由映射关系。
Vue.use(VueRouter);
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
//在组件中通过 $route.params.id 获取参数的值
];
const router = new VueRouter({
routes
});
//实现页面跳转
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
路由守卫
- 路由守卫允许你在路由发生变化前、后或者是变化过程中执行一些自定义的逻辑。
- 它们是一些钩子函数,可以用于处理导航过程中的不同阶段。
-
全局前置守卫 (
beforeEach
):
beforeEach
被用来全局性地导航守卫,可以在路由切换前执行一些逻辑。如果在全局前置守卫中调用next()
,则导航会继续进行;如果调用next(false)
,则导航会被中断;如果调用next('/path')
,则导航会被重定向到新的路径。router.beforeEach((to, from, next) => { // 在路由切换前执行一些逻辑 if (to.meta.requiresAuth && !auth.isAuthenticated) { // 如果需要验证身份但用户未认证,重定向到登录页 //meta用于存储自定义的元信息,允许你为每个路由定义一些额外的属性 //to表示即将要进入的目标路由对象 next('/login'); } else { // 继续路由导航 next(); } });
-
全局解析守卫 (
beforeResolve
):
beforeResolve
类似于beforeEach
,
最大的区别在于在 beforeResolve 中,可以确保所有的组件都已经创建。适合等待异步组件解析。什么是“异步组件解析”
有时候你希望当路由导航到 /async 路径时,才会实际加载和渲染某个组件。
component属性的值可以是一个组件对象或者一个返回组件对象的函数
在这里,component属性的值是一个函数,该函数返回一个 import() 调用,
实现了异步加载组件的效果。这是异步组件解析的一种常见用法。
const routes = [
{
path: '/async',
component: () => import('./AsyncComponent.vue')
},
// ...
];
router.beforeResolve((to, from, next) => {
// 在导航确认前执行逻辑
// 适用于需要等待异步组件解析完成的情况
next();
});
-
全局后置守卫 (
afterEach
):
afterEach
在路由切换后执行,无论导航是成功的还是失败的,都会触发。router.afterEach((to, from) => { // 在路由切换后执行一些逻辑 });
-
路由独享的守卫:
你还可以在路由配置中直接定义守卫,这些守卫只会对特定的路由产生作用。const routes = [ { path: '/profile', component: Profile, beforeEnter: (to, from, next) => { // 在进入 '/profile' 路由前执行逻辑 // 这里的逻辑不同于全局前置守卫 next(); } } ];
Route 与 router 区别
- “Route” 通常是指被路由器管理的一个单独的路径规则,包括路径的URL和对应的组件。
- 而 “router” 则是指整个路由系统,用于处理路由切换的逻辑,包括提供一些API比如路由守卫的钩子函数在切换时执行一些操作。
Vue 路由模式 hash 和 history,简单讲一下
Vue Router 支持两种模式:hash 模式和 history 模式。这两种模式决定了在浏览器中如何处理路由。
const router = new VueRouter({
mode: 'history'/'hash',
routes: [...]
});
-
Hash 模式:
- URL格式: 在 hash 模式下,URL 中的路径会带有一个
#
符号,例如http://example.com/#/about
。
而在前端路由的上下文中,“Hash 模式” 实际上是指 URL 中的 hash 部分(即 # 号后面的部分)的使用。
在传统的网页中,哈希主要用于页面内锚点(锚点链接),例如 https://example.com/page#section1,其中 section1 是页面内的某个锚点。- 特点: Hash 模式通过监听
window.location.hash
的变化来实现路由的切换。因为 hash 的改变不会导致浏览器向服务器发出请求,所以可以避免一些问题,特别是在使用单页应用(SPA)时。 - 优点: 不需要特殊的服务器配置,可以在所有的 web 服务器上运行。
- 缺点: URL 中带有
#
,可能看起来不太美观;此外,由于 hash 值变化不会触发页面刷新,有些浏览器历史记录、页面跳转等功能可能受到一些限制。(浏览器的历史记录中记录的是 hash 值的变化,而不是整个 URL 的变化。这可能导致在浏览器的历史记录中看到多个相同的 URL)
- URL格式: 在 hash 模式下,URL 中的路径会带有一个
-
History 模式:
- URL格式: 在 history 模式下,URL 中的路径更加自然,不带有
#
,例如http://example.com/about
。要求服务器配置**,确保在任何路由下都返回同一个 HTML 页面,以防止在刷新页面时出现 404 错误。 - 优点: URL 更加美观,不带有
#
,更符合传统的 URL 格式。 - 缺点: 需要特殊的服务器配置,以处理在刷新页面时的路由问题。
- URL格式: 在 history 模式下,URL 中的路径更加自然,不带有
Vue 数据绑定的几种方式
<span>{{ message }}</span>//插值表达式(Interpolation)
<img :src="imageUrl"> //绑定属性(v-bind)
<button @click="handleClick">Click me</button>//事件绑定(v-on)
<input v-model="username">//双向绑定(v-model),确保视图和数据的同步
计算属性(computed)
监听属性(watch)
Vue 注册一个全局组件
//在入口文件中
// 注册全局组件
Vue.component('my-component', MyComponent);//my-component' 是你为组件指定的全局标签名
在其他模板中使用: <my-component></my-component>
//注册局部组件
import MyLocalComponent from './MyLocalComponent.vue';
export default {
components: {
'my-local-component': MyLocalComponent
}
// ...其他配置
};
常用库的常用组件以及属性
tedesign
按钮
<t-button theme="primary" icon="search" shape="square" size="large"></t-button>
分割线
<t-divider dashed content="文字信息" align="left" />
input
<t-input
label="手机号"
placeholder="输入手机号码"
value="{{phoneNumber}}"
type="number"
tips="{{phoneError ? '手机号输入不正确' : ''}}"
bindchange="onPhoneInput"
>
进度条
<t-progress percentage="{{percentage}}" ariaLabel="{{ percentage + '%' }}" />
Vue-cli 中如何自定义指令
-
创建插件文件:
在项目的src
目录下创建一个plugins
文件夹,然后在该文件夹内创建一个 JavaScript 文件,用于定义你的自定义指令。// src/plugins/customDirective.js export default { install(Vue) { // 在这里定义你的自定义指令 Vue.directive('custom', { // 指令钩子函数 bind(el, binding) { // 指令绑定时的逻辑 el.style.color = binding.value; }, // 更多钩子函数和配置项可以根据需要添加 }); } };
-
注册插件:
在src/main.js
文件中注册你的插件。import Vue from 'vue'; import App from './App.vue'; import customDirectivePlugin from './plugins/customDirective'; Vue.config.productionTip = false; // 注册自定义指令插件 Vue.use(customDirectivePlugin); new Vue({ render: h => h(App), }).$mount('#app');
-
使用自定义指令:
<template> <div v-custom="'red'">This text is affected by the custom directive.</div> </template>
Vue 中指令有哪些
v-for:循环数组,对象,字符串,数字
v-on:绑定事件监听
v-bind:动态绑定一个或者多个属性
v-model:表单控件或者组件上创建双向绑定
v-if v-else v-else-if 条件渲染
v-show 根据表达式真假,切换元素的 display
v-html 更新元素的 innerhtml
v-text 更新元素的 textcontent
v-pre 跳过这个元素和子元素的编译过程,这意味着不会对其中的 Mustache 插值或指令进行编译,而直接输出原始的代码。
v-clock 这个指令保持在元素上知道关联实例结束编译
v-once 只渲染一次
v-if和v-show的区别
- 两种不同的条件渲染方式
- 主要区别在于元素在 DOM 中的存在与否以及性能方面的考虑。
- 对于vif,当表达式为假时,元素从 DOM 中移除
- vshow, 无论表达式为真还是假,元素始终保留在 DOM 中
- 频繁切换时,show不需要重复创建和销毁元素,性能更好。
对 vue 中 keep-alive 的理解
keep-alive 是 vue 的内置组件,用于缓存动态组件,以避免在组件切换时销毁和重新创建组件实例,从而提升性能。
<!-- ParentComponent.vue -->
//示例:两个动态切换的组件,分别是 HomeComponent 和 AboutComponent
<template>
<div>
<button @click="toggleComponent">Toggle Component</button>
<keep-alive>
<component :is="currentComponent" :key="currentComponent"></component>
</keep-alive>
</div>
</template>
<script>
import HomeComponent from './HomeComponent.vue';
import AboutComponent from './AboutComponent.vue';
export default {
data() {
return {
currentComponent: 'HomeComponent',
};
},
methods: {
toggleComponent() {
this.currentComponent = (this.currentComponent === 'HomeComponent') ? 'AboutComponent' : 'HomeComponent';
},
},
};
</script>
如何让组件中的 css 在当前组件生效
在 styled 中加上 scoped
Mvvm 与 mvc 的区别
- MVVM(Model-View-ViewModel)和 MVC(Model-View-Controller)都是设计模式,用于组织和管理应用程序的结构。
- 最主要的区别在于MVC数据流动是单向的,只能从模型流动到视图(MVC 中,一般来说,需要手动处理用户输入的数据,将其更新到模型中)。而MVVM的视图和视图模型之间的数据变化会同步更新。
Vue 组件中的 data 为什么是函数
// 不推荐写法
Vue.component('my-component', {
data: {
count: 0
},
template: '<div>{{ count }}</div>'
});
上述代码中,data
直接使用了一个对象。这样的写法存在潜在的问题,因为组件在 Vue 中是可以复用的。如果在多次使用了这个组件,那么它们会共享同一个数据对象,导致状态混乱。
为了解决这个问题,Vue 要求 data
必须是一个函数,而不是直接是一个对象。这样每个组件实例在创建时都会调用 data
函数,返回一个独立的数据对象,确保每个组件实例都有属于自己的数据对象,不会相互影响。
// 推荐写法
Vue.component('my-component', {
data() {
return {
count: 0
};
},
template: '<div>{{ count }}</div>'
});
Vue 双向绑定的原理
另一种问法:Vue 双数据绑定过程中,这边儿数据改变了怎么通知另一边改变
步骤:
- 数据劫持
- 使用proxy为vm.$data属性设置getter和setter方法来劫持读取和设置该属性的操作,
- 依赖收集(Dependency Collection):
在数据劫持的过程中,当读取属性值时,就说明要为这个属性绑定观察者,Vue会将这个属性依赖的观察者(Watcher)收集起来,以便在数据变化时通知这些观察者进行更新。 - 编译(Compilation):
- Vue使用模板编译器解析Vue模板,识别其中的指令,特别是
v-model
等双向绑定的指令。 - 为找到的指令和表达式创建对应的Watcher实例绑定点相关的Watcher实例,并在数据变化时触发更新。
- Vue使用模板编译器解析Vue模板,识别其中的指令,特别是
- 更新视图(Updating the View):
- 当数据发生变化时,通过数据劫持中的setter,触发对应属性的setter,进而触发依赖收集中的Watcher实例的update方法,最终更新视图。
- 如果是双向绑定,当视图中的表单元素发生变化时,Watcher也会通知数据进行更新。
什么是虚拟dom?
- 是一个对象来描述真实的 DOM 结构
- 传统开发中更新页面直接获取dom元素然后直接在dom上修改,这种直接更新DOM的方式可能会导致性能问题
- 当应用状态发生变化时,首先更新虚拟DOM,然后通过比较虚拟DOM与实际DOM的差异,最终只更新必要的部分,而不是整个页面,避免频繁操作实际DOM。
虚拟DOM在vue框架中是怎么工作的
- Vue的模板首先通过渲染函数得到一个虚拟DOM树(VNode)
- 当数据发生变化时,Vue会生成一个新的虚拟DOM树。
- 然后,新的虚拟DOM树与旧的虚拟DOM树进行比较,找到差异。(Diff算法)目标是尽量减少实际DOM操作的次数。
- 找到差异后,Vue会生成一系列的DOM操作指令,然后批量执行这些指令,最终将实际DOM结构更新为新的状态。
介绍一下diff算法
- 新旧虚拟DOM树的根节点开始,递归进行深度优先遍历
- 比较相同位置的新旧节点的属性、类型、子节点等等。
- 如果节点不同,然后生成一组描述这些差异的操作指令,这些操作指令最终用于更新实际的DOM结构
vue中涉及了哪些设计模式
-
观察者模式(Observer):
- Vue.js 使用观察者模式来实现数据绑定。
- 是一种一对多的依赖关系
- 举例:天气预报系统,其中包括一个天气台作为主题,以及一群订阅者(观察者)。每当天气发生变化时,天气台就会向所有订阅者发送通知。订阅者们收到通知后,可以根据自己的兴趣决定是否更新自己的状态。
-
发布-订阅模式(Pub-Sub):
- Vue 中的事件系统就是一个典型的发布-订阅模式的实现,通过
$on
和$emit
方法来监听和触发事件。 - 其实类似于观察者模式
- Vue 中的事件系统就是一个典型的发布-订阅模式的实现,通过
-
单例模式(Singleton):
- Vue 实例是单例的,一个应用中通常只有一个根实例。避免多个实例导致资源浪费或不一致的状态。
想象一下,你是一家咖啡店的老板。你决定在咖啡店里安装一个特殊的咖啡机,这个咖啡机有一些特殊的功能,比如能够调整咖啡的温度和浓度。你希望确保整个咖啡店只有一台这样的特殊咖啡机,并且所有的员工都能够通过一个统一的方式来使用它。
这里就是单例模式的应用:
1. 特殊咖啡机的制造:你安排工程师制造了这个特殊咖啡机。为了确保只有一台,你让这个咖啡机的构造方式非常特殊,不能通过常规的购买或其他方式获得。这就好比单例模式中,通过私有构造函数来限制实例的创建。 以防止通过 new 关键字直接实例化对象。
2. 全局访问点:为了让所有员工能够使用这个特殊咖啡机,你在咖啡店的中央厨房放置了这台咖啡机,并贴上了一张大大的标签,上面写着“特殊咖啡机”。所有的员工都通过这个全局访问点来获取咖啡,而不是在各个角落都放一台相同的咖啡机。这就好比单例模式中,通过静态实例和一个全局访问点来获取单例对象。
3. 只有一台咖啡机:由于特殊咖啡机是唯一的,无论员工们多么努力,他们都只能使用这一台咖啡机。这就好比单例模式确保在整个应用程序中只有一个实例存在。 -
策略模式(Strategy):
- Vue 的指令(Directives)可以被看作是一种策略模式的应用,不同的指令实现了不同的策略,例如
v-if
、v-show
可以互相替换。
形象地说,当你在网上购物并且决定结账时,你会选择支付方式,比如信用卡或 PayPal。
这时,你的购物车(上下文)就会调用选中支付方式(具体策略类)来完成支付。如果有新的支付方式出现,你只需添加一个新的具体策略类,而不需要修改购物车的代码。
这种模式的优势在于你可以轻松地切换支付方式,而不必改变购物车的代码。这就是策略模式的核心思想:定义一系列算法,将它们封装起来,并且使它们可以互相替换,而不影响客户端(购物车)的使用。 - Vue 的指令(Directives)可以被看作是一种策略模式的应用,不同的指令实现了不同的策略,例如
-
装饰者模式(Decorator):
- Mixins 在 Vue 中的使用可以被视为一种装饰者模式,通过混入(mixin)来扩展组件的功能。装饰者模式的优点在于它提供了一种灵活的方式来扩展对象的功能
- 什么时mixin?理解成小插件,将一些功能模块化,然后在多个组件中复用。
好比点奶茶时加的各种小料。
-
组合模式(Composite):
- Vue 组件的嵌套和组合可以被看作是一种组合模式的实现,通过简单组件的组合可以构建复杂的用户界面。
-
代理模式(Proxy):
- Vue 中使用了代理模式来实现数据的响应性。通过
Object.defineProperty
或Proxy
来劫持对象的属性访问,从而实现数据的监听和更新。
- Vue 中使用了代理模式来实现数据的响应性。通过
如果一个组件在多个项目中使用怎么办
将该组件封装为一个npm包,然后在每个项目中通过npm进行安装和引用。
具体流程
-
创建 npm 账号: 如果你没有 npm 账号,你需要在 npm 官方网站 上注册一个账号。
-
初始化项目: 在你的 Vue 组件项目中,运行以下命令初始化
package.json
文件:npm init
根据提示填写相关信息,确保设置了正确的
name
、version
、description
等字段。 -
编写组件代码: 编写你的 Vue 组件,确保组件代码正常运行。你的组件文件通常会被放在
src
文件夹下。 -
创建入口文件: 在项目根目录创建一个入口文件,通常命名为
index.js
。这个文件会作为你的 npm 包的入口。// index.js import YourComponent from './src/YourComponent.vue'; // Export your component export default YourComponent;
-
创建
.npmignore
文件(可选): 如果你希望在发布 npm 包时忽略一些文件,可以创建一个.npmignore
文件。这个文件的作用类似于.gitignore
,用于指定哪些文件或文件夹不会被包含在 npm 包中。 -
打包和构建: 你需要使用构建工具(如 webpack、Rollup 等)来将你的组件打包成一个可用于发布的文件。
-
发布到 npm: 运行以下命令发布你的 npm 包:
npm login # 登录到你的 npm 账号 npm publish
这将把你的组件发布到 npm 仓库中。
-
在其他项目中安装并引用: 在其他 Vue 项目中,你可以通过以下命令安装并引用你的组件:
npm install your-component-package-name
// 在项目中引用你的组件 import YourComponent from 'your-component-package-name'; // 在组件中使用 export default { components: { YourComponent }, // 其他组件配置... };
Vue 首屏加载慢的原因,怎么解决的?
原因:
太多/太大的资源和组件需要加载。
解决:
- 资源:压缩,懒加载
- 组件:动态导入模块(也叫路由懒加载,只有在访问相关路由时才会被加载)
import('./MyComponent.vue');
- 按需加载UI框架
白屏时间怎么检测,怎么解决白屏问题?
白屏时间是指用户打开网页后到页面开始呈现内容之间的时间。
检测:
chrome开发者工具中的lighthouse
解决:
- 代码分割
- 按需加载组件
- 懒加载
- 压缩
- v-cloak(cloak:披风)
<style>
[v-cloak] {
display: none;
}
</style>
<div v-cloak>
<!-- 这里的内容会在 Vue 实例初始化完成后显示 -->
{{ message }}
</div>
v-cloak解决的问题: 如果 Vue 实例初始化过程中,这些 Mustache 表达式尚未被解析,页面就有可能出现短暂的未编译内容显示。
通过上述样式,[v-cloak] 的元素在 Vue 实例初始化之前会被隐藏,而在 Vue 实例初始化完成后,v-cloak 指令会被移除,元素显示出来。
v-for 与 v-if 优先级
for比if高,下面这个例子,会先执行vfor渲染好多li,然后根绝if判断要不要渲染出来这个li。
<ul>
<li v-for="item in items" v-if="item.isActive">
{{ item.name }}
</li>
</ul>
vue3是如何变得更快的?
Vue 3.0 在性能方面进行了多方面的优化,其中包括:
a. Diff 方法优化
在 Vue 2.x 中,虚拟 DOM 的对比是全量的,即每次更新都会对整个虚拟 DOM 树进行对比,导致性能开销较大。Vue 3.0 引入了一些优化措施,使得 diff 过程更加高效。
b. 新增的静态标记(PatchFlag)
在 Vue 3.0 中,引入了静态标记(PatchFlag)的概念。在与上次虚拟节点进行对比时,只会比较带有 patch flag 的节点,通过 flag 的信息可以得知当前节点需要具体对比的内容。这一优化避免了对不必要的节点进行比较,提高了 diff 过程的效率。
同时,Vue 3.0 还引入了静态提升(hoistStatic)的机制。在 Vue 2.x 中,无论元素是否参与更新,每次都会重新创建。而在 Vue 3.0 中,对于不参与更新的元素,只会被创建一次,并在每次渲染时被不停地复用。这减少了创建和销毁的开销,提高了性能。
c. 事件侦听器缓存
Vue 3.0 引入了事件侦听器缓存的机制,其中 cacheHandlers
是一个相关的配置项。默认情况下,如果一个事件处理函数像 onClick
被视为动态绑定,Vue 会追踪它的变化。但是,由于通常这是同一个函数,实际上没有必要每次都重新追踪变化。
因此,启用 cacheHandlers
配置后,Vue 3.0 会直接缓存起来并复用,避免了不必要的追踪和处理,提高了事件处理的效率。
Vue 如何定义一个过滤器
过滤器本质就是一个函数,对输入的数据进行某些处理后输出。
【!】在Vue 3中,过滤器的概念已经被废弃。直接写个函数处理一下即可。
1.定义
// 全局定义一个名为 'capitalize' 的过滤器
Vue.filter('capitalize', function(value) {
if (!value) return '';
// 将输入的字符串首字母转为大写
return value.toString().charAt(0).toUpperCase() + value.slice(1);
});
//局部定义
export default {
data() {
return {
message: 'hello world',
};
},
filters: {
uppercase: function(value) {
return value.toUpperCase();
},
},
};
2.使用
<!-- 在模板中使用 'capitalize' 过滤器 -->
<p>{{ message | capitalize }}</p>
message 的值将被传递给 capitalize 过滤器进行处理,然后显示在页面上。注意,在过滤器使用管道符 | 连接到表达式或插值之后。