Vue
30959字约103分钟
2025-02-11
Vue2.0的生命周期? Vue3.0的生命周期?
详情
Vue2.0的生命周期
beforeCreate
:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
created
:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 (data observer)、property 和 method 的计算、watch/event 事件回调。然而,挂载阶段还没有开始,$el 属性目前不可用。
beforeMount
:在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted
:el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el 也在文档内。
beforeUpdate
:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated
:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed
:实例已经销毁之后调用。在这一步,所有的事件监听器和子实例都已经被销毁。
Vue3.0的生命周期
Vue 3.0 引入了组合式 API,生命周期钩子的使用方式有所变化,但功能基本保持一致。以下是 Vue 3.0 的主要生命周期钩子及其对应的 Vue 2.0 钩子:
setup
:在创建组件实例时,初始化 props 之后立即调用。setup 是组合式 API 的入口点,在beforeCreate
和created
钩子之前调用。
beforeCreate
:在实例初始化之后,数据观测 (data observer
) 和event/watcher
事件配置之前被调用。
created
:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 (data observer
)、property
和method
的计算、watch/event
事件回调。然而,挂载阶段还没有开始,$el
属性目前不可用。
beforeMount
:在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted
:el 被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时vm.$el
也在文档内。
beforeUpdate
:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated
:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
beforeUnmount
:实例销毁之前调用。在这一步,实例仍然完全可用。
unmounted
:实例已经销毁之后调用。在这一步,所有的事件监听器和子实例都已经被销毁。
Vue3.0 更新了哪些内容?
详情
Vue.$nextTick() 是什么?
详情
Vue.$nextTick()
是 Vue.js 提供的一个非常实用的方法,用于在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
原理
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher
被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。所以,如果你想要基于更新后的 DOM 状态来做点什么,就需要使用 Vue.$nextTick()
。
使用场景:
- 如果想要在修改数据后立刻得到更新后的DOM结构,可以使用
Vue.nextTick()
- 在
created
生命周期中进行DOM操作
Vue 实例挂载过程中发生了什么?
详情
挂载过程指的是 app.mount()
过程,这是一个初始化过程,整体上做了两件事情:初始化和建立更新机制。
初始化会创建组件实例、初始化组件状态、创建各种响应式数据。
建立更新机制这一步会立即执行一次组件的更新函数,这会首次执行组件渲染函数并执行 patch 将 vnode 转换为 dom; 同时首次执行渲染函数会创建它内部响应式数据和组件更新函数之间的依赖关系,这使得以后数据发生变化时会执行对应的更新函数。
Vue 的模版编译原理?
详情
Vue 中有个独特的编译器模块,称为 compiler
,它的主要作用是将用户编写的 template
编译为js中可执行的 render
函数。
在 Vue 中,编译器会先对 template
进行解析,这一步称为 parse
,结束之后得到一个JS对象,称之为抽象语法树 AST
;然后是对AST进行深加工的转换过程,这一步称为 transform
,最后将前面得到的 AST
生成JS代码,也就是 render
函数。
Vue 的响应式原理?
详情
Vue2.0的响应式原理
Vue 2 中的数据响应式会根据数据类型做不同的处理。如果是对象,则通过 Object.defineProperty(obj,key,descriptor)
拦截对象属性访问,当数据被访问或改变时,感知并作出反应;如果是数组,则通过覆盖数组原型的方法,扩展它的7个变更方法(push、pop、shift、unshift、splice、sort、reverse),使这些方法可以额外的做更新通知,从而做出响应。
缺点:
- 初始化时的递归遍历会造成性能损失;
- 通知更新过程需要维护大量 dep 实例和 watcher 实例,额外占用内存较多;
- 新增或删除对象属性无法拦截,需要通过 Vue.set 及 delete 这样的 API 才能生效;
- 对于ES6中新产生的Map、Set这些数据结构不支持。
Vue3.0的响应式原理
Vue 3 中利用 ES6 的 Proxy
机制代理需要响应化的数据。可以同时支持对象和数组,动态属性增、删都可以拦截,新增数据结构均支持,对象嵌套属性运行时递归,用到时才代理,也不需要维护特别多的依赖关系,性能取得很大进步。
虚拟DOM?
详情
概念:
虚拟DOM,顾名思义就是虚拟的DOM对象,它本身就是一个JS对象,只不过是通过不同的属性去描述一个视图结构。
虚拟DOM的好处:
(1) 性能提升 直接操作DOM是有限制的,一个真实元素上有很多属性,如果直接对其进行操作,同时会对很多额外的属性内容进行了操作,这是没有必要的。如果将这些操作转移到JS对象上,就会简单很多。另外,操作DOM的代价是比较昂贵的,频繁的操作DOM容易引起页面的重绘和回流。如果通过抽象VNode进行中间处理,可以有效减少直接操作DOM次数,从而减少页面的重绘和回流。
(2) 方便跨平台实现 同一VNode节点可以渲染成不同平台上对应的内容,比如:渲染在浏览器是DOM元素节点,渲染在Native(iOS、Android)变为对应的控件。Vue 3 中允许开发者基于VNode实现自定义渲染器(renderer),以便于针对不同平台进行渲染。
结构:
没有统一的标准,一般包括tag
、props
、children
三项。
tag
:必选。就是标签,也可以是组件,或者函数。props
:非必选。就是这个标签上的属性和方法。children
:非必选。就是这个标签的内容或者子节点。如果是文本节点就是字符串;如果有子节点就是数组。换句话说,如果判断 children 是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素。
diff 算法?
详情
在 Vue 中,Diff 算法是虚拟 DOM(Virtual DOM)的核心部分,它用于比较新旧虚拟 DOM 树的差异,从而找出需要更新的最小 DOM 操作集合,以提高性能。Diff 算法的主要目的是尽可能减少直接操作真实 DOM 的次数,因为直接操作真实 DOM 的代价比较高,会引起页面的重绘和回流。
Vue 2.x 的 Diff 算法
Vue 2.x 使用的是双指针比较算法,主要步骤如下:
- 同层比较:只比较同一层级的节点,不会跨层级比较。
- 节点比较:
- key 比较:如果节点有 key,会根据 key 来判断节点是否相同。
- 标签名比较:如果 key 相同,会比较标签名是否相同。
- 属性比较:如果标签名相同,会比较节点的属性是否相同。
- 文本内容比较:如果属性相同,会比较节点的文本内容是否相同。
- 列表比较:对于列表节点,会使用双指针法进行比较,尽量复用已有的节点。
Vue 3.x 的 Diff 算法
Vue 3.x 在 Diff 算法上进行了优化,引入了静态标记(Static Flag)和最长递增子序列(Longest Increasing Subsequence)算法,主要步骤如下:
- 静态标记:在编译阶段,会对模板中的静态节点和动态节点进行标记,这样在 Diff 过程中可以跳过静态节点的比较,提高性能。
- 最长递增子序列:对于列表节点,会使用最长递增子序列算法来找出需要移动的最小节点集合,减少节点的移动次数。
Vue中key的作用?
详情
key的作用主要是为了更加高效的更新虚拟 DOM。
Vue 判断两个节点是否相同时,主要是判断两者的key和元素类型tag。因此,如果不设置key,它的值就是 undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,将造成大量的 DOM 更新操作。
为什么组件中的 data 是一个函数?
详情
在 new Vue() 中,可以是函数也可以是对象,因为根实例只有一个,不会产生数据污染。
在组件中,data 必须为函数,目的是为了防止多个组件实例对象之间共用一个 data,产生数据污染;而采用函数的形式,initData 时会将其作为工厂函数都会返回全新的 data 对象。
Vue 中组件间的通信方式?
详情
父子组件通信:
父向子传递数据是通过props,子向父是通过 $emit
触发事件;通过父链/子链也可以通信($parent/$children
);ref也可以访问组件实例;provide/inject
;$attrs/$listeners
。
兄弟组件通信:
全局事件总线EventBus
、Vuex
。
跨层级组件通信:
全局事件总线EventBus
、Vuex
、provide/inject
。
v-show 和 v-if 的区别?
详情
- 控制手段不同。
v-show
是通过给元素添加 css 属性display: none
,但元素仍然存在;而v-if
控制元素显示或隐藏是将元素整个添加或删除。
- 控制手段不同。
- 编译过程不同。
v-if
切换有一个局部编译/卸载的过程,切换过程中合适的销毁和重建内部的事件监听和子组件;v-show
只是简单的基于 css 切换。
- 编译过程不同。
- 编译条件不同。
v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建,渲染条件为假时,并不做操作,直到为真才渲染。
- 编译条件不同。
- 触发生命周期不同。
v-show
由 false 变为 true 的时候不会触发组件的生命周期;v-if
由 false 变为 true 的时候,触发组件的beforeCreate
、created
、beforeMount
、mounted
钩子,由 true 变为 false 的时候触发组件的beforeDestory
、destoryed
钩子。
- 触发生命周期不同。
- 性能消耗不同。v-if有更高的切换消耗;v-show有更高的初始渲染消耗。
使用场景:
如果需要非常频繁地切换,则使用 v-show
较好,如:手风琴菜单,tab 页签等; 如果在运行时条件很少改变,则使用 v-if
较好,如:用户登录之后,根据权限不同来显示不同的内容。
computed 和 watch 的区别?
详情
computed
计算属性,依赖其它属性计算值,内部任一依赖项的变化都会重新执行该函数,计算属性有缓存,多次重复使用计算属性时会从缓存中获取返回值,计算属性必须要有return
关键词。watch
侦听到某一数据的变化从而触发函数。当数据为对象类型时,对象中的属性值变化时需要使用深度侦听deep
属性,也可在页面第一次加载时使用立即侦听immdiate
属性。
运用场景:
计算属性一般用在模板渲染中,某个值是依赖其它响应对象甚至是计算属性而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
v-if 和 v-for 为什么不建议放在一起使用?
详情
- Vue 2 中,
v-for
的优先级比v-if
高,这意味着v-if
将分别重复运行于每一个v-for
循环中。如果要遍历的数组很大,而真正要展示的数据很少时,将造成很大的性能浪费。 - Vue 3 中,则完全相反,
v-if
的优先级高于v-for
,所以v-if
执行时,它调用的变量还不存在,会导致异常。
通常有两种情况导致要这样做:
- 为了过滤列表中的项目,比如:
v-for = "user in users" v-if = "user.active"
。这种情况,可以定义一个计算属性,让其返回过滤后的列表即可。 - 为了避免渲染本该被隐藏的列表,比如
v-for = "user in users" v-if = "showUsersFlag"
。这种情况,可以将v-if移至容器元素上或在外面包一层template
即可。
Vue 2 中的 Vue.set 方法?
详情
Vue.set
是 Vue 2 中的一个全局API。可手动添加响应式数据,解决数据变化视图未更新问题。当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,会发现页面并没有更新。这是因为 Object.defineProperty()
的限制,监听不到数据变化,可通过 this.$set
(数组或对象,数组下标或对象的属性名,更新后的值)解决。
keep-alive 是什么?
详情
作用
实现组件缓存,保持组件的状态,避免反复渲染导致的性能问题。
工作原理
Vue.js 内部将 DOM 节点,抽象成了一个个的 VNode 节点,keep-alive组件的缓存也是基于 VNode 节点的。它将满足条件的组件在 cache 对象中缓存起来,重新渲染的时候再将 VNode 节点从 cache 对象中取出并渲染。
可以设置以下属性:
- include:字符串或正则,只有名称匹配的组件会被缓存。
- exclude:字符串或正则,任何名称匹配的组件都不会被缓存。
- max:数字,最多可以缓存多少组件实例。
匹配首先检查组件的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称(父组件 components选项的键值),匿名组件不能被匹配。
如果同时使用了 include
、exclude
,那么 exclude
的优先级高于 include
。
生命周期
设置了keep-alive缓存的组件,会多出两个生命周期钩子:activated
、deactivated
。 首次进入组件时:beforeCreate
--> created
--> beforeMount
--> mounted
--> activated
--> beforeUpdate
--> updated
--> deactivated
再次进入组件时:activated
--> beforeUpdate
--> updated
--> deactivated
Vue2.0中的 mixin 和 Vue3.0中的 (组合式函数)hooks 有什么区别?
详情
1. 语法和使用方式
- Vue2.0中的 mixin mixin 是一个包含组件选项的对象,可以将其混入到多个组件中。在组件中使用 mixins 选项引入 mixin。
- Vue 3.0 的组合式函数(hooks) 组合式函数是一个普通的 JavaScript 函数,通常以 use 开头,返回需要复用的响应式状态和方法。在组件的 setup 函数中调用组合式函数。
2. 命名冲突和代码可读性
- Vue 2.0 的 mixin 当多个 mixin 或 mixin 与组件本身存在相同的选项(如 data、methods)时,会发生命名冲突。冲突的解决规则不直观,可能导致代码难以理解和维护。
- 组合式函数返回的变量和方法可以明确命名,不会出现命名冲突的问题。代码的可读性和可维护性更高。
3. 逻辑复用和代码组织
- Vue 2.0 的 mixin mixin 中的逻辑是分散在各个选项(如 data、methods、created)中的,当 mixin 较多时,很难清晰地知道某个逻辑来自哪个 mixin,不利于代码的组织和复用。
- Vue 3.0 的组合式函数(hooks) 组合式函数将相关的逻辑封装在一起,形成一个独立的功能模块。可以根据需要在不同的组件中复用这些模块,代码的复用性和可维护性更好。
4. 生命周期钩子的使用
- Vue 2.0 的 mixin mixin 中的生命周期钩子会与组件的生命周期钩子合并执行,可能会导致代码逻辑分散,难以追踪。
- Vue 3.0 的组合式函数(hooks) 组合式函数中可以直接使用 Vue 3 的生命周期钩子,逻辑更加清晰。
插槽 slot ?
详情
slot插槽,一般在组件内部使用,封装组件时,在组件内部不确定该位置是以何种形式的元素展示时,可以通过slot占据这个位置,该位置的元素需要父组件以内容形式传递过来。slot分为:
默认插槽:子组件用
<slot>
标签来确定渲染的位置,标签里面可以放DOM结构作为后备内容,当父组件在使用的时候,可以直接在子组件的标签内写入内容,该部分内容将插入子组件的<slot>
标签位置。如果父组件使用的时候没有往插槽传入内容,后备内容就会显示在页面。具名插槽:子组件用 name 属性来表示插槽的名字,没有指定 name 的插槽,会有隐含的名称叫做
default
。父组件中在使用时在默认插槽的基础上通过v-slot指令指定元素需要放在哪个插槽中,v-slot
值为子组件插槽name属性值。使用v-slot指令指定元素放在哪个插槽中,必须配合<template>
元素,且一个<template>
元素只能对应一个预留的插槽,即不能多个<template>
元素都使用v-slot
指令指定相同的插槽。v-slot
的简写是#
,例如v-slot:header
可以简写为#header
。作用域插槽:子组件在
<slot>
标签上绑定props
数据,以将子组件数据传给父组件使用。父组件获取插槽绑定props
数据的方法:- scope="接收的变量名":
<template scope="接收的变量名">
- slot-scope="接收的变量名":
<template slot-scope="接收的变量名">
- v-slot:插槽名="接收的变量名":
<template v-slot:插槽名="接收的变量名">
- scope="接收的变量名":
Vue 中的修饰符有哪些?
详情
在Vue 中,修饰符处理了许多 DOM 事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事情,而能有更多的精力专注于程序的逻辑处理。Vue中修饰符分为以下几种:
表单修饰符
lazy
: 填完信息,光标离开标签的时候,才会将值赋予给value
,也就是在change
事件之后再进行信息同步。number
: 自动将用户输入值转化为数值类型,但如果这个值无法被parseFloat
解析,则会返回原来的值。trim
: 自动过滤用户输入的首尾空格,而中间的空格不会被过滤。
事件修饰符
stop
: 阻止了事件冒泡,相当于调用了event.stopPropagation
方法。prevent
: 阻止了事件的默认行为,相当于调用了event.preventDefault
方法。self
: 只当在 event.target 是当前元素自身时触发处理函数。once
: 绑定了事件以后只能触发一次,第二次就不会触发。capture
: 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理。passive
: 告诉浏览器你不想阻止事件的默认行为。native
: 让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用v-on
只会监听自定义事件。
鼠标按键修饰符
left
: 左键点击。right
: 右键点击。middle
: 中键点击。
键值修饰符
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的,有如下: keyCode存在很多,但vue为我们提供了别名,分为以下两种:
- 普通键(enter、tab、delete、space、esc、up...)
- 系统修饰键(ctrl、alt、meta、shift...)
对 SPA 的理解?
详情
概念:
SPA(Single-page application),即单页面应用,它是一种网络应用程序或网站的模型,通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换时打断用户体验。在SPA中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是响应用户操作)动态装载适当的资源并添加到页面。页面在任何时间点都不会重新加载,也不会将控制转移到其他页面。举个例子,就像一个杯子,上午装的是牛奶,中午装的是咖啡,下午装的是茶,变得始终是内容,杯子始终不变。
SPA与MPA的区别:
MPA(Muti-page application),即多页面应用。在MPA中,每个页面都是一个主页面,都是独立的,每当访问一个页面时,都需要重新加载 Html、CSS、JS 文件,公共文件则根据需求按需加载。
功能区别 | SPA | MPA |
---|---|---|
组成 | 一个主页面和多个页面片段 | 多个主页面 |
url模式 | hash模式 | history模式 |
SEO搜索引擎优化 | 难实现,可使用SSR方式改善 | 容易实现 |
数据传递 | 容易 | 通过url、cookie、localStorage等传递 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
双向绑定?
详情
在 Vue 中,双向数据绑定是指数据的变化会自动更新到视图,而视图的变化也会自动更新到数据。这大大简化了开发者处理数据和视图同步的工作。下面我将为你详细解释双向绑定的原理、实现方式和使用场景。
原理:
Vue 的双向绑定主要基于数据劫持结合发布者-订阅者模式实现。具体来说,在 Vue 2 中使用 Object.defineProperty()
方法来劫持数据的 getter
和 setter
,当数据发生变化时,会触发 setter
方法,通知所有订阅者更新视图;在 Vue 3 中则使用了 ES6 的 Proxy
对象来实现数据劫持,相比 Object.defineProperty()
更加灵活和强大。
子组件是否可以直接改变父组件的数据?
详情
在 Vue 中,子组件不建议直接改变父组件的数据,主要原因在于这违背了单向数据流的原则,单向数据流使得数据的流动方向清晰,便于维护和调试。若子组件直接修改父组件的数据,会让数据流向变得混乱,难以追踪数据的变化。不过,我们可以通过以下几种方式实现子组件对父组件数据的修改:
1. 通过 $emit
触发自定义事件
子组件可以通过 $emit
触发一个自定义事件,父组件监听这个事件并更新自身的数据。
2. 使用 v-model
指令
v-model
是一个语法糖,它结合了 :value
和 @input
事件,可以更方便地实现双向数据绑定。
3. 使用 provide
和 inject
provide
和 inject
可以实现跨级组件之间的数据传递,但通常不建议用于修改数据,因为这会让数据流向变得不清晰。不过,在某些特定场景下也可以使用。
router 和 route 的区别?
详情
router
router
指的是路由实例,它是 Vue Router 的核心对象,负责管理整个应用的路由配置、导航守卫、路由跳转等功能。通常在创建 Vue Router 实例时会得到这个对象。
主要功能:
- 路由导航:通过
router.push
、router.replace
等方法进行页面跳转。 - 全局导航守卫:使用
router.beforeEach
、router.afterEach
等方法设置全局的导航守卫。
route
route
表示当前激活的路由信息对象,它包含了当前路由的路径、参数、查询参数等信息。可以在组件中通过 this.$route
(Vue 2)或 useRoute()
(Vue 3)来访问。
主要属性:
- path:当前路由的路径。
- params:路由参数,例如 /user/:id 中的 id。
- query:查询参数,例如 /search?q=vue 中的 q。
- hash:当前路由的哈希值。
vue-router 的路由传参方式?
详情
1. 声明式导航 router-link:
<router-link :to="'/users?userId:1'"></router-link>
<router-link :to="{ name: 'users', params: { userId: 1 } }"></router-link>
<router-link :to="{ path: '/users', query: { userId: 1 } }"></router-link>
2. 编程式导航 router.push
:
- 通过
params
传参: - 通过
query
传参: - 动态路由
Vue Router中的常用路由模式和原理?
详情
Vue Router 有两种常用路由模式:hash
模式和 history
模式,下面分别介绍它们的原理和特点。
1. hash 模式:
原理:location.hash
的值是 URL 中 #
后面的内容。改变 hash
不会触发页面的重新加载,也不会包含在 HTTP 请求中,对后端无影响。可通过 window.addEventListener("hashchange", funcRef, false)
监听 hash
变化,利用此特性实现前端路由更新视图但不重新请求页面。
特点:兼容性好,但 URL 中有 #
不够美观。
2. history 模式
原理:利用 HTML5 的 History API 来实现。history.pushState()
和 history.replaceState()
可以改变浏览器的历史记录而不触发页面刷新。当用户点击后退或前进按钮时,会触发 popstate
事件,通过监听该事件可以实现路由切换。
特点:URL 更美观,但需要后端支持,因为当用户直接访问某个路由时,后端需要返回正确的 HTML 文件。
动态路由?
详情
动态路由允许在定义路由时使用参数,使得同一路由可以匹配不同的路径,常用于处理具有相似结构但不同数据的页面,比如用户详情页、文章详情页等。
定义动态路由
在 Vue Router 中,可在路由配置里使用冒号 :
来定义动态路由参数。
获取动态参数
在组件里可以通过 $route.params
(Vue 2)或者 useRoute().params
(Vue 3)获取动态路由参数。
<template>
<div>
<h1>用户 ID: {{ userId }}</h1>
</div>
</template>
<script>
export default {
name: 'User',
computed: {
userId() {
return this.$route.params.id; // Vue 2
// 在 Vue 3 中使用组合式 API
// import { useRoute } from 'vue-router';
// const route = useRoute();
// return route.params.id;
}
}
};
</script>
动态路由匹配多个段
还能在路由配置里使用 *
来匹配多个路径段。
const routes = [
{
path: '/user/:id/*', // 匹配 /user/123/abc/def 等路径
name: 'User',
component: User
}
];
对Vuex的理解?
详情
概念
Vuex 是 Vue 专用的状态管理库,它以全局方式集中管理应用的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
解决的问题
Vuex 主要解决的问题是多组件之间状态共享。利用各种通信方式,虽然也能够实现状态共享,但是往往需要在多个组件之间保持状态的一致性,这种模式很容易出问题,也会使程序逻辑变得复杂。Vuex 通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,响应式的数据也能够保证简洁的单向流动,使代码变得更具结构化且易于维护。
什么时候用
Vuex 并非是必须的,它能够管理状态,但同时也带来更多的概念和框架。如果我们不打算开发大型单页应用或应用里没有大量全局的状态需要维护,完全没有使用Vuex的必要,一个简单的 store
模式就够了。反之,Vuex将是自然而然的选择。
用法:
Vuex 将全局状态放入state对象中,它本身是一颗状态树,组件中使用store实例的state访问这些状态;然后用配套的 mutation
方法修改这些状态,并且只能用 mutation
修改状态,在组件中调用commit
方法提交mutation
;如果应用中有异步操作或复杂逻辑组合,需要编写action
,执行结束如果有状态修改仍需提交mutation
,组件中通过dispatch
派发action
。最后是模块化,通过modules
选项组织拆分出去的各个子模块,在访问状态(state
)时需注意添加子模块的名称,如果子模块有设置namespace
,那么提交mutation
和派发action
时还需要额外的命名空间前缀。
页面刷新后Vuex 状态丢失怎么解决?
详情
Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就需要保存起来。
localStorage
就很合适,提交 mutation
的时候同时存入 localStorage
,在 store
中把值取出来作为 state
的初始值即可。
也可以使用第三方插件,推荐使用 vuex-persist
插件,它是为 Vuex 持久化储存而生的一个插件,不需要你手动存取 storage
,而是直接将状态保存至 cookie
或者 localStorage
中。
Vuex 和 Pinia 的区别?
详情
1. 设计理念
- Vuex:遵循严格的单向数据流设计,状态修改需通过
mutations
(同步)和actions
(异步),代码结构严谨但复杂。 - Pinia:更灵活,去除了
mutations
概念,可直接在actions
中同步或异步修改状态,简化开发流程。
2.类型支持
- Vuex:对 TypeScript 支持不够友好,需额外配置和类型定义。
- Pinia:原生支持 TypeScript,定义状态、actions 和 getters 时能自动推导类型,利于大型项目开发。
3. 模块化
- Vuex:使用
modules
实现模块化,需处理命名空间等复杂问题。 - Pinia:模块定义更简单自然,每个
defineStore
生成独立模块,自动管理命名。
4. 性能
- Pinia:内部实现更高效,响应式系统优化好,在大型应用中性能表现可能更佳。
关于 Vue SSR 的理解?
详情
SSR 即服务端渲染(Server Side Render),就是将 Vue 在客户端把标签渲染成 html 的工作放在服务端完成,然后再把 html 直接返回给客户端。
优点:
有着更好的 SEO,并且首屏加载速度更快。
缺点:
开发条件会受限制,服务器端渲染只支持 beforeCreate
和 created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。服务器会有更大的负载需求。
了解哪些 Vue 的性能优化方法?
详情
- 路由懒加载。有效拆分应用大小,访问时才异步加载。
keep-alive
缓存页面。避免重复创建组件实例,且能保留缓存组件状态。v-for
遍历避免同时使用v-if
。实际上在 Vue 3 中已经是一个错误用法了。- 长列表性能优化,可采用虚拟列表。
v-once
。不再变化的数据使用v-once
。- 事件销毁。组件销毁后把全局变量和定时器销毁。
- 图片懒加载。
- 第三方插件按需引入。
- 子组件分割。较重的状态组件适合拆分。
- 服务端渲染。
Vue 组件的 data 为什么必须是函数
详情
在 Vue 中,组件的 data 必须是一个函数,而不是一个对象,主要是为了确保每个组件实例都有自己独立的数据副本。以下是详细解释:
1. 组件复用的问题
Vue 组件是可以复用的,如果 data 是一个对象,那么所有复用的组件实例都会共享同一个数据对象。这意味着一个组件实例对数据的修改会影响到其他所有实例,这通常不是我们想要的结果。
2. 函数返回独立数据
当 data 是一个函数时,每个组件实例都会调用这个函数,返回一个新的数据对象。这样,每个组件实例就有了自己独立的数据副本,它们之间的数据修改不会相互影响。
Vue 中的 spa 应用如何优化首屏加载速度?
详情
#### 1. 路由懒加载
将路由组件分割成多个小的代码块,只有在访问对应路由时才加载相应的代码,减少初始加载的文件大小。
const routes = [
{
path: '/about',
name: 'About',
// 懒加载组件
component: () => import('@/views/About.vue')
}
]
2. 代码分割
除了路由懒加载,还可以手动进行代码分割,将一些不常用的代码分离出去,按需加载。
3. 压缩代码
使用工具(如 UglifyJS、Terser)压缩 JavaScript、CSS 代码,减少文件体积。
4. 图片优化
使用合适的图片格式(如 WebP),压缩图片大小,采用图片懒加载技术,只在图片进入可视区域时加载。
5. 开启 Gzip 压缩
在服务器端开启 Gzip 压缩,减少传输的数据量。
6. 使用 CDN
将静态资源(如 JavaScript、CSS、图片等)托管到 CDN 上,利用 CDN 的全球节点加速资源加载。
7. 缓存
使用浏览器缓存机制,合理设置缓存策略,避免重复加载相同的资源。
8. 预加载和预渲染
使用 <link rel="preload">
预加载关键资源,使用 <link rel="prerender">
预渲染页面,提高页面加载速度。
9. 服务端渲染(SSR)
将 Vue 应用在服务器端进行渲染,直接返回 HTML 给客户端,减少客户端的渲染时间。
组件中写 name 选项有哪些好处
详情
在 Vue 组件中定义 name 选项的主要作用是为组件指定一个名字,这个名字在调试、递归组件、全局注册和基础组件复用时会非常有用。具体来看,name 选项会:
- 1)帮助在 Vue DevTools 中识别组件,增强调试体验。
- 2)在递归组件调用中,确保 Vue 能够正确引用自身。
- 3)用于全局组件注册,使得组件能够被全局识别和使用。
- 4)提高在
<keep-alive>
中使用时的可读性和可调试性。
说一下 ref 的作用是什么?
详情
标签属性
在 Vue 中,ref
是一个特殊的属性,用于给元素或子组件注册引用信息。以下是 ref
的主要作用:
- 获取 DOM 元素 通过
ref
可以直接访问 DOM 元素,从而对其进行操作,比如获取元素的尺寸、绑定事件等。
- 获取 DOM 元素 通过
- 访问子组件实例 使用
ref
可以访问子组件的实例,进而调用子组件的方法或访问其数据。
- 访问子组件实例 使用
3. 在组合式 API 中使用
在 Vue 3 的组合式 API 中,可以使用 ref
函数创建响应式引用。
ref 和 reactive 有什么区别?
详情
在 Vue 3 里,ref 和 reactive 都用于创建响应式数据,但存在明显区别。
1. 定义方式
ref
:借助ref
函数定义,能处理基本数据类型(像number
、string
、boolean
等)和对象。使用时要通过.value
访问和修改值。reactive
:利用reactive
函数定义,只能处理对象类型(包含数组、普通对象等)。可直接访问和修改属性。
2. 响应式原理
ref
:把传入的值包裹成一个对象,对象有 value 属性,通过Object.defineProperty
或Proxy
实现响应式。reactive
:基于Proxy
代理对象,拦截对象属性的访问和修改操作,以此实现响应式。
3. 使用场景
ref
:适合处理基本数据类型的响应式数据,在模板中使用时可省略.value
;也能用于获取 DOM 元素引用。reactive
:适合处理复杂对象的响应式数据,无需.value
访问属性。
Vue 计算属性的函数名和 data 中的属性可以同名吗?为什么?
详情
在 Vue 里,计算属性的函数名和 data
中的属性不可以同名,原因如下:
- 命名冲突:Vue 实例在初始化时,会把
data
里的属性和计算属性都添加到实例上。若二者同名,会造成命名冲突,导致程序无法正确区分使用的是哪个属性。 - 覆盖问题:要是计算属性和
data
里的属性同名,计算属性会覆盖data
里的属性。这意味着你没办法再访问data
里的原始属性,会产生预期之外的结果。
Vue 2.0 支持哪个版本以上的 IE 浏览器?
详情
Vue 2.0 官方支持到 IE9 及以上版本的 Internet Explorer 浏览器。不过要注意,在 IE9 里,Vue 2.0 不支持 Object.defineProperty
方法,所以它使用的是旧的 Object.observe
方法来实现响应式系统。并且,Vue 2.0 的一些新特性(像 v-bind 和 v-on 的缩写语法)在 IE9 中是不被支持的。
在 Vue 渲染模板时,如何保留模板中的 HTML 注释?
详情
在 Vue 渲染模板时,默认情况下,HTML 注释不会被保留。但你可以通过在 Vue 组件中使用特殊的指令 v-html 来实现保留注释的需求。v-html 是 Vue 提供的一个特殊指令,可以用于直接插入 HTML 内容,包含了 HTML 注释。
具体操作步骤如下: -1)创建一个 Vue 组件。
- 2)使用 v-html 指令,绑定一个包含 HTML 注释的字符串。
Vue 的 template 标签有什么用?
详情
在 Vue 里,<template>
标签有多种用途,以下为你详细介绍:
1. 作为模板容器
在单文件组件(.vue 文件)中,<template>
标签用于包裹组件的 HTML 结构,是组件模板的载体。
2. 条件渲染和循环渲染
<template>
标签可配合 v-if
、v-for
等指令进行条件渲染或循环渲染,且不会在最终渲染结果中显示。
3. 插槽内容
在组件中,<template>
可用于定义插槽内容,通过 v-slot
指令来使用。
Vue 中 MVVM、MVC 和 MVP 模式的区别是什么?
详情
MVVM(Model-View-ViewModel)、MVC(Model-View-Controller)和 MVP(Model-View-Presenter)是三种不同的软件架构模式,虽然它们的目标都是实现一种分离关注点的结构,但它们之间存在显著的差异。
1)MVC 模式:
- Model:数据层,负责与数据库或远程服务器交互,存取和操作数据。
- View:视图层,负责用户界面的呈现。它不包含任何业务逻辑,仅显示从 Model 获得的数据。
- Controller:控制器层,协调 Model 和 View,处理用户输入并更新 Model 和 View。
2)MVP 模式:
- Model:与 MVC 中相同,负责数据的管理。
- View:与 MVC 中相同,负责显示内容。
- Presenter:充当中介者,从 Model 获取数据并更新 View,而且能够处理复杂的逻辑,减轻 View 的负担。
3)MVVM 模式:
- Model:与 MVC 和 MVP 中相同,负责数据的管理。
- View:与 MVC 和 MVP 中相同,负责显示内容。
- ViewModel:负责将 Model 和 View 连接起来。通过数据绑定,View 自动更新以响应一切 Model 的变化,从而显著简化了代码量。
在 Vue 框架中,采用的是 MVVM 模式:
- Model:数据状态(在 Vue 中通过 data 属性定义)。
- View:模板(在 Vue 中通过 HTML、模板语法和 插值)。
- ViewModel:Vue 实例,它连接了 Model 和 View,通过双向数据绑定(Vue 的核心功能之一)使得 View 会自动更新以响应 Model 的变化。
总结
- MVC 更适用于传统的服务器渲染应用。
- MVP 适用于 Web 应用和桌面应用,但逻辑较为复杂。
- MVVM 非常适合前端框架如 Vue、React、Angular,能够更好地处理 UI 绑定和状态管理。
Vue Router 如何配置 404 页面?
详情
在 Vue 项目中,如果你想配置一个 404 页面(即找不到页面提示),你需要通过 Vue Router 来设置。这通常通过将路由配置中的 *
(通配符)指向一个 404 组件来实现。
你了解 Vue 中的过滤器吗?它有哪些应用场景?
详情
在 Vue 里,过滤器是一种特殊的函数,其作用是对数据进行格式化和转换。在插值表达式和 v-bind 指令里,能借助管道符 | 来调用过滤器。以下是详细介绍:
过滤器的定义与使用
在 Vue 2 中,既可以在组件选项里定义局部过滤器,也能在全局定义过滤器。下面是示例代码:
<template>
<div>
<!-- 使用局部过滤器 -->
<p>{{ message | capitalize }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello world'
};
},
filters: {
// 局部过滤器
capitalize(value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
};
</script>
全局过滤器的定义
import Vue from 'vue';
// 全局过滤器
Vue.filter('currency', function (value) {
if (typeof value !== 'number') {
return value;
}
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
return formatter.format(value);
});
应用场景
- 文本格式化:能够把字符串转换为大写、小写或者首字母大写等格式。
- 日期格式化:将日期对象转换为特定格式的字符串,像 YYYY-MM-DD 这样的格式。
- 货币格式化:把数字转换为货币格式,例如 $1,000.00。
- 数据筛选和转换:对数组或者对象进行筛选和转换,比如只显示数组里的偶数。
注意事项
在 Vue 3 中,过滤器已被移除,推荐使用计算属性或者方法来替代过滤器的功能。
Vue Router 中如何获取路由传递过来的参数?
详情
在 Vue Router 里,路由传参有多种方式,获取参数的方法也因传参方式而异。下面为你详细介绍不同传参方式下获取参数的方法。
1. 通过 params 传参
- 传参方式:在路由配置里定义动态路由参数,或者在路由跳转时使用 params 对象传参。
- 获取方式:在组件中借助 $route.params(Vue 2)或者 useRoute().params(Vue 3)来获取。
示例代码:
<!-- 路由配置 -->
const routes = [
{
path: '/user/:id',
name: 'User',
component: User
}
];
<!-- 路由跳转 -->
<router-link :to="{ name: 'User', params: { id: 1 } }">Go to User</router-link>
<!-- 组件中获取参数(Vue 2) -->
export default {
computed: {
userId() {
return this.$route.params.id;
}
}
};
<!-- 组件中获取参数(Vue 3) -->
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
const userId = route.params.id;
return {
userId
};
}
};
2. 通过 query 传参
- 传参方式:在路由跳转时运用 query 对象传参。
- 获取方式:在组件中使用 $route.query(Vue 2)或者 useRoute().query(Vue 3)来获取。
示例代码:
<!-- 路由跳转 -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">Search</router-link>
<!-- 组件中获取参数(Vue 2) -->
export default {
computed: {
searchQuery() {
return this.$route.query.q;
}
}
};
<!-- 组件中获取参数(Vue 3) -->
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
const searchQuery = route.query.q;
return {
searchQuery
};
}
};
3. 动态路由匹配多个段
- 传参方式:在路由配置中使用 * 来匹配多个路径段。
- 获取方式:在组件中使用 $route.params(Vue 2)或者 useRoute().params(Vue 3)来获取。
示例代码:
<!-- 路由配置 -->
const routes = [
{
path: '/user/:id/*',
name: 'User',
component: User
}
];
<!-- 路由跳转 -->
<router-link to="/user/123/abc/def">Go to User</router-link>
<!-- 组件中获取参数(Vue 2) -->
export default {
computed: {
userId() {
return this.$route.params.id;
},
remainingPath() {
return this.$route.params[0];
}
}
};
<!-- 组件中获取参数(Vue 3) -->
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
const userId = route.params.id;
const remainingPath = route.params[0];
return {
userId,
remainingPath
};
}
};
综上所述,依据不同的传参方式,你可以在组件中使用 $route.params
、$route.query
或者 useRoute().params
、useRoute().query
来获取路由传递过来的参数。
Vue 的 v-cloak 和 v-pre 指令有什么作用?
详情
在 Vue 中,v-cloak
和 v-pre
是两个实用的指令,它们分别具有不同的作用:
1. v-cloak 指令
v-cloak 指令主要用于解决 Vue 实例在初始化时,页面闪烁的问题。在 Vue 实例还未完全挂载之前,模板中的插值表达式(如 )会直接显示在页面上,直到 Vue 实例完成挂载并替换为实际的数据。这会导致用户在短时间内看到未编译的模板,影响用户体验。v-cloak
指令可以隐藏这些未编译的模板,直到 Vue 实例完成挂载。
使用方法: 在 CSS 中为 [v-cloak] 选择器添加 display: none
样式,然后在需要隐藏的元素上添加 v-cloak 指令。
2. v-pre 指令
v-pre 指令用于跳过元素及其子元素的编译过程,直接显示原始的 HTML 内容。这在需要显示 Vue 语法(如插值表达式、指令等)而不希望它们被编译的情况下非常有用。使用 v-pre 指令可以提高性能,因为 Vue 会跳过对这些元素的编译。
使用方法: 在需要跳过编译的元素上添加 v-pre 指令。
在 Vue 组件中如何访问根实例?
详情
1. 使用 this.$root
在 Vue 2 里,this.$root
可用于访问根实例。在组件的方法或者生命周期钩子中,你能够借助 this.$root
来访问根实例的属性和方法。
2. 在 Vue 3 组合式 API 中使用 getCurrentInstance
在 Vue 3 的组合式 API 中,要先借助 getCurrentInstance
获取当前实例,再通过 appContext.config.globalProperties
访问根实例。
在 Vue 中使用 this 时应该注意哪些问题?
详情
在 Vue 中使用 this 主要涉及以下几点需要注意的地方:
- 1)this 的上下文绑定问题,特别是在使用箭头函数和普通函数时,this 的指向会有不同。
- 普通函数:this 指向调用该函数的对象,在 Vue 组件里,通常指向 Vue 实例。
- 箭头函数:this 指向定义该箭头函数时的上下文,而非调用时的上下文。因此在 Vue 组件里使用箭头函数时,this 不会指向 Vue 实例。
- 2)在生命周期钩子函数中,this 的指向是 Vue 实例。
- 3)在模板内的 this 是隐式的,我们可以直接引用数据和方法,而无需手动绑定 this。
- 4)在组件间通信中,this 的指向需要特别注意,是父组件还是子组件。
Vue Router 有什么作用?它有哪些组件?
详情
Vue Router 的作用
Vue Router 是 Vue.js 官方的路由管理器,它在 Vue 项目中扮演着至关重要的角色,主要作用如下:
- 实现单页面应用(SPA)的路由功能:在单页面应用中,用户的页面切换不会导致整个页面的刷新,而是通过路由切换来动态加载不同的组件。Vue Router 能够根据用户的操作(如点击链接),在不刷新整个页面的情况下,展示不同的组件内容,提供流畅的用户体验。
- 路由导航:支持声明式导航(如使用
<router-link>
组件)和编程式导航(如使用 router.push、router.replace 等方法),方便用户在不同的路由之间进行跳转。
- 路由导航:支持声明式导航(如使用
- 路由参数传递:可以通过路由参数(如 params 和 query)在不同的路由之间传递数据,满足不同页面之间的数据共享需求。
- 导航守卫:提供全局导航守卫、路由独享守卫和组件内守卫,用于在路由切换前进行权限验证、数据预加载等操作,确保用户访问的合法性和安全性。
- 路由懒加载:支持将路由组件分割成多个小的代码块,只有在访问对应路由时才加载相应的代码,减少初始加载的文件大小,提高应用的加载速度。
Vue Router 的组件
Vue Router 提供了一些内置组件,方便开发者在项目中使用,主要组件如下:
<router-link>
:用于创建声明式导航链接,当用户点击该链接时,会触发路由跳转。可以通过 to 属性指定目标路由。
<router-view>
:作为路由的出口,用于渲染当前匹配的路由组件。当路由发生变化时,<router-view>
会动态加载并渲染对应的组件。
<router-scroll>
:用于处理路由切换时的滚动行为,可以控制页面滚动到指定位置。
Vue 中 data 的属性可以与 methods 中的方法同名吗?为什么?
详情
在 Vue 中,data
中的属性和 methods
中的方法不能同名。原因是 Vue 的实例在初始化时会将 data
中的属性和 methods
中的方法都代理到 Vue 实例上,如果同名会导致冲突,无法区分调用的是方法还是属性。
扩展知识
在 Vue.js 中,data 对象和 methods
对象是在 Vue 实例进行初始化的过程中代理到 Vue 实例的原型上的。这意味着你可以通过 this.propertyName
来访问 data
中的属性,或者 this.methodName
来调用 methods
中的方法。
- 1)数据代理
Vue 使用了一种叫做“数据代理”的机制来实现 data
属性和 methods
方法的访问。通过这项机制,Vue 劫持了所有对属性的 getter
和 setter
操作。以下是一个简单的 Vue 初始化实例示例:
var vm = new Vue({
data: {
message: 'Hello'
},
methods: {
showMessage: function() {
console.log(this.message);
}
}
});
// 你可以这样调用
vm.message; // 'Hello'
vm.showMessage(); // 控制台输出 'Hello'
- 2)属性和方法同名冲突
当 data
和 methods
同名时,Vue 无法区分调用的是 getter
方法(访问 data
属性)还是需要调用 methods
中的方法。
var vm = new Vue({
data: {
example: 'example data'
},
methods: {
example: function() {
console.log('example method');
}
}
});
// 这种情况下,Vue 实例上的 example 会优先考虑 methods 里的方法。
console.log(vm.example); // 打印整个方法定义而不是 'example data'
- 3)调试和维护问题
为了避免上述问题,保持 data
和 methods
之间的唯一性是个好习惯。此外,好的命名习惯也有助于代码的可读性和可维护性。一般来说,我们应该尽量避免使用易混淆的名称。
- 4)命名冲突的影响
一旦发生命名冲突,Vue 实例上的该名称就会指向 methods
中的方法,从而导致无法访问或意外覆盖 data 中的数据属性,导致代码运行错误和调试困难。例如,任何对 vm.example
的访问都会返回方法的引用,而不是我们期望的数据属性。
什么是 Vue 的自定义指令?自定义指令的应用场景有哪些?
详情
Vue 的自定义指令是指在 Vue 框架中,开发者可以根据需求创建自己的自定义指令,与内置指令(如 v-model、v-show)相对应。自定义指令通常用于直接操作 DOM 元素,适合在需要对 DOM 进行底层操作的时候使用。
应用场景主要包括:
- 1)需要对某些 DOM 元素进行特定操作,例如聚焦、剪贴板操作等。
- 2)复用某些常见的 DOM 操作逻辑,以提高代码可读性和可维护性。
- 3)在第三方库或插件与 Vue 集成时,通过自定义指令来封装库的操作。
- 4)在不方便使用组件或其他逻辑时的快捷解决方案。
扩展知识
自定义指令可以通过 Vue 指令钩子函数实现,主要的钩子函数有以下几种:
- 1)bind:指令绑定到元素时调用,只会执行一次。
- 2)inserted:绑定元素插入父节点时调用。
- 3)update:所在组件的 VNode 更新时调用,不一定发生在子 VNode 更新之前。
- 4)componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- 5)unbind:指令与元素解绑时调用,只会执行一次。
自定义指令的具体定义方法如下:
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
除了基础用法,Vue3 支持的组合式 API 也非常强大。在 Vue3 中,我们可以通过 app.directive 来定义自定义指令:
const app = Vue.createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
关于自定义指令的最佳实践,一般建议: 1)尽量避免与已有的内置指令名称冲突。 2)确保指令的职责单一、清晰。 3)在使用第三方库时,合理封装,减少库与 Vue 框架的耦合。
Vue 的模板语法使用的是哪个 Web 模板引擎?介绍下该模板引擎?
详情
Vue 的模板语法使用的是基于 HTML 的模板引擎。严格来说,Vue 并没有使用一个外部的、预先存在的模板引擎,而是它自身内置了一个模板编译器来处理模板语法。
扩展知识
Vue 的模板语法借鉴了一些传统的模板引擎,但同时也引入了一些独特的特性,这让开发者可以更高效地构建用户界面。
- 1)双花括号语法:在 Vue 模板中,我们使用 双花括号来绑定数据,这被称为“Mustache”语法。与很多模板引擎类似,这种语法用来输出普通文本。
- 2)指令系统:Vue 提供了非常强大的指令系统,比如 v-bind、v-model、v-if、v-for 等,通过这些指令可以便捷地将 HTML 表达式与数据绑定。
- 3)事件处理:使用 v-on 指令或 @ 符号可以非常方便地绑定事件处理器,例如 v-on:click 或 @click。
- 4)计算属性和侦听属性:Vue 中支持通过计算属性和侦听属性来处理复杂逻辑,从而保持模板简洁。
- 5)组件系统:Vue 的模板语法非常适合用于其强大的组件系统,通过模板和组件的结合,可以轻松实现复用和抽象。
进一步扩展
尽管 Vue 有自己独特的模板编译器,但它也借鉴了其他流行的模板引擎的一些设计理念。例如:
- 1)Mustache.js:这是一个简单的逻辑少的模板引擎,它的双花括号语法与 Vue 的类似。
- 2)Handlebars.js:这是 Mustache 的进化版本,支持更多的功能,如条件判断和循环。
- 3)Jinja2(Python)和 ERB(Ruby):这些模板引擎的语法和设计理念同样深深影响了现代前端模板引擎,包括 Vue。
除此之外,我还想分享一点关于 Vue 模板编译器的优化策略。Vue 会在编译阶段将模板转化为渲染函数,这让模板渲染比传统的模板引擎更为高效。通过这种方式,Vue 能够在开发模式下提供直观的模板语法,同时在生产模式下确保卓越的性能。
Vue 2.0 的 v-html 指令不再支持使用过滤器,还有哪些处理 HTML 内容的方法?
详情
在 Vue 2.0 中,v-html 指令不再支持使用过滤器。如果需要处理 HTML 内容,我们可以采取以下几种方法:
- 1)在 v-html 绑定的数据中预先进行处理;
- 2)在组件中使用计算属性或方法处理 HTML 内容;
- 3)使用自定义指令来处理复杂的 HTML 内容。
扩展知识
- 1)在 v-html 绑定的数据中预先进行处理:
这意味着在将数据绑定到 v-html 指令前,先将数据处理成所需的格式。通常我们会在获取数据后立即进行处理,然后再将其绑定到 v-html。
- 2)在组件中使用计算属性或方法处理 HTML 内容:
使用计算属性或方法来动态处理绑定到 v-html 的数据。这种方法简洁且便于维护。
- 3)使用自定义指令来处理复杂的 HTML 内容:
如果需要更复杂的HTML内容处理,可以使用自定义指令。自定义指令允许你在 DOM 绑定或更新时执行更为复杂的操作。
有哪些定义 Vue 组件模板的方法?
详情
在 Vue 中,定义组件模板的方法主要有以下三种:
- 1)字符串模板:在 Vue 组件对象的
template
属性中直接以字符串的形式定义模板。 - 2)单文件组件(SFC):使用
.vue
文件,同时包含<template>
、<script>
、<style>
三种标签。 - 3)渲染函数(render function):在 Vue 组件的
render
方法中使用 JavaScript 函数来生成虚拟 DOM。
为什么 Vue 写组件时可以放在 .vue 文件里?可以使用其他文件后缀吗?
详情
在 Vue.js 中,.vue
文件被称为单文件组件(Single File Components, SFCs)。单文件组件是 Vue 提供的一种机制,允许开发者把 HTML、JavaScript、CSS 集成在一个文件里,从而实现更好的模块化和局部化管理。Vue 官方推荐使用 .vue
文件作为组件的文件后缀,因为它能够明确表示这是一个 Vue 组件,同时也有利于工具链和插件(如 Vue Loader)来处理这种文件。
虽然官方推荐使用 .vue
文件,但你确实可以使用其他文件后缀。Vue 解析组件的方式依赖于工具链的设置,而不是文件后缀本身。只要合适地配置,你可以使用任何你喜欢的文件后缀。但是,使用不同的文件后缀可能会让其他开发者感到困惑,并且大多数工具和插件都是针对 .vue
文件进行优化的。
扩展知识
- 1)Vue 单文件组件的优势:
模块化:通过将 HTML、CSS 和 JavaScript 集成到同一个文件里,开发者可以更加方便地管理和复用组件。 作用域CSS:Scoped CSS 可以使样式仅作用于当前组件,避免样式冲突。 开发工具支持:Vue 提供的 CLI 和第三方插件(如 Vue Loader),都能很好地支持和优化单文件组件的使用。
- 2)Vue Loader:
Vue Loader 是一个 webpack 插件,用于将 .vue 文件转换为 JavaScript 模块。它能够处理和解析 .vue 文件中的模板、脚本和样式部分。 当我们在 Vue 项目中使用 .vue 文件时,webpack 会通过 Vue Loader 读取和处理 .vue 文件,进而生成相应的模块。 如果你决定使用其他后缀文件,那么需要调整工具链配置,确保 webpack 能正确识别和处理这些文件,这可能会增加一些额外的配置复杂度。
- 3)文件后缀自定义:
虽然理论上可以使用任意文件后缀来创建 Vue 组件,但这需要在构建工具中进行适当的配置。 例如,在 webpack 配置中,你可以利用某些 loader(如 vue-loader)的扩展性来支持其他文件后缀,但这并没有太大的实际益处。
- 4)异步组件加载:
除了使用 .vue 文件,Vue 还支持异步组件加载。通过异步组件加载,可以在需要时动态引入组件,有助于提升应用的性能,尤其是在大型单页应用中。
- 5)其他前端框架组件处理方式:
类似的,React 也支持将组件定义在单独的 .jsx 或 .tsx 文件中,它们也有相应的工具链(如 Babel 和 TypeScript 编译器)来处理这些文件。 Angular 则常使用多个文件来分离模板、样式和脚本,但也可以使用 .component.ts 文件来进行管理。
Vue 中组件、插件、插槽三个概念的区别是什么?
详情
在 Vue.js 中,组件、插件和插槽是三个不同的概念,它们各自有不同的功能和使用场景。简要来说:
- 1)组件:是指 Vue 应用中的功能独立的模块,可以复用和嵌套。组件可以包含数据、模板和逻辑,形成一个完整的 UI 单位。
- 2)插件:是扩展 Vue 核心功能的工具。插件可以向 Vue 添加全局功能,比如挂载方法、指令或混入等。
- 3)插槽:是用于组件模板中的一个占位符,让开发者能够在使用组件时,将自定义内容插入到组件的预定义区域。
扩展知识
为了更好地理解这三个概念,我们可以深入一些,看看它们的具体实现和使用场景。
1)组件:
- 定义:组件是可复用的 Vue 实例,用于封装 UI 和业务逻辑。每个组件通常在单独的 .vue 文件中定义。
- 使用:可以在父组件中通过标签的方式来引入和使用子组件。
2)插件:
- 定义:插件用于扩展 Vue 的功能,使得某些功能在全局范围内可用。插件一般是一个具有 install 方法的对象。
- 使用:可以通过 Vue.use(plugin) 来安装插件。
3)插槽:
- 定义:插槽是一种将内容分发到组件内部特定位置的方法。它允许开发者在使用组件时,定制组件内部的部分内容。
- 使用:有默认插槽和具名插槽两种,使用 slot 标签定义插槽。
什么是 Vue 的 v-model?有什么作用?
详情
v-model
是 Vue.js 提供的一个指令,用于在表单控件元素(如 <input>
、 <textarea>
和 <select>
等)和数据模型之间创建双向绑定。它的作用是简化表单输入控件和数据之间的同步工作,使得数据更新变得更为方便和高效。
简单来说,v-model
把用户在表单控件中的输入值自动绑定到视图模型,根据用户输入的变化,模型也会自动更新,反之亦然。
怎么在组件初始化时立即调用 Vue 的 watch 函数?
详情
在 Vue 中,watch 是一个非常强大的工具,它可以用于观察和响应数据的变化。在默认情况下,当被监听的数据变化时,watch 才会触发。但有时候我们需要在组件初始化时即刻执行 watch 逻辑。幸运的是,Vue 提供了一个名为 immediate
的选项,它可以帮助我们实现这个需求。
扩展知识
了解了基本实现后,我再来讲讲 watch 更深层次的一些知识点。
- 1)Deep Watch:有时我们需要监听一个对象的变化,但默认情况下,Vue 只会监听对象的引用变化。如果你想监听对象内部值的变化,就需要使用 deep 选项。
- 2)Watch vs Computed:在 Vue 中,有时我们可能会困惑什么时候该用 watch,什么时候该用 computed。简单来说,computed 更适用于需要基于其他数据进行派生的情况下,而且它们具有缓存特性。而 watch 更适用于需要在数据变化时执行异步操作或比较复杂的逻辑。
- 3)监听多个数据源:
watch: {
'firstName': 'nameChanged',
'lastName': 'nameChanged'
},
methods: {
nameChanged() {
console.log(`Name changed: ${this.firstName} ${this.lastName}`);
}
}
- 4)watch VS watch: 除了组件的 watch 选项,还有实例方法 watch。它在运行时增加一个观察者,适用于一些需要动态监听的场景。
this.$watch('myData', function(newVal, oldVal) {
console.log('myData:', newVal);
}, { immediate: true });
在 .vue 文件中,style 和 script 必须要写么?为什么?
详情
在 .vue 文件中,style 和 script 并不是必须的。
原因如下: 1).vue
文件通常分为 <template>
, <script>
, <style>
三个部分,用于定义 Vue 组件的结构、逻辑和样式。但这三者并不是强制要求全部存在,具体取决于组件的实际需求。 2)如果一个组件只负责展示静态内容,而没有交互逻辑,就可以不包含 <script>
部分。 3)如果组件不需要特定的样式定义,也可以省略 <style>
部分。
在 Vue 组件中如何获取当前的路由信息?
详情
要在 Vue 组件中获取当前的路由信息,可以通过 this.$route
属性。 this.$route
是一个 Vue Router 提供的对象,包含了当前路由的所有信息,如路径、参数、查询字符串等。例如,你可以在任何组件的 mounted 生命周期钩子中访问这个对象。
Vue 项目中如何监听键盘事件?
详情
在 Vue 项目中监听键盘事件主要可以通过在模板中绑定原生事件处理器或者在组件的生命周期方法中添加事件监听器来实现。大致有以下两种方法: 1)在模板中直接绑定键盘事件,例如使用 @keydown
指令。 2)在组件的 mounted 生命周期函数中通过 JavaScript 的 addEventListener
方法监听键盘事件。
Vue 父子组件之间传值有哪些方式?
详情
在 Vue 中,父子组件之间传值的常用方式有以下几种:
- 1)Props:父组件通过 props 向子组件传递数据。
- 2)自定义事件:子组件通过 $emit 发送事件到父组件,并附带数据。
- 3)refs:父组件通过 ref 获取子组件实例,然后直接访问子组件的数据或方法。
为什么 Vue2.0 要求组件模板只能有一个根元素?
详情
回答重点 Vue 要求组件模板只能有一个根元素是为了避免多个根元素带来的复杂性和潜在的问题。单根元素帮助组件的递归渲染过程更加简单和高效,确保组件树结构的清晰和一致。
扩展知识
首先,让我们更详细地看一下 Vue 中为什么会有这个限制:
- 1)组件结构的清晰性:当每个组件都有一个根元素时,组件的结构变得更加直观和简单。我们可以很容易地追踪到组件树的层次结构。
- 2)提升性能:单一根元素可以让虚拟DOM的计算和差异化(diffing)算法更加高效。在处理多根元素时,Vue 的 diff 算法会变得更加复杂,可能会带来不必要的性能损失。
- 3)简化管理:当各个组件有明确、单一的根元素时,管理样式和事件绑定变得更加简单。一旦我们有了多个根元素,这些操作将变得难以追踪和维护。
- 4)与任何 DOM 操作工具更好地兼容:像 jQuery 或其他直接操作 DOM 的工具,更期望有一个明确的入口点来进行操作。单根元素能更好地与这些工具兼容。
接下来,我们来讨论一些实际的场景和例子:
- 组合使用多个组件:当我们在一个父组件中包含多个子组件时,每个子组件都有自己的根元素,能很好地确保各自内容的独立性和隔离性。
- 样式作用域:Vue 的 scoped styles 依赖于组件的根元素来限定样式的作用范围,如果没有单一根元素,很难避免样式被误用或影响其他组件。
扩展:从 Vue 2 到 Vue 3 的变化
在 Vue 2 中,模板必须有一个单根元素,否则会报错。然而,在 Vue 3 中引入了“Fragments”的概念,这意味着可以拥有多个根元素。不过, 在大多数场景下,建议我们仍然遵循单根元素的最佳实践,因为它能带来更好的代码可读性和维护性。
是否了解 Vue 官方的风格指南?请列举其中的一些规则
详情
Vue 官方的风格指南为开发者提供了一套建议,以帮助开发更可维护、更可预测和更一致的代码。以下是一些重要的规则:
- 1)组件名称始终是多个单词:避免使用单个单词的组件名称,以不与 HTML 元素名称冲突。例如,命名为 UserProfile 而不是 Profile。
- 2)Prop 名小写加连字符优先:在模板中定义 Prop 时,使用小写加连字符的形式,如 user-name。在 JavaScript 代码中则使用驼峰命名法,如 userName。
- 3)避免使用内联模板:尽量不要在 JavaScript 代码中使用内联模板,因为这样会降低可读性。优先使用 .vue 文件或 template 标签。
- 4)自闭合组件:单文件组件 (SFC) 或模板中的自定义组件应总是自闭合。如果某个组件内没有内容,应使用自闭合格式,如
<MyComponent />
。 - 5)每个页面定义一个顶级组件:在每个页面组件中,都应该有一个命名为 Base 的顶级布局组件,其它组件可以嵌套在这个顶级组件之后。
扩展知识
Vue 官方的风格指南分为几个优先级:必要(Essential)、强烈推荐(Strongly Recommended)和推荐(Recommended)。我们了解这些规则不仅可以提高代码质量,还能方便团队协作,一些常用的规则可以进一步讲解:
- 1)必要规则通常是那些违反后会导致严重错误或显著影响开发体验的规则。例如,始终将
v-for
与v-bind:key
一同使用,这样便可确保 DOM 的刷新逻辑正常。 - 2)强烈推荐的规则主要是为了提高代码的可读性和可维护性。例如,单文件组件的
<template>
、<script>
、<style>
标签顺序固定成先模板再脚本,其次是样式。 - 3)推荐的规则则是一些提升美观和一致性的建议。例如,组件标签或 Prop名称保持统一风格(如都使用驼峰形式或短横线形式)。
此外,Vue 的风格指南也强调了一些性能优化的建议,比如如何避免不必要的 watchers
、优先使用 computed
属性而不是 methods
方法等。这些细节在长期开发项目时将对项目维护和性能优化有很大帮助。
Vue 中是如何使用 event 对象的?
详情
在 Vue 中,我们可以通过事件处理器方法直接访问和使用 event
对象。具体来说,当我们在模板中添加事件监听器并绑定一个方法时,event
对象会自动作为参数传递给该方法。例如:
<!-- 示例:在点击事件中使用 event 对象 -->
<button @click="handleClick">点击我</button>
在 Vue 实例中定义 handleClick 方法:
methods: {
handleClick(event) {
console.log(event); // 这里可以访问到事件对象
}
}
在这个例子中,当按钮被点击时,handleClick
方法会被调用,并且点击事件对象 event
会作为第一个参数传递给方法。
扩展知识
- 1)事件修饰符:Vue 提供了一些事件修饰符,可以更加简便地处理常见的事件行为,例如 @click.stop 来阻止事件冒泡,@click.prevent 来阻止默认行为等。使用示例:
- 2)键盘事件:在处理键盘事件时,经常需要检查按下的键。Vue 提供了对常用键的别名支持,使得代码读起来更加直观,例如 @keyup.enter 用于检测回车键:
- 3)Event 对象属性:在处理 DOM 事件时,常用的 event 对象属性包括 event.target、event.currentTarget、event.preventDefault 以及 event.stopPropagation 等。这些属性和方法可以帮助我们更细粒度地控制事件行为。例如:
- 4)事件委托:在某些情况下,我们希望通过一个父元素来管理子元素的事件,这就需要用到事件委托。在 Vue 中,可以在父元素上绑定事件,并通过 event.target 来区分触发事件的具体子元素。例如:
怎么使 CSS 样式只在当前 Vue 组件中生效?
详情
在 Vue.js 中,如果我们希望 CSS 样式只在当前组件中生效,可以使用 scoped 关键字。具体做法是在 <style>
标签后面加上 scoped
属性,这样,样式就只会应用在当前组件内的元素上,不会影响外部的样式,甚至不会影响其他组件。
介绍下 Vue 组件的命名规范?
详情
在 Vue 中,组件名称应当遵循以下规范,以便提高代码的可读性和一致性:
- 1)使用多单词的组件名:为了避免与 HTML 元素名称冲突,尽量使用多个单词来命名组件,例如 "UserProfile" 而不是 "Profile"。
- 2)使用
PascalCase
或kebab-case
:在单文件组件(.vue
)和字符串模板中使用PascalCase
(如 "MyComponent"),在 DOM 模板或 HTML 文件中使用kebab-case
(如 "my-component")。 - 3)自闭合组件:在单文件组件中,自闭合组件使用
PascalCase
,DOM 模板中使用kebab-case
。 - 4)前缀化:组件名称通常应该以特定前缀开始,特别是在创建库或可复用组件时,避免与他人组件名称冲突。例如 "BaseButton","AppHeader"。
扩展知识
除了基本的命名规范外,还有一些额外的最佳实践和注意事项:
- 1)子组件命名:当组件依赖于兄弟组件或父组件时,可以使用描述这些关系的命名方式。例如在 "UserProfile" 组件中,相关联的组件可以命名为 "UserProfileDetail" 或 "UserProfileAvatar"。
- 2)组织结构:将组件文件依据功能进行分组和命名,比如在
project/src/components/
目录下创建 "User" 文件夹,其中包含 "UserProfile.vue", "UserList.vue" 等。 - 3)动态组件名称:在动态组件中 ("
<component :is="componentName"></component>
"),确保动态加载的组件名也符合命名规范,以保证一致性。 - 4)组件名冲突问题:尤其是在大型项目和多人协作时,命名冲突可能变得频繁,因此前缀化和模块化是避免冲突的最佳解决方案。例如在 组件库 中,用库名作为前缀(如 "VButton" 表示来自某个组件库的按钮)。
- 5)统一命名规则:团队内部应当对组件命名标准达成一致,并记录在案。这样可以避免因为命名不统一带来的维护麻烦。
Vue 常用的修饰符有哪些?分别有哪些应用场景?
详情
在 Vue 中,修饰符是一种特殊的后缀,用于为指令提供额外的功能。以下是 Vue 中常用的修饰符及其应用场景:
1. 事件修饰符
.stop
:阻止事件冒泡。
应用场景:当你不希望事件向上传播到父元素时使用。例如,在一个嵌套的按钮点击事件中,只触发当前按钮的点击事件,而不触发父元素的点击事件。
.prevent
:阻止默认事件。
应用场景:当你需要阻止表单提交、链接跳转等默认行为时使用。例如,在表单提交时,你可能需要先进行一些验证,而不是直接提交表单。
.capture
:使用事件捕获模式。
应用场景:在事件捕获阶段触发事件处理函数,而不是在冒泡阶段。这在需要优先处理父元素事件时使用。
.self
:只当事件在该元素本身触发时才触发回调。
应用场景:确保事件处理函数只在当前元素上触发,而不是由子元素冒泡上来触发。
.once
:事件只触发一次。
应用场景:当你只需要事件触发一次时使用,例如点击按钮后只执行一次某个操作。
.passive
:告诉浏览器你不想阻止事件的默认行为。
应用场景:在处理滚动事件时,可以提高性能,避免浏览器在处理滚动事件时进行不必要的检查。
2. 按键修饰符
.enter
、.tab
、.delete
、.esc
、.space
、.up
、.down
、.left
、.right
:监听特定的按键事件。
应用场景:在输入框中监听特定的按键事件,例如按下回车键提交表单。
3. 表单修饰符
.lazy
:将v-model
在change
事件而不是input
事件上同步。
应用场景:当你不希望每次输入都触发数据更新,而是在输入框失去焦点或按下回车键时更新数据。
.number
:将输入的值转换为数字类型。
应用场景:在输入框中输入数字时,确保绑定的数据为数字类型。
.trim
:自动过滤输入的首尾空格。
应用场景:在输入文本时,去除首尾空格,避免不必要的空格影响数据处理。
第一次加载 Vue 页面时会触发哪些生命周期钩子?
详情
Vue2 版本
在首次加载 Vue 页面时,会按照以下顺序触发一系列生命周期钩子函数:
- beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时,data 和 methods 等属性都还未初始化,不能访问。
- created:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 (data observer)、property 和 method 的计算、watch/event 事件回调。然而,挂载阶段还没有开始,$el 属性目前不可用。
- beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。此时模板已经编译完成,但尚未挂载到页面上。
- mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。此时,模板已经编译完成并挂载到页面上,可以访问 DOM 元素。
Vue3 版本
- setup: setup 是 Vue 3 组合式 API 的入口函数,它在组件初始化时最先执行,在 beforeCreate 和 created 钩子之前。setup 主要用于处理响应式数据、方法和生命周期钩子的组合。
- onBeforeMount: 在挂载开始之前被调用,此时模板已经编译完成,但还未挂载到页面上。
- onMounted: el 被新创建的 vm.$el 替换,并挂载到实例上之后调用。此时可以访问 DOM 元素。
在 Vue 中,如果变量名以 _ 或 $ 开头,会有什么问题?如何访问到这些值?
详情
在 Vue 中,如果变量名以 _
或 $
开头,这些变量会被 Vue 框架认为是保留属性,不会作为数据代理到 Vue 实例上,不会出现在 this.$data
对象中。所以,直接通过 this.variableName
无法访问这些变量。
为了访问这些变量,可以通过 this.$data._variableName
或 this.$data.$variableName
的形式访问到这些值。
扩展知识
1)保留属性概念:Vue 框架在设计时,为了避免与用户定义的属性名称产生冲突,专门保留了一些以
_
和$
开头的属性,比如$el
,$data
,$_uid
等。这些保留属性通常用于 Vue 内部处理和框架特性中。2)Vue Proxy 机制:Vue 通过 Proxy 实现数据的双向绑定和响应式系统,但为了防止污染 Vue 实例对象以及避免属性名冲突,Vue 对以
_
和$
开头的属性进行了特殊处理。3)访问方式:当需要访问以
_
或$
开头的变量时,不要试图直接通过this.variableName
访问,而是应通过this.$data.variableName
即$data
对象来访问。这是一种绕过 Vue 代理机制的小技巧,但通常不推荐过多使用这种命名方式。4)设计原则:常规开发中,通常建议避免使用以
_
和$
开头的变量名,以防止混淆和访问上的困难。遵守这种约定可以让代码更清晰、易于维护。
为什么 Vue 中给对象添加新属性后界面不刷新?
详情
在 Vue 中,给对象添加新属性后,界面不会自动刷新。这是因为 Vue 不能检测属性的添加或删除。Vue 的响应式系统在初始化时只会对对象已有的属性进行监听,因此新属性的添加不会被 Vue 监测到,也不会触发视图更新。
扩展知识
1)Vue 是如何实现响应式的?
Vue 使用了 Object.defineProperty 来实现对数据的劫持和监听。当你给某个对象添加一个新的属性时,Vue 并没有机会通过 Object.defineProperty 监听这个新的属性,因为这个属性在 Vue 初始化之后才添加的。
2)如何解决这个问题?
有几种常见的方法可以让 Vue 重新监测到新添加的属性:
- 使用
Vue.set()
方法:Vue 提供了一个全局方法 Vue.set(target, key, value), 可以向对象添加新属性并触发视图更新。 this.set()
方法:在 vue 实例内部,可以使用实例方法this.set()
, 效果和Vue.set()
一样。- 替换整个对象:另一种办法是创建一个新的对象,将旧对象的属性和新属性一起赋值给这个新对象,然后替换旧对象。
3)Vue 3 的 Proxy 对象改善:
在 Vue 3 中,这个问题得到了解决。Vue 3 使用 Proxy
对象来实现响应式系统,Proxy
可以直接监听属性的添加和删除,因此对于新添加的属性也能自动检测到并更新视图。
4)实际应用中应注意的问题:
- 当我们依赖 Vue 的响应式系统的时候,务必要注意初始数据结构的设计,尽量避免后续频繁地添加新属性。
- 在大型项目中,应努力保持数据结构的稳定和确定性,这样可以减少维护成本和潜在的 Bug。
v-on 在 Vue 中可以绑定多个方法吗?
详情
在 Vue 中,v-on
可以绑定多个方法,但是需要通过包裹一个方法,在这个方法内部再依次调用其他方法。直接使用 v-on
指定多个方法是不被支持的。
<template>
<button @click="handleClick">点击我</button>
</template>
<script>
export default {
methods: {
method1() {
console.log('方法 1 被调用');
},
method2() {
console.log('方法 2 被调用');
},
handleClick() {
// 依次调用多个方法
this.method1();
this.method2();
}
}
};
</script>
Vue Router 的 hash 模式和 history 模式有什么区别?
详情
Vue Router 的 hash 模式和 history 模式主要区别在于 URL 的表现形式和实现方式。
1)hash 模式:URL 中会出现 #
号,例如:http://example.com/#/home
。hash 模式的原理是基于 window.location.hash
的变化,来实现更新视图而不重新加载页面。hash 模式的核心特性是利用了浏览器对哈希的支持,使得页面在哈希值变化时不会重新加载全部内容。
2)history 模式:URL 是普通路径形式,例如:http://example.com/home
。history 模式的原理是基于 HTML5 的 history.pushState
和 history.replaceState
,通过这两个 API 来操作浏览器历史记录,改变 URL 同时不重新加载页面。所以使用 history 模式时,需要在服务端进行配置,让所有路由都指向同一个入口页面,否则会出现 404 错误。
扩展知识
既然了解了 hash 模式和 history 模式的基本区别,可以进一步来看看它们各自的优缺点以及具体的实现细节。
1)hash 模式优缺点:
优点: 兼容性好:即使在一些老旧的浏览器中也能正常工作,因为 hash 模式不依赖于 HTML5 的新特性。 实现简单:不需要服务器端的额外配置,适合仅用在客户端的单页面应用。
缺点: URL 不美观:# 符号在 URL 中显得有点冗杂,影响美观。 SEO 不友好:搜索引擎通常不会索引 # 后面的内容,可能影响网页的 SEO 效果。
2)history 模式优缺点:
优点: URL 美观:与传统的 URL 形式一致,没有冗余的符号,显得更加简洁和专业。 SEO 友好:由于 URL 保持干净,搜索引擎能够正常索引,有利于 SEO。
缺点: 需要服务器端支持:如果服务器没有做相应的配置(比如使用 Node.js,配置 express 的 history-fallback 中间件,或者 Nginx 等服务器配置单页面应用的路由跳转),页面刷新时可能会出现 404 错误。 兼容性相对较差:虽然大部分现代浏览器都已经支持,但一些老旧浏览器可能不支持 HTML5 的 history API。 关于如何选择模式,可以根据项目的实际需求来决定。如果重视 SEO 或者需要 URL 保持简洁美观,推荐使用 history 模式;如果对兼容性要求较高或项目不涉及 SEO,hash 模式则是一个相对简单、安全的选择。
Vue 实例在挂载过程中发生了什么?
详情
Vue 实例在挂载过程中,主要发生了以下几个步骤:
- 1)初始化:Vue 实例会依次初始化生命周期函数、事件、props、methods、data、computed 和 watch 等选项。
- 2)模板编译:如果提供了 template 选项,则会编译该模板。如果没有,则使用 el 属性所指向的 DOM 元素的内容作为模板。
- 3)创建虚拟 DOM:Vue 会根据编译好的模板生成虚拟 DOM。
- 4)数据响应系统:Vue 会使用其响应式系统将数据和 DOM 进行绑定,数据变化时会自动更新 DOM。
- 5)挂载到 DOM:最终,Vue 会将虚拟 DOM 渲染成真实的 DOM 并替换挂载点元素。
扩展知识
1)初始化流程 当我们调用 new Vue() 创建实例时,Vue 会进行一系列的初始化操作,这包括建立生命周期钩子中的各个函数(比如 beforeCreate、created 等)以及初始化事件系统。
2)模板编译 如果用户提供的是字符串形式的 template,Vue 会调用编译器将模板编译成渲染函数。这个过程会解析模板中的指令(例如 v-if、v-for)并转换成相应的 JavaScript 代码。这一步主要是通过 Vue 的编译器完成的。
3)虚拟 DOM 与响应式系统 Vue 使用虚拟 DOM(Virtual DOM)技术来最小化直接操作真实 DOM 的成本。虚拟 DOM 是对真实 DOM 的一个轻量级副本。在初始化时,Vue 会“观察”数据对象,对其进行属性劫持,使得数据变更时能自动触发视图更新。Vue 采用的是基于 ES5 的 Object.defineProperty 进行双向的数据绑定,当数据改变时,它会通知视图层做出相应的更新。
4)挂载过程 在实际挂载到 DOM 上的时候,Vue 会通过虚拟 DOM diff 算法找到最小的变更路径,提高性能。这一步会把虚拟 DOM 转化为真实的 DOM,并插入到页面中。
5)生命周期钩子 在整个挂载过程中,Vue 提供了一系列的生命周期钩子,允许开发者在某些特定阶段执行代码,例如在 beforeMount、mounted、beforeUpdate 和 updated 等钩子函数中,我们可以添加一些需要在特定阶段执行的逻辑。
说说你对 Vue 的理解?相比于原生开发,使用 Vue 有哪些优点?
详情
Vue 是一个用于构建用户界面的渐进式 JavaScript 框架。它的核心库专注于视图层,易于上手,并且可以与其他库或已有项目集成。Vue 主要通过双向数据绑定、组件化开发和虚拟 DOM 来简化前端开发。
相比于原生开发,使用 Vue 有以下优点:
- 1)双向数据绑定:Vue 提供了一套简洁的双向数据绑定机制,开发者可以轻松实现数据和视图的同步更新,而无需自己去操作 DOM。
- 2)组件化开发:Vue 强调组件化,通过组件可以将 UI 拆分成独立、可复用的部件,使得代码更模块化、可维护。
- 3)虚拟 DOM:Vue 的虚拟 DOM 有效地提升了性能,减少了直接操作 DOM 带来的性能开销。
- 4)渐进式框架:可以根据需要渐进地选择 Vue 的组件和功能,扩展项目规模。
- 5)生态系统丰富:Vue 具有丰富的官方和第三方库,如 Vue Router、Vuex 等,方便实现项目中的常见需求。
扩展知识
1)Vue 生命周期 Vue 组件有一系列的生命周期钩子,从实例创建到销毁的过程,包括created、mounted、updated、destroyed等。理解这些钩子函数有助于在适当的时机执行特定的代码。
2)Vue CLI Vue CLI(命令行工具)是一个基于 Vue.js 的全功能开发工具,可以很方便地创建、管理、配置和调试 Vue.js 项目。它的图形用户界面(GUI)进一步简化了开发和管理项目的过程。
3)Vue 路由(Vue Router) Vue Router 是 Vue.js 官方的路由管理器,用于构建单页面应用(SPA)。它支持嵌套路由、命名视图、导航守卫等功能,极大地简化了前端路由管理。
4)状态管理(Vuex) Vuex 是 Vue.js 官方的状态管理模式,适用于中大型项目状态的集中式管理。它提供了一个单一状态树(单一数据源),让组件之间更容易共享状态和数据。
5)SSR(服务器端渲染) 服务器端渲染(SSR)是将 Vue 组件在服务器端渲染成 HTML 字符串,再发送到浏览器。相比于客户端渲染,SSR 能提升首屏加载速度,对 SEO 友好。
6)性能优化 Vue 提供了一系列的性能优化策略,如 v-if 和 v-show 的合理使用、路由懒加载、异步组件、优化复杂视图中的渲染性能等。这些优化手段尤为重要,能大大提升应用的响应速度和用户体验。
什么是 Vue Router 的 router-link 组件?
详情
Vue Router 的 router-link
组件是一个用于创建导航链接的组件,它允许我们在应用中声明式地定义不同路由之间的导航。换句话说,router-link
用来在一个 Vue.js 应用中创建链接,通过点击这些链接,我们可以在不同的视图组件之间进行切换,而不需要手动地写 JavaScript 代码来处理导航逻辑。
1)基本用法: router-link 组件主要通过 prop 属性来配置目的地路径。例如:
<router-link to="/about">About</router-link>
2)动态参数: router-link 还支持动态参数,例如:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
3)激活和精确匹配: router-link 的 exact 属性决定了它是否在匹配路由的时候要求路径完全匹配:
<router-link to="/" exact>Home</router-link>
4)样式控制: router-link 组件也允许我们通过 active-class 和 exact-active-class 属性来自定义激活链接的 CSS 类名:
<router-link to="/" active-class="my-active-class">Home</router-link>
5)导航守卫: 除了导航功能,Vue Router 还能提供一些钩子函数,这些函数在导航前、导航后都可以执行特定的逻辑,例如验证用户权限,加载数据等等。
Vue 中 :class 和 :style 有几种表示方式?
详情
在 Vue 中,:class 和 :style 可以用多种方式进行绑定: 1)对象语法:用对象来动态地绑定多个 class 或 style。 2)数组语法:通过数组来动态地绑定 class 或 style。 3)直接绑定字符串:直接绑定单个 class 名字或 style 字符串。
什么是 Vue 的前端路由?如何实现?
详情
Vue 的前端路由是一种用来管理单页面应用(SPA)中不同视图之间导航的机制。Vue 通过 Vue Router 这个官方插件来实现前端路由。Vue Router 可以让你在不重新加载整个页面的前提下,用户能够通过 URL 导航到不同的页面,保持单页面应用的流畅性和用户体验。
Vue 路由之间是如何跳转的?有哪些跳转方式?
详情
在 Vue 项目中,路由跳转是通过 Vue Router 实现的。Vue Router 提供了两种基本的跳转方式:编程式导航和声明式导航。
1)编程式导航:使用 this.$router.push
和 this.$router.replace
方法进行路由跳转。其中 push
方法会在浏览器的历史记录中增加一条新记录,而 replace
方法则会替换当前的历史记录。
// 编程式导航实例
this.$router.push('/home'); // 跳转到/home路径
this.$router.replace('/login'); // 跳转到/login路径,替换当前的路径
2)声明式导航:通过 <router-link>
组件进行跳转。这种方式在模板中使用,使代码更清晰直观。
<!-- 声明式导航实例 -->
<router-link to="/home">Go to Home</router-link>
Vue 使用 v-for 遍历对象时,是按什么顺序遍历的?如何保证遍历顺序?
详情
在 Vue 中使用 v-for 遍历对象时,遍历的顺序是按照对象的键进行的,它并不保证按插入的顺序进行。实际上,键的顺序是由 JavaScript 引擎决定的。在现代的 JavaScript 引擎中,通常会先遍历整数键,然后遍历字符串键。
为了保证遍历顺序,可以将对象转换成一个数组,用 v-for 遍历这个数组,并在转换过程中按需要的顺序排序。例如,将对象转换为键值对的数组,然后用 Array.prototype.sort 方法进行排序。
扩展知识
1)对象的遍历顺序: 在 JavaScript 中,对象的键是无序的。这意味着不同的 JavaScript 引擎可能会对对象的键以不同的顺序进行遍历。通常,处于下列顺序:
- 整数键按升序排列。
- 之后是字符串键,按照插入的顺序进行排列(但这并不是严格保证的)。
- 最后是符号键,按插入顺序排列。
2)使用数组确保顺序: 为了明确保证遍历的顺序,我们可以先将对象的键值对转换为数组,然后对数组进行排序。最后在 Vue 中通过 v-for 指令遍历这个排序后的数组。
Vue 中怎么获取 DOM 节点?
详情
在 Vue 中获取 DOM 节点,通常使用的是 ref 属性。具体操作如下:
- 1)在模板中,我们给要获取的 DOM 节点添加一个
ref
属性,例如<div ref="myDiv"></div>
。 - 2)在 Vue 实例中,我们通过
this.$refs
对象来访问这个 DOM 节点,例如this.$refs.myDiv
。
如何重置 Vue 的 data?
详情
要重置 Vue 组件的 data,你可以通过重置 data 属性的初始状态来实现。通常有两种方式:
- 1)使用一个初始状态的工厂函数。
- 2)直接替换整个 data 对象。
最常用的方法是使用工厂函数返回原始状态的数据对象。下面是具体操作步骤:
- 1)首先,把 data 设计成一个函数来返回对象,而不是直接定义为一个对象。这是为了确保每次调用时都得到一个全新的对象。
- 2)然后,通过发布/订阅模式或某种事件去触发数据重置,从而调用这个工厂函数。
如何访问 Vue 子组件的实例或子元素?
详情
在 Vue 中,我们可以通过 $refs
来访问子组件的实例或子元素。首先,确保您在子组件或子元素上添加了 ref
属性。然后,在父组件中就可以通过 this.$refs
来访问这些子组件或子元素的实例。
在 Vue 事件中传入 $event 使用 e.target 和 e.currentTarget 有什么区别?
详情
在 Vue 事件中,$event
代表的是原生的 JavaScript 事件对象。通过这个对象,你可以访问事件发生的元素、事件类型等详细信息。而在事件对象中,e.target
和 e.currentTarget
是非常重要的两个属性。
- 1)
e.target
:表示实际触发事件的元素。也就是说,用户点击或操作的那个具体的 DOM 元素。 - 2)
e.currentTarget
:表示事件绑定的元素。也就是说,你在 Vue 模板中用来绑定事件监听器的那个元素。
扩展知识
实际使用场景对比:
- 假设我们有一个按钮在一个容器内,容器绑定了一个点击事件,按钮也绑定了一个点击事件。当我们点击按钮时:
- e.target 是按钮,因为它是实际被点击的元素。
- e.currentTarget 是事件监听器绑定的容器。
事件冒泡:
- 事件冒泡机制会让事件从最深层的元素逐步向上传播,这意味着如果一个事件监听器绑定在某个父元素上,但事件是由其子元素触发的,那么:
- e.target 是子元素。
- e.currentTarget 是父元素。
- 理解这两者的差异对于正确处理冒泡事件和控制事件传播非常重要。
在 Vue 中的应用:
当处理复杂的组件交互时,特别是在父子组件事件绑定时,区分 e.target 和 e.currentTarget 能帮助你更准确地确定是哪一个元素触发了事件以及事件监听器是绑定在哪一层的元素上。 例如:假设有一个包含多个元素的列表,点击列表中的某个元素时,你想要获取被点击元素的特定属性值,此时使用 e.target 可能会更加合适。如果你需要确定事件的绑定元素及其相关特性,则使用 e.currentTarget 更为适宜。
Vue Router 中 params 和 query 有什么区别?
详情
在 Vue Router 中,params 和 query 是两种不同的传参方式:
- 1)params:用于动态路径参数,需要在路由配置中定义。例如
/user/:id
中的 id 是一个动态参数,你可以通过this.$route.params.id
来获取它。params 更加隐蔽,不直接暴露在 URL 中。 - 2)query:用于查询参数,使用前不需要在路由配置中定义。它们会以键值对的形式出现在 URL 的问号之后。例如
/user?id=123
,你可以通过this.$route.query.id
来获取 id 的值。query 更适合较为显性的参数传递和处理。
请说下封装 Vue 组件的过程?
详情
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。 1、分析需求:确定业务需求,把页面中可以服用的结构,样式以及功能,单独抽离成一个文件,实现复用。 2、具体步骤:使用 Vue.extend
方法创建一个组件,然后使用 Vue.component
方法注册组件,子组件需要数据,可以在 props 中接受定义,而子组件修改好数据后,想把数据传递给父组件,可以采用$emit
方法。
怎么捕获 Vue 组件的错误信息?
详情
在 Vue 里,捕获组件错误信息可以通过以下几种方式实现:
1. errorCaptured
钩子函数
在组件内使用 errorCaptured
钩子,该钩子能够捕获来自子孙组件的错误。它接收三个参数:错误对象、发生错误的组件实例以及错误信息的字符串。
<template>
<div>
<ChildComponent />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
errorCaptured(err, vm, info) {
console.error('捕获到错误:', err);
console.log('错误发生的组件实例:', vm);
console.log('错误信息:', info);
// 返回 false 阻止错误继续向上传播
return false;
}
};
</script>
2. Vue.config.errorHandler
全局设置 Vue.config.errorHandler
,它能捕获所有未被处理的错误。
import Vue from 'vue';
import App from './App.vue';
Vue.config.errorHandler = function (err, vm, info) {
console.error('全局捕获到错误:', err);
console.log('错误发生的组件实例:', vm);
console.log('错误信息:', info);
};
new Vue({
render: h => h(App)
}).$mount('#app');
3. @vueuse/core 中的 useErrorBoundary
如果你使用 @vueuse/core
库,可以借助 useErrorBoundary
来捕获组件错误。
如果你使用 @vueuse/core
库,可以借助 useErrorBoundary
来捕获组件错误。
<template>
<div>
<slot v-if="!hasError" />
<div v-else>发生错误: {{ error.message }}</div>
</div>
</template>
<script>
import { useErrorBoundary } from '@vueuse/core';
export default {
setup() {
const { error, hasError } = useErrorBoundary();
return {
error,
hasError
};
}
};
</script>
4. 在异步操作中捕获错误
在 async/await
或者 Promise
里捕获错误,防止错误冒泡。
<template>
<button @click="fetchData">获取数据</button>
</template>
<script>
export default {
methods: {
async fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('异步操作出错:', error);
}
}
}
};
</script>
Vue 组件里的定时器要怎么销毁?
详情
在 Vue 组件中使用定时器时,为了避免内存泄漏,需要在组件销毁时清除定时器。不同 Vue 版本的实现方式有所不同,下面分别介绍 Vue 2 和 Vue 3 的处理方法。
Vue 2
在 Vue 2 中,可以在 beforeDestroy
(Vue 2.0 - 2.2)或 beforeUnmount
(Vue 2.3+)钩子中清除定时器。
Vue 3
在 Vue 3 中,使用组合式 API,需要在 onBeforeUnmount
钩子中清除定时器。
Vue 该如何实现组件缓存?
详情
在 Vue 里,要实现组件缓存可借助 <keep-alive>
组件。<keep-alive>
是一个内置组件,它能把包裹的组件实例缓存起来,防止组件在切换时被销毁和重新创建,从而提升性能。下面详细介绍其使用方法。
基本使用 在模板中用
<keep-alive>
包裹需要缓存的组件。包含与排除 可通过
include
和exclude
属性来指定哪些组件需要缓存或者不缓存。
include
:只缓存名称匹配的组件。 exclude
:不缓存名称匹配的组件。
- 缓存组件的生命周期钩子 被
<keep-alive>
包裹的组件会新增两个生命周期钩子:
- activated:组件被激活时调用。
- deactivated:组件被停用时调用。
- Vue 3 中的使用 在 Vue 3 里,
<keep-alive>
的使用方式和 Vue 2 基本一致,不过在组合式 API 里,可通过onActivated
和onDeactivated
来处理组件激活和停用的逻辑。
指令 v-el 的作用是什么?
详情
v-el 是 Vue 1.x 版本中的一个指令,在 Vue 2.0 及更高版本中已经被弃用,取而代之的是 ref 属性。下面为你详细介绍 v-el 的作用以及在 Vue 2.x 和 3.x 中的替代方案。
v-el 在 Vue 1.x 中的作用 在 Vue 1.x 里,v-el 指令用于给元素或组件添加一个引用 ID。通过这个 ID,就能在 Vue 实例中直接访问该元素或组件的 DOM 节点。
Vue 2.x 和 3.x 中的替代方案
在 Vue 2.0 及更高版本中,v-el 被弃用,采用 ref 属性来替代。ref 的使用方式和 v-el 类似,不过访问方式有所不同。
Vue-loader 是什么?使用它的用途有哪些?
详情
Vue-loader 是一个用于处理 Vue 单文件组件(SFC,即 .vue 文件)的 webpack 加载器。单文件组件是 Vue.js 中一种非常方便的组织代码的方式,它将模板、脚本和样式封装在一个文件中。Vue-loader 的主要作用就是将这些 .vue 文件转换为 JavaScript 模块,以便 webpack 能够处理它们。
以下是使用 Vue-loader 的主要用途:
模板编译:Vue-loader 会将 .vue 文件中的
<template>
部分编译成渲染函数。渲染函数是 Vue 用来创建虚拟 DOM 的函数,它们可以在运行时高效地生成实际的 DOM 元素。脚本处理:
<script>
部分通常包含组件的 JavaScript 代码。Vue-loader 会确保这些代码被正确地加载和处理,并且可以与其他 JavaScript 模块集成。样式处理:
<style>
部分包含组件的 CSS 样式。Vue-loader 可以处理这些样式,并且支持不同的 CSS 预处理器(如 Sass、Less 等)。它还可以通过 scoped 属性实现局部样式,确保样式只应用于当前组件。热更新:Vue-loader 支持热更新(Hot Module Replacement,HMR),这意味着在开发过程中,当你修改 .vue 文件时,不需要刷新整个页面就可以看到更新后的效果。这大大提高了开发效率。
资源导入:Vue-loader 允许在 .vue 文件中导入其他资源,如图像、字体等。它会使用 webpack 的资源加载器来处理这些导入,确保资源被正确地打包和引用。
Vuex 的5个核心属性是什么?
详情
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 的 5 个核心属性分别是:state、getters、mutations、actions 和 modules。以下是对这 5 个核心属性的详细解释:
- state: 作用:state 是 Vuex 中的数据源,它是一个对象,用于存储应用的所有状态数据。在 Vue 组件中,可以通过 this.$store.state 来访问这些数据。
- getters: 作用:getters 类似于 Vue 中的计算属性,用于从 state 中派生出一些状态。当 state 中的数据发生变化时,getters 会自动更新。在 Vue 组件中,可以通过 this.$store.getters 来访问这些派生状态。
- mutations: 作用:mutations 是唯一可以修改 state 的地方,它是一个对象,其中的每个方法都接收 state 作为第一个参数,还可以接收一个可选的 payload 参数。在 Vue 组件中,可以通过 this.$store.commit 来触发 mutations 中的方法。
- actions: 作用:actions 用于处理异步操作,如发送网络请求等。它是一个对象,其中的每个方法都接收一个 context 对象作为第一个参数,context 对象包含了 state、getters、commit 等属性。在 Vue 组件中,可以通过 this.$store.dispatch 来触发 actions 中的方法。
- modules: 作用:当应用变得非常复杂时,state 会变得非常庞大,这时可以使用 modules 将 store 分割成多个模块,每个模块都有自己的 state、getters、mutations 和 actions。
Vuex 的出现解决了什么问题?
详情
主要解决了以下两个问题:
- 1、多个组件依赖于同一状态时,对于多层嵌套的组件的传参将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
- 2、来自不同组件的行为需要变更同一状态。以往采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码
简述 Vuex 的数据传递流程?
详情
Vuex 是专为 Vue.js 应用程序开发的状态管理模式,其数据传递流程如下:
- 组件触发 Action:在 Vue 组件里,当用户操作(像点击按钮)产生时,组件会调用 this.$store.dispatch 方法触发一个 Action。
- Action 处理异步操作:Action 是用来处理异步操作的,像发送网络请求。它接收一个 context 对象作为参数,此对象包含 state、getters、commit 等属性。在 Action 里可以进行异步操作,操作完成后调用 context.commit 触发一个 Mutation。
- Mutation 修改 State:Mutation 是唯一能修改 State 的地方,它接收 state 作为第一个参数,还能接收一个可选的 payload 参数。在 Mutation 里修改 State 后,Vuex 会自动更新与之绑定的组件视图。
- 组件获取更新后的 State:组件可以通过 this.$store.state 来获取更新后的 State 数据,从而更新视图。
综上所述,Vuex 的数据传递流程是:组件触发 Action,Action 处理异步操作后触发 Mutation,Mutation 修改 State,组件获取更新后的 State 数据更新视图。
Vuex 的 Mutation 和 Action 之间的区别是什么?
详情
1、流程顺序
“相应视图—>修改State”拆分成两部分,视图触发Action,Action再触发Mutation
2、角色定位
基于流程顺序,二者扮演不同的角色
- 1)Mutation:专注于修改State,理论上是修改State的唯一途径
- 2)Action:业务代码、异步请求
3、限制
1)角色不同,二者有不同的限制 2)Mutation:必须同步执行 3)Action:可以异步,但不能直接操作State
active-class 属于哪个组件中的属性?该如何使用?
详情
active-class
是 router-link
组件中的属性,router-link
是 Vue Router 提供的用于创建导航链接的组件。active-class
属性的作用是自定义当 router-link
对应的路由被激活时,应用到该链接上的 CSS 类名。
使用方法
下面是几种使用 active-class 属性的示例:
1. 基本使用
在 router-link
组件中直接使用 active-class
属性指定激活时的 CSS 类名。
<router-link to="/home" active-class="my-active-class">Home</router-link>
当用户访问 /home
路由时,该链接的 HTML 元素会自动添加 my-active-class
这个 CSS 类名。你可以在 CSS 中定义这个类的样式:
.my-active-class {
color: red;
font-weight: bold;
}
2. 全局配置
除了在每个 router-link
组件中单独设置 active-class
,还可以在创建 Vue Router 实例时进行全局配置:
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/home',
name: 'Home',
component: Home
}
];
const router = new VueRouter({
routes,
linkActiveClass: 'my-active-class' // 全局配置激活类名
});
export default router;
这样,所有的 router-link
组件在对应的路由被激活时,都会自动添加 my-active-class
类名。
3. 精确匹配激活类名
如果需要在路由精确匹配时应用不同的激活类名,可以使用 exact-active-class
属性:
<router-link to="/" exact-active-class="exact-active">Home</router-link>
当用户访问根路径 /
时,该链接的 HTML 元素会添加 exact-active
类名。
通过以上方式,你可以灵活地使用 active-class
属性来控制 router-link
组件在激活时的样式。
Vue 怎么实现跨域
详情
在 Vue 项目中,跨域问题通常是由于浏览器的同源策略导致的。同源策略要求浏览器在访问资源时,协议、域名和端口都必须相同,否则会被阻止。以下是几种常见的实现跨域的方法:
1. 开发环境下使用代理服务器(Vue CLI)
如果你使用的是 Vue CLI 创建的项目,可以在 vue.config.js 文件中配置代理服务器。这样,在开发环境中,所有的请求都会被转发到目标服务器,从而绕过浏览器的同源策略。
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com', // 目标服务器地址
changeOrigin: true,
pathRewrite: {
'^/api': '' // 重写请求路径
}
}
}
}
}
在上述配置中,所有以 /api
开头的请求都会被代理到 http://api.example.com
。在组件中使用时,你可以这样发起请求:
this.$axios.get('/api/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
2. JSONP(JSON with Padding)
JSONP 是一种跨域数据交互的技术,它利用了 <script>
标签的 src
属性不受同源策略限制的特点。JSONP 只支持 GET 请求。
3. CORS(Cross-Origin Resource Sharing)
CORS 是一种现代的跨域解决方案,它需要服务器端进行配置。服务器通过设置响应头来允许跨域请求。
4. 使用代理服务器(生产环境)
在生产环境中,你可以使用 Nginx 或 Apache 等服务器作为代理服务器。以下是一个 Nginx 的配置示例:
Vue 中动画如何实现
详情
1、哪个元素需要动画就给那个元素加transition标签 2、进入时class的类型分为以下几种
<name>-enter
<name>-enter-active
<name>-enter-to
3、离开时class的类型分为以下几种<name>-leave
<name>-leave-active
<name>-leave-to
如果需要一组元素发生动画需要用标签 <transition-group>
你对 Vue.js 的 template 编译的理解?
详情
简而言之,就是先转化成 AST 树,再得到的 render 函数返回 Vnode(Vue的虚拟DOM节点) 1、首先通过 compile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即源代码的抽象语法结构的树状表现形式),compile 是 createCompiler 的返回值,createCompiler 是用以创建编译器的,另外 compile 还负责合并 option 2、然后 AST 会经过 generate(将 AST 语法树转化成 render funtion 字符串的过程)得到 render 函数,render 的返回值是 Vnode,Vnode 是 Vue 的虚拟 DOM 节点,里面有(标签名、子节点、文本等等)
Vue 如何去除 URL 中的
详情
在 Vue 里,URL 中的 #
通常是由 Vue Router 的 hash
模式所产生的。若要去除这个 #
,可以把 Vue Router 的模式从 hash
模式切换成 history
模式。下面是具体的操作步骤:
- 修改路由配置 在路由配置文件(一般是 router/index.js)里,把 mode 选项设置为 'history'。
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home
}
];
const router = new VueRouter({
mode: 'history', // 设置为 history 模式
routes
});
export default router;
- 服务端配置 采用
history
模式时,因为 URL 不再包含#
,浏览器在刷新页面时会直接向服务器发送请求。所以,需要在服务端进行配置,让所有路由都指向同一个入口页面(通常是index.html
),不然就会出现 404 错误。
在Vue 中使用插件的步骤
详情
- 采用ES6的
import ... from ...
语法或CommonJS的require()
方法引入插件 - 使用全局方法
Vue.use( plugin )
使用插件,可以传入一个选项对象Vue.use(MyPlugin,{ someOption: true })
VNone 是什么?什么是虚拟 DOM?
详情
在提供的文本里,VNone
并未出现过,推测你可能是笔误,一般在 Vue 里与之接近的概念是 VNode
。下面为你分别解释 VNode
和虚拟 DOM
的概念:
VNode(虚拟节点)
VNode 即虚拟节点(Virtual Node),它是真实 DOM 节点的抽象表示。在 Vue 里,每个组件都会被渲染成一个 VNode 树,这个树结构代表了组件的模板结构。VNode 本质上是一个普通的 JavaScript 对象,包含了标签名、属性、子节点等信息。
虚拟 DOM(Virtual DOM)
虚拟 DOM 是一种轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。Vue 运用虚拟 DOM 技术来减少直接操作真实 DOM 带来的性能开销。具体的工作流程如下:
- 创建虚拟 DOM:Vue 会依据组件的模板和数据生成一个虚拟 DOM 树。
- 数据变更:当组件的数据发生变化时,Vue 会生成一个新的虚拟 DOM 树。
- 对比差异:Vue 会对新旧虚拟 DOM 树进行对比,找出其中的差异。
- 更新真实 DOM:根据对比结果,Vue 只更新真实 DOM 中发生变化的部分。
虚拟 DOM 的优势
- 性能优化:减少直接操作真实 DOM 的次数,从而提升性能。
- 跨平台:虚拟 DOM 是一个 JavaScript 对象,所以可以在不同的平台上使用,例如浏览器、Node.js 等。
Vue 中如何实现一个虚拟 DOM?说说你的思路
详情
在 Vue 里实现虚拟 DOM,核心思路是构建一个轻量级的 JavaScript 对象来抽象表示真实的 DOM 节点,然后通过对比新旧虚拟 DOM 树的差异,最小化对真实 DOM 的操作,以此提升性能。下面是实现虚拟 DOM 的详细步骤和思路:
1. 定义虚拟 DOM 节点类
首先,需要定义一个类来表示虚拟 DOM 节点。这个类包含标签名、属性、子节点等信息。
class VNode {
constructor(tag, props, children) {
this.tag = tag;
this.props = props || {};
this.children = children || [];
}
}
2. 创建虚拟 DOM 树
依据组件的模板和数据生成虚拟 DOM 树。可以编写一个函数来创建虚拟 DOM 节点。
function createVNode(tag, props, children) {
return new VNode(tag, props, children);
}
3. 将虚拟 DOM 转换为真实 DOM
编写一个函数,把虚拟 DOM 节点转换为真实的 DOM 节点。
function createElement(vnode) {
const { tag, props, children } = vnode;
const el = document.createElement(tag);
// 设置属性
for (const key in props) {
el.setAttribute(key, props[key]);
}
// 递归创建子节点
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(createElement(child));
}
});
return el;
}
4. 对比新旧虚拟 DOM 树
实现一个对比函数,用于比较新旧虚拟 DOM 树的差异,找出需要更新的部分。
function diff(oldVNode, newVNode) {
if (oldVNode.tag !== newVNode.tag) {
return createElement(newVNode);
}
// 对比属性
const el = createElement(oldVNode);
const newProps = newVNode.props;
const oldProps = oldVNode.props;
for (const key in newProps) {
if (newProps[key] !== oldProps[key]) {
el.setAttribute(key, newProps[key]);
}
}
for (const key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
el.removeAttribute(key);
}
}
// 对比子节点
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
const len = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < len; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
if (oldChild && newChild) {
const childEl = diff(oldChild, newChild);
el.replaceChild(childEl, el.childNodes[i]);
} else if (oldChild) {
el.removeChild(el.childNodes[i]);
} else if (newChild) {
el.appendChild(createElement(newChild));
}
}
return el;
}
5. 更新真实 DOM
最后,将对比得到的差异应用到真实 DOM 上。
function updateDOM(parentEl, oldVNode, newVNode) {
const newEl = diff(oldVNode, newVNode);
parentEl.replaceChild(newEl, parentEl.firstChild);
}
完整示例代码:
// 定义虚拟 DOM 节点类
class VNode {
constructor(tag, props, children) {
this.tag = tag;
this.props = props || {};
this.children = children || [];
}
}
// 创建虚拟 DOM 节点
function createVNode(tag, props, children) {
return new VNode(tag, props, children);
}
// 将虚拟 DOM 转换为真实 DOM
function createElement(vnode) {
const { tag, props, children } = vnode;
const el = document.createElement(tag);
// 设置属性
for (const key in props) {
el.setAttribute(key, props[key]);
}
// 递归创建子节点
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(createElement(child));
}
});
return el;
}
// 对比新旧虚拟 DOM 树
function diff(oldVNode, newVNode) {
if (oldVNode.tag !== newVNode.tag) {
return createElement(newVNode);
}
// 对比属性
const el = createElement(oldVNode);
const newProps = newVNode.props;
const oldProps = oldVNode.props;
for (const key in newProps) {
if (newProps[key] !== oldProps[key]) {
el.setAttribute(key, newProps[key]);
}
}
for (const key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
el.removeAttribute(key);
}
}
// 对比子节点
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
const len = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < len; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
if (oldChild && newChild) {
const childEl = diff(oldChild, newChild);
el.replaceChild(childEl, el.childNodes[i]);
} else if (oldChild) {
el.removeChild(el.childNodes[i]);
} else if (newChild) {
el.appendChild(createElement(newChild));
}
}
return el;
}
// 更新真实 DOM
function updateDOM(parentEl, oldVNode, newVNode) {
const newEl = diff(oldVNode, newVNode);
parentEl.replaceChild(newEl, parentEl.firstChild);
}
// 使用示例
const oldVNode = createVNode('div', { id: 'old' }, ['Old Text']);
const newVNode = createVNode('div', { id: 'new' }, ['New Text']);
const parentEl = document.getElementById('app');
const el = createElement(oldVNode);
parentEl.appendChild(el);
// 更新 DOM
updateDOM(parentEl, oldVNode, newVNode);
通过以上步骤,就可以在 Vue 中实现一个简单的虚拟 DOM 系统。不过,实际的 Vue 虚拟 DOM 实现要复杂得多,还包含了很多优化和处理边界情况的逻辑。
Vue 中操作 data 中数组的方法中哪些可以触发视图更新,哪些不可以,不可以的话有什么解决办法?
详情
在 Vue 里,对 data 中的数组进行操作时,部分方法能够触发视图更新,而有些则不能。下面为你详细介绍:
可触发视图更新的方法
这些方法被 Vue 进行了包装,在调用时会触发视图更新:
- push():在数组末尾添加一个或多个元素,并返回新的长度。
- pop():移除数组的最后一个元素,并返回该元素。
- shift():移除数组的第一个元素,并返回该元素。
- unshift():在数组开头添加一个或多个元素,并返回新的长度。
- splice():用于添加或删除数组中的元素。
- sort():对数组的元素进行排序。
- reverse():颠倒数组中元素的顺序。
不可触发视图更新的方法
直接通过索引修改数组元素或者修改数组长度时,Vue 无法检测到这些变化,也就不会触发视图更新:
- 通过索引设置元素:
this.list[index] = newValue
- 通过索引设置元素:
- 修改数组长度:
this.list.length = newLength
- 修改数组长度:
解决办法
1. 使用 splice() 方法
this.list.splice(index, 1, newValue)
可以用来替换指定索引位置的元素。
2. 使用 Vue.set()
或者 this.$set()
(Vue 2)
在 Vue 2 中,可以使用 Vue.set()
或者 this.$set()
方法来修改数组元素。
3. 使用 toRefs
和 reactive
(Vue 3)
在 Vue 3 中,可以使用 toRefs
和 reactive
来创建响应式数据,确保对数组的修改能够触发视图更新。
在Vue 实例中编写生命周期 hook 或其他 option/propertie 时,为什么不使用箭头函数?
详情
在 Vue 实例里编写生命周期钩子(lifecycle hook)或者其他选项(option)、属性(property)时,不建议使用箭头函数,主要是因为箭头函数的特性与 Vue 实例的上下文绑定机制存在冲突。下面详细阐述:
1. 箭头函数的特性
箭头函数不会创建自己的 this
,它的 this
值继承自外层函数。而在 Vue 实例中,this
指向的是 Vue 实例本身,通过 this
可以访问实例的数据、方法和属性。如果使用箭头函数,this
就会指向定义箭头函数时的上下文,而不是 Vue 实例,这会导致无法正确访问 Vue 实例的属性和方法。
箭头函数无法使用 arguments 对象
箭头函数没有自己的 arguments 对象,而在 Vue 的一些方法和钩子中,可能会使用 arguments 对象来获取传递的参数。如果使用箭头函数,就无法使用 arguments 对象,这会导致代码无法正常工作。
3. 推荐使用普通函数
为了确保 this
正确指向 Vue 实例,应该使用普通函数来定义生命周期钩子和其他选项。
is 这个特性你有用过吗?主要用在哪些方面?
详情
在 Vue 里,is 特性是一个相当实用的工具,下面为你详细介绍它的使用场景和作用:
1. 动态组件切换
借助 is
特性,你能够在不同组件间进行动态切换。这在创建选项卡界面或者多步骤表单时特别有用。
2. 解决 HTML 标签限制问题
在某些 HTML 标签里,Vue 组件无法直接使用。比如在 <table>
标签内,只能使用特定的 HTML 标签,像 <tr>
、<td>
等。这时,is 特性就能派上用场。
3. 在 v-for 循环中使用
当在 v-for
循环里动态渲染不同组件时,is
特性也能发挥作用。