前端(HTML+CSS+JS+打包+环境+网络)面试题基础八股集合——2023 - 掘金 (juejin.cn)
TypeScript面试题八股集合——2023 - 掘金 (juejin.cn)
原理:
Vue 的响应式原理?
数据驱动视图的过程:1、数据变化 2、生成vdom节点,使用diff算法比较vdom 节点的变化 3、更新视图
Vue 的响应式原理是核心是通过 Object.defindeProperty
完成的,他为所有的data的属性绑定 get
和 set
方法,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,
检测到数据的变化,会通知观察者 Wacher,重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
Vue 框架原理
vue
是一个MVVM 渐进式框架,MVVM是vue的设计模式,在vue框架中数据会自动驱动视图。
MVVM 框架的核心是数据绑定,Vue.js 通过 vue.js 中的指令实现数据双向绑定。指令中的表达式与模型中的数据绑定,一旦模型数据发生变化,指令表达式自动更新,从而实现了视图自动更新。当用户修改视图中的元素时,也会自动将修改的数据更新到模型中。
M:Model(模型),代表应用程序中业务逻辑和数据保存、检索的部分,通常与后端数据交互。在vue中指data中的数据。
V:View(视图),是应用程序的用户界面,通常由 HTML、CSS、JavaScript 组成,负责在屏幕上展示数据。在vue中指templement中的html代码片段
VM:ViewModel(视图模型),连接视图和模型之间的桥梁。Vue 采用了双向数据绑定技术(data binding),使用 ViewModel 构建并管理 View,也就是在 ViewModel 中定义 View 属性和行为,从而将 View 的状态和行为抽象成 ViewModel,使得 View 可以通过 ViewModel 进行绑定和操作,对于 View 中的数据变化可以通知 ViewModel,反之亦然。这样 View 和 Model 就可以互相独立,开发者只需要处理和调整 ViewModel 即可。 在vue中例如:在代码某个标签上绑定了某个click事件,并且触发后这个事件会对视图上的数据进行修改。
vue双向数据绑定原理?
Vue 的双向数据绑定是通过数据劫持和发布/订阅模式相结合来实现的,可以实现数据的同步更新,同时避免了手动更新视图的操作。
-
数据变化更新视图
-
视图变化更新数据。
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。(因为订阅者Watcher是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。)
接着,我们还需要有一个指令解析器Compile,替换模板数据或者绑定相应的函数(对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher),此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
- 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
vue事件绑定原理?
- DOM事件绑定
当在模板中绑定 DOM 事件时,Vue 在内部使用 addEventListener 将事件处理函数注册到真实的DOM元素上。例如: v-on:click="handleClick" 绑定click事件。
Vue 通过浏览器提供的DOM API向元素添加EventListener监听事件,当事件触发时,执行绑定的回调函数。
2.自定义事件
Vue 中可以使用 on方法监听自定义事件,emit 方法触发自定义事件。
Vue 内部通过Vue实例维护一个事件中心,通过事件中心来实现自定义事件的监听和触发。
一个组件在创建时会同时创建一个 vm 实例,vm 实例中会包含 on方法和emit 方法,在组件中你就可以通过 this 来调用这两个方法。在父组件中,通过 emit触发自定义事件,同时子组件通过on 来监听事件。
综上可知,Vue的事件绑定原理就是使用DOM事件和自定义事件两种方式,通过调用浏览器原生的DOM API和Vue内部提供的事件系统,来实现事件的触发和监听。
vue模板编译的原理?
Vue.js 是基于模板的渲染机制来实现页面的渲染的。Vue.js 通过将模板编译成渲染函数的方式来进行模板的渲染。模板编译的过程主要包括以下几个步骤:
- 解析模板字符串:Vue.js 将模板字符串解析成 AST(Abstract Syntax Tree)。
- 静态优化:在编译过程中,Vue.js 会静态地分析整个模板,检测不需要更改的部分,将其优化成静态内容,这些内容不需要在每次重新渲染时都重新生成。
- 代码生成:将 AST 转换成渲染函数。这个过程包括将每个 AST 节点转换成代码字符串并拼接成渲染函数。
- 渲染函数:将生成的渲染函数执行后,会得到一个 VNode(Virtual DOM 节点)。
这些步骤将模板编译成渲染函数,用于生成 Virtual DOM,并更新到页面中,从而实现页面的渲染。在每次有数据变化时,Vue.js 会重新调用渲染函数,生成新的 VNode,通过对比新旧 VNode 差异,从而仅仅更新需要更新的部分,提高页面的渲染效率。
描述组件渲染的过程?
Vue组件渲染的过程是将组件的模板渲染成真实的DOM节点并插入页面中的过程。它大致分为以下几个步骤:
- 创建Vue实例:在使用Vue框架时,需要创建Vue实例。Vue实例是Vue应用的入口,它将负责管理各个组件之间的数据传递和状态管理。
- 模板解析:在Vue组件中,使用HTML模板描述组件的结构和样式。当Vue实例被创建时,它会解析组件的模板,并将解析后的模板存储在内存中。Vue使用HTML解析器将模板解析为AST(抽象语法树)。
- 模板编译:在Vue实例渲染组件时,Vue将AST编译为渲染函数,渲染函数是一个返回HTML字符串的JavaScript函数。
- 组件渲染:当Vue实例需要渲染组件时,它会使用渲染函数将组件渲染成HTML字符串。
- 真实DOM生成:将HTML字符串转换成真实的DOM节点。Vue使用虚拟DOM算法,将HTML字符串转化为真实的DOM节点,Vue会尽量复用之前生成的DOM节点,以提高性能。
- 真实DOM插入:将真实的DOM节点插入到页面中。Vue将生成的真实DOM节点插入到指定的挂载点,完成组件渲染的过程。
需要注意的是,Vue会对组件进行一定的优化,包括条件渲染、属性绑定、事件处理等,以提高渲染效率和用户体验。
event-bus原理?
EventBus是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。
- on('name', fn)订阅消息,name:订阅的消息名称, fn: 订阅的消息
- emit('name', args)发布消息, name:发布的消息名称 , args:发布的消息
class Bus {
constructor () {
this.callbacks = {}
}
$on(name,fn) {
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name,args) {
if(this.callbacks[name]){
//存在遍历所有callback
this.callbacks[name].forEach(cb => cb(args))
}
}
}
创建一个全局事件总线: 挂载在Vue.prototype之上
VUEX的原理?
Vuex是通过全局注入store对象,来实现组件间的状态共享
vue.$set方法的原理?
①传入的target如果是undefined
、null
或是原始类型,则直接跑出错误。
②如果传入的是一个数组的话,就会调用数组的splice方法进行实现响应式。
③如果不是一个数组,就当做对象来处理,先判断当前key在源对象是否存在,如果存在,说明当前key已经是响应式的,就直接进行操作对应的动作。如果key不在源对象中,就调用Object.defineReactive方法将该key添加到源对象上,并且实现了响应式
Vue中scoped原理?
给HTML的DOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性。
在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器的哈希特征值(如[data-v-2311c06a])来私有化样式。
nextTick的原理:
nextTick的参数(callback),在promise.resolve.than异步的行为里面去执行
vue.mixin的原理?
使用原理: Vue.mixin的实现原理是基于Vue对组件选项的合并策略,在创建和合并组件选项时,会递归地将对象的所有属性合并到目标对象上,如果有相同名字的属性,则会进行策略性合并。当合并的对象包含生命周期事件、methods、data等选项时,这些选项会以先来后到的顺序合并到目标组件中。
在使用Vue.mixin时,我们可以通过定义一个Object,将通用的逻辑封装到这个对象中,然后通过Vue.mixin()方法全局注册这个对象,从而让所有组件可通过“继承”这个Object的方式,达到共享的目的。
需要注意的是,使用Vue.mixin并不能完全解决代码复用的问题,过度使用Vue.mixin可能会导致代码难以维护,潜在影响可读性、可维护性等。因此,应慎重使用Vue.mixin,并确保对其的合理使用。
computed实现原理
实现原理的核心在于依赖收集。当计算属性被创建时,Vue.js会通过 Object.defineProperty() 方法将其定义为getter函数,并且在getter函数中,调用其依赖的属性的getter函数。当依赖的属性发生改变时,它们会通知计算属性的setter函数,从而触发计算属性的更新。
除了依赖收集,Vue.js还提供了缓存机制,以避免非必要的计算耗时。当一个计算属性首次被访问时,它的值会被计算,并缓存下来。只有当依赖的属性发生改变时,计算属性才会重新计算,否则直接返回缓存的值。
diff算法原理(重点)
是vdom中最核心、最关键的部分。Diff算法是虚拟DOM(Virtual DOM)的核心算法,用于比较新旧虚拟DOM树,找出变化的部分,最终只更新变化的部分,以达到优化渲染性能的目的。
宗旨:只比较同一层级, 时间复杂度O(N),深度优先
步骤:
patch方法:对比当前同层的虚拟节点是否为同一种类型的标签
是:继续执行
patchVnode方法
进行更深比对否:没必要比对了,直接整个节点替换成
新虚拟节点
function sameVnode(oldVnode, newVnode) { return ( oldVnode.key === newVnode.key && // key值是否一样 oldVnode.tagName === newVnode.tagName && // 标签名是否一样 oldVnode.isComment === newVnode.isComment && // 是否都为注释节点 isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同 ) }
patchVnode方法:
- 找到对应的
真实DOM
,称为el
- 判断
newVnode
和oldVnode
是否指向同一个对象,如果是,那么直接return
- 如果他们都有文本节点并且不相等,那么将
el
的文本节点设置为newVnode
的文本节点。- 如果
oldVnode
有子节点而newVnode
没有,则删除el
的子节点- 如果
oldVnode
没有子节点而newVnode
有,则将newVnode
的子节点真实化之后添加到el
- 如果两者都有子节点,则执行
updateChildren
函数比较子节点,这一步很重要updateChildren方法: 新旧虚拟节点的子节点对比
首尾指针法
,新的子节点集合和旧的子节点集合然后会进行互相进行比较,总共有五种比较情况:
- 1、
oldS 和 newS
使用sameVnode方法
进行比较,sameVnode(oldS, newS)
- 2、
oldS 和 newE
使用sameVnode方法
进行比较,sameVnode(oldS, newE)
- 3、
oldE 和 newS
使用sameVnode方法
进行比较,sameVnode(oldE, newS)
- 4、
oldE 和 newE
使用sameVnode方法
进行比较,sameVnode(oldE, newE)
- 5、如果以上逻辑都匹配不到,再把所有旧子节点的
key
做一个映射到旧节点下标的key -> index
表,然后用新vnode
的key
去找出在旧节点中可以复用的位置。
日常应用场景:循环中key的使用
基础:
为什么Vue中的v-if和v-for不建议一起用?
永远不要把 v-if 和 v-for 同时用在同一个元素上。
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级(vue2),这意味着 v-if 将分别重复运行于每个 v-for 循环中,一起使用会造成性能浪费
- 解决方案有两种,把v-if放在v-for的外层或者把需要v-for的属性先从计算属性中过滤一次
vue图片懒加载?
$ npm i vue-lazyload -D
-
main.js 在入口文件** import VueLazyload from 'vue-lazyload' //引入这个懒加载插件
-
在入口文件添加后,在组件任何地方都可以直接使用把 img 里的:src -> v-lazy**
v-show和v-if的区别
v-show:通过CSS的display:none控制显示和隐藏(频繁切换时候)
v-if:组件进行真正的渲染和销毁,删除DOM节点。
补充问:为什么v-show用的不是visibility:hidden 隐藏元素?
因为visibility: hidden
可以隐藏元素但保留其空间,而在某些情况下,这并不是所需的效果。
在页面中不占有位置和删除DOM节点是有很大的区别的。
如果一个元素被删除了,那么它所占据的位置也会随之消失,可以让后面的元素顶上来,相当于从DOM树中移除了该元素。而如果一个元素不占有位置,可能是因为它被隐藏了,但它仍然存在于DOM树中,占据了一定的空间。这意味着即使被隐藏的元素不可见,但是它依然可以影响到相邻元素的排布,也可能会影响到整个页面的布局。
因此,删除DOM元素会对页面布局产生影响,而隐藏元素不会对布局产生影响。在实际的应用场景中,删除DOM元素通常用于释放一些无用的内存空间,而隐藏元素则用于在必要的时候暂时隐藏某个元素,等到需要重新显示的时候再将其显示出来。
为何v-for中要使用key
- 必须使用key,不能是index和random,因为diff算法中通过tag和key来判断是否为相同节点。 匹配了key,则只移动元素,性能较好,未匹配key,则删除重建,性能较差。
- 这样可以减少渲染次数,提升渲染性能
scoped导致的问题?
父组件无scoped
属性,子组件带有scoped
,父组件是无法操作子组件的样式的(原因在原理中可知),虽然我们可以在全局中通过该类标签的标签选择器设置样式,但会影响到其他组件
父组件有scoped
属性,子组件无,父组件也无法设置子组件样式,因为父组件的所有标签都会带有data-v-469af010唯一标志,但子组件不会带有这个唯一标志属性,与1同理,虽然我们可以在全局中通过该类标签的标签选择器设置样式,但会影响到其他组件
父子组建都有,同理也无法设置样式,更改起来增加代码量
循环问:vue什么时候操作DOM比较合适
mounted和updated都不能保证子组件全部挂载完成,所有使用$nextTick的时机进行操作DOM。
nextTick的使用和原理?
功能:可以获取到更新后的DOM,nextTick返回一个Promise,是一个 异步行为。
因为vue采用的是异步更新策略,数据发生变化,DOM节点并不会立刻发生变化,而是开启一个队列,把组件更新函数保存在队列中,同一个事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不能立刻的体现在DOM上,此时如果我们想获取更新后的DOM状态,就要使用nextTick。在开发时,一般两个场景用:
1、created中想要获取DOM时;
2、响应式数据变化后获取DOM更新后的状态
Vue组件如何通讯
-
父子组件:props和this.$emit
-
refs.属性名称 调用子组件事件,
-
parent.方法名() 调用父组件事件
-
自定义事件(总线通讯):开发者自己定义的事件,与浏览器自带的事件(如click、load、scroll等)不同。通常用于在应用程序不同部分之间的通信。
在JavaScript中,可以使用Event来创建自定义事件。用event.emit/event.on/event.off分别进行事件触发、监听和清除。
-
vuex:可全局组件通讯
-
$attrs:透传 attribute指的是传递给一个组件,却没有被该组件声明为 props或 emits的 attribute 或者
v-on
事件监听器。可以实现上下级组件的透传,但是依赖于v-bind = "attrs"进行一层一层的传递。 -
provide/inject:用于上下级组件的透传。下面是响应式数据
父组件:
子组件:
描述组件渲染和更新的过程
给vue中所有data对象的每个属性分别定义了getter和setter,然后通过getter收集依赖,依赖是指每个数据属性的对应关系。使用setter对数据的赋值和修改进行拦截,然后vue就知道了数据的变化,然后就只会通知vue中的watcher,进行数据和页面的更改。
Vuex中action和mutation有什么区别?
- mutation是原子操作,必须同步代码
- action可包含多个mutation;可包含 异步代码
computed有什么特点,与watch有什么区别?
computed有缓存,data不变化时,不重新计算。可以提高性能。而method中的方法没有缓存
区别
computed用于计算产生新的数据
watch用于监听现有数据
为什么组件data必须是函数?
因为定义的Vue文件是一个类,class文件,每个地方使用这个资源 的时候,相当于是对类的实例化。如果data不是函数,则每个组件的实例数据都会共享。
Ajax请求应该放在哪个生命周期?
mounted中,因为JS是单线程的,Ajax异步获取数据。只要JS没有渲染完,异步数据得到了也是在异步的排队过程中,不会有什么效果。
如何将组建所有的props传递给子组件?
$props <子组件 v-bind=“$props”/>
多个组件有相同逻辑,如何抽离?
mixin
vue.mixin的使用场景
使用场景: Vue.mixin是一种混入(mix-in)技术,可以在多个组件之间共享组件选项,包括生命周期方法、data选项等等,使用Vue.mixin可以将一些通用的功能或逻辑封装起来,让多个组件可共同调用这些相同的逻辑,避免了冗余代码的出现。
在使用Vue.mixin时需要注意以下几点:
- 全局注册的Mixin会影响到所有Vue组件,包括第三方组件,因此需要确保Mixin的逻辑可适用于所有组件,不会产生意想不到的副作用。
- Mixin可能会覆盖组件本身的选项,因此需要仔细考虑Mixin的选项名称和组件本身选项名称的冲突。如果两者有重复,则Mixin的选项会覆盖组件本来的选项。一般来说,建议Mixin的选项命名要以特殊前缀开头以避免冲突。
- Mixin中的函数和数据,都是共享的,因此需要注意变量名和数据结构的一致性,避免出现数据混乱的情况。同时也需要注意数据结构的引用和拷贝,如果Mixin中的数据是对象或数组,需要避免直接修改这些数据的引用,推荐使用Object.freeze()或类似方法锁定数据对象。
- 注意在组件中使用parent或root来访问父组件或根组件的数据,因为Mixin在多个组件中都有使用,这些数据实际上是共享的,可能会导致数据不一致性。
- 不要在Mixin中使用箭头函数,因为在Mixin中this指向的是Vue实例,而箭头函数中的this指向的是全局对象。
总之,在使用Vue.mixin时需要确保Mixin本身的合理性,合理考虑Mixin与组件本身选项的交互和数据传递,以避免意外情况的出现。同时要保持代码的可维护性和可读性,减少不必要的代码迁移和重构。
何时使用keep-alive?
①缓存组件,不用重复渲染的时候。 如,前端多个静态tab页面切换。可以优化性能
何时需要使用beforeDestory?
①解绑自定义事件 ②清除定时器 ③解绑自定义DOM事件
什么是作用域插槽?
带数据的插槽,即让插槽内容能够访问子组件中才有的数据。
v-model自定义实现
我们可以通过分别绑定value属性和input事件来实现自定义的v-model功能:
<template>
<div>
<counter :value="count" @input="count = $event"></counter>
<p>Count: {{ count }}</p>
</div>
</template>
<script>
import Counter from './Counter';
export default {
components: {
Counter
},
data() {
return {
count: 0
}
}
}
</script>
在上述代码中,我们使用自定义计数器组件,并将count属性绑定到计数器组件的value属性上,通过@input事件监听计数器组件的值的变化,并将变化的值赋给父组件的count属性,从而实现了自定义v-model的功能。 不用:value和@input两个事件名字时:
vue3:
单页应用和多页应用的优缺点?
单页应用和多页应用是两种常见的web应用程序的架构设计方案。它们各自有着自己的优缺点,适用于不同的场景。
单页应用(Single Page Application,SPA):
优点:
- 用户体验好,局部刷新,不需要页面刷新,加载速度快,提升用户满意度。
- 可以提供类似原生应用的用户体验,例如前后端分离实现的页面无刷新化跳转,多级下拉菜单等。
- 前后端分离,API和UI开发分离,可以提高开发效率,工作分工更清晰。
缺点:
- 首次加载时间长,因为要加载JavaScript/CSS文件等资源,可能会影响SEO优化。
- 复杂性高,需要前端框架进行实现,不利于初学者上手,调试以及维护也相应复杂。
- 安全性要求高,容易受到跨站点脚本攻击,需要更高的安全措施。
多页应用(Multiple Page Application,MPA):
优点:
- 早期开发方式,对SEO优化友好,每个页面都有自己的URL地址。
- 容易上手,不需要掌握很多特殊技能,语言不限。
- 页面之间互相独立,不容易出现问题,容错率高。
缺点:
- 用户体验比较差,每次页面切换都需要重新加载页面,用户体验不佳。
- 开发效率低,每个页面都需要独立地开发和维护,工作量大。
- 需要后端参与渲染页面,前后端耦合程度高。
综上所述,单页应用适用于对用户体验有较高要求的场景,例如实时应用、互动游戏等。而多页应用适合于需要SEO的场景,如电商网站、新闻资讯等。
vuex为什么要分模块并加命名空间?
Vuex 分模块并加命名空间的主要目的是为了更好地管理应用的状态。在一个大型的 Vuex 应用中,如果所有的状态都集中在一个 store 中,会导致代码难以维护和扩展,因此需要将状态进行模块化管理,以优化应用的设计结构,并更好地组织和管理组件的状态。
同时,Vuex 命名空间的设计,则是为了解决模块之间命名冲突的问题。在一个大型的 Vuex 应用中,可能会存在多个模块都定义了同名的 state、mutation、action、getter,这时如果不加命名空间,容易出现命名冲突的问题,导致出现不可预期的错误。因此,Vuex 引入了命名空间的概念,可以保证每个模块具有独立的命名空间,避免了命名冲突,方便进行模块之间的数据通信和状态管理。
总之,Vuex 分模块并加命名空间可以让我们更好地管理和维护应用状态,并且可以更好地定义和隔离模块之间的状态和行为,使得应用的代码更易于组织和扩展。
vue SSR?
Vue SSR(Server-Side Rendering 服务端渲染)是一种将 Vue 应用程序渲染为 HTML 的技术,其核心思想是在服务器端渲染 Vue 应用程序组件并将其输出为 HTML,然后将 HTML 发送回浏览器进行渲染,从而提高 Vue 应用程序的性能和SEO表现。
Vue SSR 主要解决了两个问题:
- 首屏渲染:当用户第一次访问应用时,由于需要下载并执行 JS 文件,Vue SPA(Single Page Application 单页应用)通常存在白屏时间,给用户带来不良体验。而使用 SSR 技术在服务器端就可以渲染组件并生成相应的 HTML,最终将完整的 HTML 响应给浏览器,提高首屏渲染效率。
- SEO优化:在单页应用中,搜索引擎爬虫不能够直接读取到页面内容,这对于SEO(Search Engine Optimization 搜索引擎优化)十分不利。而使用 SSR 技术可以生成静态 HTML,方便搜索引擎抓取并提升 SEO 的效率。
Vue SSR 的原理是将 Vue 组件在服务器端渲染成 HTML,并通过浏览器请求这些静态 HTML 文件,跳过了客户端渲染的过程, 从而使用户能够更快地看到应用程序内容。具体实现可以分为以下几个步骤:
- 获取请求的路由信息。
- 根据路由信息创建一个 Vue 实例。
- 调用 Vue 实例的 renderToString 方法,将组件渲染成 HTML 字符串。
- 将渲染出来的 HTML 字符串返回给浏览器。
- 浏览器解析 HTML、加载 JS 文件再进行客户端渲染。
需要注意的是,由于 SSR 需要在服务器上构建完整的应用程序,因此需要在服务器端运行一个 Node.js 框架来处理请求和构建 Vue 实例,并通过 Webpack 可以使 SSR 应用开发和部署更加方便。同时,很多浏览器 API 只能在浏览器环境中运行(例如:window 和 document 等对象),在服务器端无法使用,因此需要考虑这些因素,对组件的代码进行适当的修改和处理。
vue修饰符有哪些?
Vue修饰符是 Vue 提供的一种特殊语法,用于在指令后面添加一些特殊前缀,来表达指令的特殊含义。Vue 修饰符主要被用于指令的模板用法中,可以用来改变指令的行为方式,包括以下几个:
.prevent
: 阻止默认事件的发生。.stop
: 阻止事件冒泡,即不再向上层元素传播事件。.capture
: 添加事件监听器时使用事件捕获模式。.self
: 只触发当前元素上的事件,不会触发它的子元素。.once
: 只触发一次事件,即使绑定了多个相同的事件监听器。.passive
: 指示浏览器不需要在执行操作时通知 Vue 是否阻止了事件的默认行为,可以提高页面的滚动性能。
除了上述常见的修饰符,Vue 还提供了一些用于键盘事件的快捷键修饰符,例如:
.keyCode
: 只在特定的按键按下时才触发事件。.enter
: 按下回车键时触发事件。.tab
: 按下 Tab 键时触发事件。.delete
: 按下删除键时触发事件。
还有一些其他的键盘快捷键修饰符,通过这些修饰符可以方便地绑定和处理各种键盘事件。
前端路由的实现原理
通过监听URL的变化,然后根据不同的URL路径显示不同的页面内容,而不是通过向服务器请求不同的HTML页面来实现页面切换。
hash 模式:
在URL中添加一个
#
符号,后面跟上对应的路径,例如http://example.com/#/ path/to/page
。浏览器不会重新加载页面,而是通过监听hashchange
事件来切换页面内容。可以使用window.location.hash
来获取当前的URL路径,也可以通过设置window.location.hash
来改变URL路径。可以对浏览历史记录栈进行修改,以及popState事件的监听到状态的更改。
history模式:
通过HTML5提供的
history
API来实现路由切换。可以使用pushState
方法或replaceState
方法来修改URL路径,然后通过监听popstate
事件来响应URL的变化。例如history.pushState({}, '', '/path/to/page')
。需要注意的是,使用History模式需要在服务器端进行配置,将所有的URL请求都重定向到主页面,避免在刷新或直接访问URL时出现404错误。
请用vnode描述一个DOM结构:
监听data变化的核心API是什么?
object.defineProperty
缺点:无法监听数组变化,无法监听对象的添加删除,深度监听,需要递归到底,一次性计算量大
Vue为何是异步渲染,$nextTick有何用?
Vue是异步渲染的原因主要是为了提高性能和用户体验。当组件中的数据发生变化时,Vue会根据新的数据重新生成虚拟DOM,并将新旧虚拟DOM进行比较,找出差异部分进行更新。由于比较虚拟DOM是一项计算密集型的操作,如果每次数据变化都同步更新虚拟DOM和页面,会导致页面性能下降,用户体验变差,尤其是在大规模数据变化时,会造成页面卡顿或甚至崩溃。
在Vue中,异步渲染的机制主要是通过nextTick
方法来实现的,nextTick
方法会将回调函数推迟到下一个事件循环中执行,从而实现异步渲染的效果。微任务
router的区别?
在Vue.js中,router都是与路由相关的对象,但是它们扮演的角色不同,有以下区别:
- $route对象是当前路由跳转的对象,它包含了当前路由的信息,例如当前路由路径、参数、查询参数等。
- router对象监听路由变化或进行编程式导航。
简单来说,router主要为了管理路由,跳转到不同的路由,或者对路由做一些其它的操作。
Vue-router有几种模式?
①history:
history 模式使用 HTML5 的 History API 来实现路由管理,该模式会将路由信息放在浏览器的历史记录中,即 URL 中的路径部分不包含 #
符号,而是正常的 URL 格式 /path/to/route
。在这种模式下,当用户访问不同的路由时,URL 发生的变化会触发浏览器整页刷新,但可以通过设置服务器端配置来避免刷新问题。
②hash:
默认情况下,Vue-router 使用的是 hash 模式,在 URL 中使用 #
符号作为路由的标识,即 URL 中的路径部分是 /#/path/to/route
的形式。在这种模式下,当用户访问不同的路由时,URL 发生的变化都只会影响 #
符号后面的部分,而不会触发浏览器的整页刷新,因此可以实现单页应用(SPA)。
③abstract: abstract 模式不依赖于浏览器的历史记录或者 URL,而是将路由信息都保存在 JavaScript 对象中。这种模式主要用于测试或者非浏览器环境下的应用程序,例如 Electron 等。
Vue中如何扩展一个组件?
1、内容扩展:slot
2、逻辑扩展:mixins,extends,composition api
子组件可以修改父组件中的数据吗?
子组件确实可以修改父组件中的数据,但是并不推荐这样做,官方开发文档中有一个单项数据流的原则
Vue中权限管理怎么做?
一般分为:按钮权限和页面权限;也分为前端配置和后端配置:
页面权限
前端配置:在Vue中,可以使用路由拦截器实现权限控制。通过路由拦截器,可以在页面跳转之前判断用户是否具有访问权限。 例如:
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/Home.vue'),
meta: {
requiresAuth: true, // 需要登录才能访问
requiresAdmin: true // 需要管理员权限才能访问
}
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue')
},
{
path: '/about',
name: 'about',
component: () => import('@/views/About.vue'),
meta: {
requiresAuth: true // 需要登录才能访问
}
}
]
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 判断是否需要认证
if (!store.getters.isAuthenticated) {
// 如果用户未登录,跳转至登录页面
next('/login')
} else {
// 判断是否需要管理员权限
if (to.matched.some(record => record.meta.requiresAdmin)) {
if (store.getters.isAdmin) {
next()
} else {
// 如果用户不是管理员,跳转至无权限页面
next('/no-permission')
}
} else {
next()
}
}
} else {
next()
}
})
export default router
后端配置:会把所有页面路由信息存在数据库中,用户登录时,根据其角色查询可以得到他能访问的所有页面路由信息,然后返回给前端,前端通过addRoutes动态添加路由信息。
按钮权限
通常会实现一个组件指令。如v-permission,将按钮要求角色通过传给v-permission指令,在指令的moutend钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除。
生命周期与钩子函数:
描述vue组件生命周期(有父子组件的情况呢),每个生命周期都做了什么?
1、创建:beforecreate created beforemount mounted
2、更新:beforeupdate updated
3、销毁:beforedestroyed destroyed
父子组件:挂载:由父------->子;渲染:由子------->夫
vue父子组件生命周期器钩子函数顺序问题
- 父组件 beforeCreate 钩子函数
- 父组件 created 钩子函数
- 父组件 beforeMount 钩子函数
- 子组件 beforeCreate 钩子函数
- 子组件 created 钩子函数
- 子组件 beforeMount 钩子函数
- 子组件 mounted 钩子函数
- 父组件 mounted 钩子函数
做了什么:
beforecreate:创建一个空白vue实例,data和method尚未被初始化,不可使用
created:Vue实例初始化完成,完成响应式绑定data和method被初始化,未开始渲染模板
beforemount:编译模板,调用render生成vdom,未开始渲染DOM
mounted:完成渲染DOM,组件渲染完成,由“创建阶段”进入“运行阶段”
beforeupdate:data发生变化之后,但是尚未更新DOM
updated:data发生变化,并且完成DOM更新(不能在这一步修改data,否则会导致死循环)
beforedestroyed:组件进入销毁阶段,但是尚未进行销毁,任然可使用,可以用来接触一些自定义的事件,定时器自定义DOM节点等
destroyed:组件被销毁,所有子组件都被销毁
vue-router 的几种钩子函数?
在 Vue.js 中,Vue-Router 是官方推荐的路由管理器,主要用于通过 URL 地址来匹配对应的组件并将其渲染到页面中。Vue-Router 提供了一些生命周期钩子函数,可以在特定的时刻执行一些操作,常用的钩子函数有以下几种:
- beforeRouteEnter: 这个钩子函数在路由进入前被调用,但这个时候该组件实例还没有被创建,所以无法直接访问组件实例,因此只能通过一个回调函数来访问到组件实例。
- beforeRouteUpdate: 这个钩子函数在路由更新前被调用,因为路由更新可能会引起一些组件内部状态的变化,因此这个钩子函数可以用来处理这些状态变化。
- beforeRouteLeave: 这个钩子函数在路由离开前被调用,主要作用是在组件离开前询问用户是否要保存未保存的数据,或者是在离开前做一些清理操作。
- afterEach: 这个钩子函数在路由进入后被调用,可以用来添加页面访问统计、错误处理等公共逻辑。
- beforeResolve: 这个钩子函数会在路由解析执行前被调用,可以在此修改或筛选后续要执行的路由。
这些钩子函数可以方便地对路由进行处理,帮助我们更好地管理页面的状态。
优化:
vue常见的性能优化
①合理使用v-show和v-if
②v-for加key,以及避免和v-if同时使用
③自定义事件、DOM事件及时销毁
④合理使用异步组件
⑤合理使用Keep-live
⑥前端通用优化,图片懒加载
如何优化SPA首屏加载速度慢的问题?
优化SPA首屏加载速度慢的问题可以从以下几个方面入手:
-
代码压缩与打包:使用Webpack等构建工具进行代码压缩,合并和打包,减少代码体积,提高加载速度。
-
图片优化:使用合适的图片格式,采用lazy load和懒加载等方式,减少页面图片加载带来的时间成本。
-
异步加载组件和数据:采用异步组件和Data Fetching,可以延迟一些用户不紧急需要的组件和数据的加载时间,从而加快首屏加载速度。
-
缓存页面和数据:在浏览器端实现缓存机制,减少重复请求和加载,提升页面渲染速度。
-
启用Gzip压缩:将服务器上的静态资源采用Gzip压缩后再进行传输,减少网络带宽占用,提升加载速度。
-
代码懒加载:使用codesplitting的技术,根据需要进行懒加载,减少页面加载时的资源开销。
-
采用服务器渲染:对于复杂的SPA应用,为了提高首屏渲染速度,可以采用服务器渲染的方式,通过提前将页面渲染出来返回给浏览器,减少浏览器端的渲染时间和开销。
通过以上方式的综合操作,可以极大地提升SPA应用的首屏加载速度。
Vdom真的很快吗?
vdom并不快,JS直接操作DOM才是最快的,但是“数据驱动视图”要有合适的技术方案,不能全部DOM重建,vdom就是目前最合适的技术方案。
Vue如何实现动态组件和异步组件?
动态组件:
动态组件
:是Vue中一个特殊的Html元素:<component>
,它拥有一个特殊的 is
属性,属性值可以是 已注册组件的名称
或 一个组件的选项对象
,它是用于不同组件之间进行动态切换的。
使用标签和is属性可以实现动态组件,可以在同一位置动态切换不同的组件。具体使用方法如下:
<component :is="componentName"></component>
其中,componentName是一个变量,它的值可以是一个组件的名称,例如:
data() { return { componentName: 'ComponentA' } }
这样,就会渲染ComponentA组件。
异步组件:
异步组件
:简单来说是一个概念,一个可以让组件异步加载的方式;它一般会用于性能优化,比如减小首屏加载时间、加载资源大小。
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
何时使用异步组件?
①加载大组件 ②路由异步加载
vue中 动态组件和keep-alive的区别?
相同:Vue中动态组件和<keep-alive>
都是用来优化组件性能的。
动态组件可以根据不同的条件以不同的组件形式来渲染同一个组件位置,它使得多个组件之间实现了灵活的切换。动态组件的优点是灵活,缺点是在不同组件之间频繁切换时,会导致组件的销毁和重建,使得页面状态丢失。动态组件不会默认缓存已经被销毁的组件。
而<keep-alive>
的作用是缓存已经渲染的组件,来避免重复渲染已经存在的组件,优化性能。通过使用<keep-alive>
可以使组件在切换时保持状态,不会因为被销毁而导致页面状态丢失。
<keep-alive>
有两个重要的属性:
include
:被缓存的组件名称列表,只有该列表中的组件会被缓存。exclude
:不被缓存的组件名称列表,该列表中的组件不会被缓存。
使用<keep-alive>
缓存组件时,需要注意,被缓存的组件在缓存期间是不会被销毁的,因此如果组件占用的资源比较多,缓存过多的组件会导致内存占用过高,从而影响网页的性能。因此在使用<keep-alive>
时,需要合理地配置include
和exclude
属性,以减小缓存的开销。
如何减少DOM操作?
- 减少读取DOM元素的次数:每次访问DOM都会产生一定的代价,尽量将元素的引用缓存起来,避免重复访问相同的DOM元素。
- 批处理DOM元素操作:尽量将多个DOM操作合并成一次操作。例如,当需要修改一个列表的多个元素时,可以先将它们缓存起来,待操作完成后再进行一次性修改。
- 使用事件委托:通过将事件绑定在父元素上,利用事件冒泡机制让父元素处理子元素的事件,可以减少事件绑定次数,提高页面性能。
- 使用文档碎片:将需要操作的多个DOM节点先添加到文档碎片中,完成所有的操作后再将文档碎片一次性添加到页面中,可以减少对页面的多次渲染。
- 避免频繁的DOM操作:频繁的DOM操作会导致页面不断地渲染和重排,影响页面性能和用户体验,尽量将DOM操作封装在一起,在完成一定的操作后再进行渲染。
一次性给你大量的dom怎么优化?
- 懒加载:只在需要时才加载DOM元素,而不是一次性将它们全部加载出来。
- 分批加载:将大量DOM元素按照一定的规则进行分组,逐个进行加载,避免一次性加载所有DOM元素造成堵塞现象。
- 虚拟滚动:只渲染屏幕内可见的DOM元素,滚动时实时更新渲染,避免一次性渲染大量DOM元素。
- 使用列表组件:对于重复的、相似的DOM元素,可以使用列表组件如Vue的v-for来进行渲染,避免大量手工操作DOM元素。
vue如何兼容IE?
Vue.js 在默认情况下不支持 IE8 及其以下版本的浏览器,但是它可以通过一些方法来进行兼容:
- 在 HTML 文件的头部添加 meta 标签:
<meta http-equiv="X-UA-Compatible" content="IE=edge">
这样可以让 IE 浏览器使用最新的渲染模式来渲染页面。
- 启用 babel-polyfill:
在 Vue 项目中安装 babel-polyfill,它可以在代码中自动加入一些特性的 shim 和 polyfills,从而支持更广泛的浏览器。例如:
npm install babel-polyfill --save-dev
在 src/main.js
文件中引入:
import 'babel-polyfill';
- 使用 es5 版本的 Vue:
Vue 2.0 以上版本默认使用了 ES2015+ 的语法,如果要在 IE8 中运行,则需要使用 es5 版本的 Vue,可以通过在业务入口处,比如 main.js 中手动安装并指定 es5 版本的 Vue:
import Vue from 'vue/dist/vue.esm.js';
- 使用 Vue-cli 中的配置:
如果是使用 Vue-cli 工具创建的项目,可以通过修改 .babelrc
文件来配置,例如:
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "ie >= 8"]
}
}]
]
}
这个配置指定编译目标浏览器为 "last 2 versions" 和 "IE 8 及以上",可以实现对 IE8 的兼容。
总的来说,针对 IE 的兼容问题,可以结合不同的方法进行处理,具体方法可以根据项目需求来选择。