前端Vue.js面试题(2)
✨✨✨目录
1.为什么Vue中的data属性是一个函数而不是一个对象?
2.谈谈你对Vue中keep-alive的理解?
3.Vue中的$nextTick原理及作用?
4.Vue单页面应用与多页面应用的区别?
5.Vue是怎么把template模版编译成render函数的?
6.说说你对Vue的mixin的理解,以及有哪些应用场景?
7.自定义指令是什么?有哪些应用场景?
8.Vue是如何收集依赖的?
9.React和Vue的异同?
10.Vue的优点?
11.assets和static的区别?
12.delete和Vue.delete删除数组的区别?
13.对SSR的理解?
14.Vue的性能优化有哪些?
15.template和jsx的有什么分别?
1.为什么Vue中的data属性是一个函数而不是一个对象?
根实例对象data
可以是对象也可以是函数(根实例是单例),不会产生数据污染情况。
组件实例对象data
必须为函数,目的是为了防止多个组件实例对象之间共用一个data
,产生数据污染。采用函数的形式,initData
时会将其作为工厂函数都会返回全新data
对象。
2.谈谈你对Vue中keep-alive的理解?
keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,避免重新渲染,也就是组件缓存。
(1)keep-alive属性
include 包含的意思。值为字符串或正则表达式或数组。只有组件的名称与include的值相同的才会被缓存,即指定哪些被缓存,可以指定多个被缓存。这里以字符串为例,指定多个组件缓存,语法是用逗号隔开。如下:
// 指定home组件和about组件被缓存
<keep-alive include="home,about" ><router-view></router-view>
</keep-alive>
exclude相当于include的反义词,就是除了的意思,指定哪些组件不被缓存,用法和include类似,如下:
// 除了home组件和about组件别的都缓存
<keep-alive exclude="home,about" ><router-view></router-view>
</keep-alive>
(2)使用keep-alive的钩子函数执行顺序问题
首先使用了keep-alive的组件以后,组件上就会自动加上了activated
钩子和deactivated
钩子。
activated
当组件被激活(使用)的时候触发 可以简单理解为进入这个页面的时候触发deactivated
当组件不被使用(inactive状态)的时候触发 可以简单理解为离开这个页面的时候触发
缓存一个组件,在钩子中打印出对应的顺序。如下:
初始进入和离开 created ---> mounted ---> activated --> deactivated
后续进入和离开 activated ---> deactivated
(3)keep-alive使用场景
查看表格某项数据详情页,返回后要保持之前的筛选条件或页数等。
填写的表单的内容路由跳转返回还在,比如input框、下选择拉框、开关切换等用户输入了一大把东西,跳转再回来不能清空,不用让用户再写一遍。
3.Vue中的$nextTick原理及作用?
官方定义:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
我们可以理解成,Vue在更新 DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新。
所以,在以下情况会用到nextTick:
- 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中。
- 在vue生命周期中,如果在created钩子进行DOM操作,也一定要放在nextTick()的回调函数中。因为在created钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。
原理:Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用;
nextTick核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法来模拟对应的微/宏任务的实现,根据当前环境支持什么方法则确定调用哪个。
4.Vue单页面应用与多页面应用的区别?
(1)概念
SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。
MPA多页面应用(MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。
(2)区别
5.Vue是怎么把template模版编译成render函数的?
Vue的编译过程将模板转换为渲染函数,这是Vue在运行时动态编译和渲染组件的关键步骤。下面是Vue将模板编译成渲染函数的大致过程:
解析模板:首先,Vue会解析模板字符串,将其转化为抽象语法树(AST)。AST是一个表示模板结构和内容的树状数据结构;
优化AST:接下来Vue会对AST进行优化处理,以提升渲染性能。这包括标记静态节点、静态属性和静态文本等;
生成渲染函数:利用优化后的AST,Vue会生成渲染函数。渲染函数是一个JavaScript函数,它接收一个上下文对象作为参数,并返回一个虚拟DOM树(VNode);
渲染虚拟DOM:当执行渲染函数时,它将生成一个新的虚拟DOM树。如果之前已经存在真实的DOM树,Vue将通过比较新旧VNode来计算最小的更新操作并应用在真实DOM上,从而进行局部更新,提高效率;
生成DOM:最后,Vue将根据最新的VNode生成真实的DOM元素,并将其插入到页面中,完成渲染。
Vue的编译过程通常在构建时(比如使用Vue CLI)或运行时的初始阶段完成,以便在实际渲染组件时获得更好的性能。这样一来渲染函数会被缓存并重复使用,而不需要每次重新编译模板。
Vue还可以使用render
函数直接编写组件而不依赖于模板。这种情况下,手动编写的render
函数会跳过模板解析和优化的步骤,直接生成渲染函数并进行渲染。这种方式可以在需要更高级别的动态和灵活性时使用。
6.说说你对Vue的mixin的理解,以及有哪些应用场景?
Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin
类的方法而不必成为其子类。Mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂。
Vue中的mixin(混入),提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。
本质其实就是一个js
对象,它可以包含我们组件中任意功能选项,如data
、components
、methods
、created
、computed
等等。在Vue
中Mixin可以局部混入跟全局混入。
(1)局部混入
定义一个mixin
对象,有组件options
的data
、methods
属性
var myMixin = {created: function () {this.hello()},methods: {hello: function () {console.log('hello from mixin!')}}
}
组件通过mixins
属性调用mixin
对象
Vue.component('componentA',{mixins: [myMixin]
})
该组件在使用的时候,混合了mixin
里面的方法,在自动执行create
生命钩子,执行hello
方法
(2)全局混入
通过Vue.mixin()
进行全局的混入
Vue.mixin({created: function () {console.log("全局混入")}
})
使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)
PS:全局混入常用于插件的编写
注意事项:
当组件存在与mixin
对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin
的选项。
但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin
的钩子,再执行组件的钩子。
使用场景:
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立。这时,可以通过Vue
的mixin
功能将相同或者相似的代码提出来
举个例子
定义一个modal
弹窗组件,内部通过isShowing
来控制显示
const Modal = {template: '#modal',data() {return {isShowing: false}},methods: {toggleShow() {this.isShowing = !this.isShowing;}}
}
定义一个tooltip
提示框,内部通过isShowing
来控制显示
const Tooltip = {template: '#tooltip',data() {return {isShowing: false}},methods: {toggleShow() {this.isShowing = !this.isShowing;}}
}
通过观察上面两个组件,发现两者的逻辑是相同,代码控制显示也是相同的,这时候mixin
就派上用场了。首先抽出共同代码,编写一个mixin
const toggle = {data() {return {isShowing: false}},methods: {toggleShow() {this.isShowing = !this.isShowing;}}
}
两个组件在使用上,只需要引入mixin
const Modal = {template: '#modal',mixins: [toggle]
};const Tooltip = {template: '#tooltip',mixins: [toggle]
}
通过上面小小的例子,知道了Mixin
对于封装一些可复用的功能如此有趣、方便、实用
7.自定义指令是什么?有哪些应用场景?
在Vue中,自定义指令(Custom Directive)是一种用于扩展 Vue 的模板语法的机制。通过自定义指令,你可以在 DOM 元素上添加自定义行为,并在元素插入、更新和移除时进行相应的操作。
自定义指令由 Vue.directive 函数定义,它接收两个参数:指令名称和指令选项对象。指令选项对象包含一系列钩子函数,用于定义指令的行为。
以下是一些常见的自定义指令应用场景:
操作DOM:自定义指令可以用于直接操作 DOM 元素,例如修改元素的样式、属性、事件绑定等。你可以通过在指令的钩子函数中访问和操作 DOM 元素。
表单验证:你可以创建自定义指令来实现表单验证逻辑。通过自定义指令,你可以监听输入框的值变化,并根据自定义的验证规则进行验证,以便提供实时的反馈。
权限控制:自定义指令可以用于权限控制场景,例如根据用户权限来隐藏或禁用某些元素。你可以在自定义指令中根据用户权限进行条件判断,并修改元素的显示或行为。
第三方库集成:当你需要在 Vue 中使用第三方库或插件时,可以使用自定义指令来进行集成。你可以创建一个自定义指令,在其中初始化和配置第三方库,并在适当的时机调用库的方法。
动画和过渡效果:自定义指令可以与 Vue 的过渡系统一起使用,实现自定义的动画和过渡效果。你可以在自定义指令中监听过渡钩子函数,并根据需要操作元素的样式或类名来实现过渡效果。
这只是一些常见的应用场景,实际上自定义指令的应用范围非常广泛。通过自定义指令,你可以扩展 Vue 的能力,实现更复杂和灵活的交互行为。
8.Vue是如何收集依赖的?
在Vue.js中,依赖收集是一个核心概念,特别是在实现其响应式系统的过程中。Vue的响应式系统依赖于几个关键组件:观察者模式、依赖收集和派发更新。下面详细解释Vue是如何收集依赖的。
(1) 响应式数据
Vue通过Object.defineProperty
(Vue 2)或Proxy
(Vue 3)来将数据对象中的属性转换为getter和setter。这使得Vue能够拦截对这些属性的访问和修改操作。
在Vue 2中,Vue通过Object.defineProperty
为每个属性创建getter和setter。例如:
var data = {message: 'Hello'
};Object.defineProperty(data, 'message', {get() {// 在这里收集依赖console.log('message被访问了');return val;},set(newVal) {if (val !== newVal) {val = newVal;// 在这里通知依赖更新console.log('message被更新了');}}
});
在Vue 3中,使用Proxy
对象来实现同样的功能,这使得拦截更全面,包括对数组的拦截:
const data = {message: 'Hello'
};const handler = {get(target, prop, receiver) {// 在这里收集依赖console.log(`${prop} 被访问了`);return Reflect.get(...arguments);},set(target, prop, value, receiver) {const oldValue = target[prop];if (oldValue !== value) {target[prop] = value;// 在这里通知依赖更新console.log(`${prop} 被更新了`);}return true;}
};const observed = new Proxy(data, handler);
(2)依赖收集
当getter被触发时(例如,在模板中访问某个响应式属性),Vue会检查当前是否有正在执行的watcher(观察者)。如果有,这个watcher就会被添加到当前属性的依赖列表中。这个过程通过Dep
类实现,在Vue 2中是手动管理的,而在Vue 3中是通过更自动化的方式实现的。
Vue 2示例:手动依赖收集
class Dep {constructor() {this.subs = []; // 存储观察者的数组}addSub(sub) { // 添加观察者到依赖列表中this.subs.push(sub);}notify() { // 通知所有观察者更新this.subs.forEach(sub => sub.update());}
}
每次属性被访问时,都会检查当前是否有活跃的watcher,如果有,就将其添加到当前属性的Dep
实例中。例如:
Object.defineProperty(data, 'message', {get() {if (Dep.target) { // 检查是否有活跃的watcher(例如组件渲染函数)Dep.target.addDep(this.dep); // 将watcher添加到依赖列表中}return val;},set(newVal) { /* ... */ }
});
(3)派发更新(触发依赖)
当响应式数据变化时(setter被触发),Vue会通知所有依赖于该数据的观察者进行更新。这是通过Dep
实例的notify
方法完成的。例如:
data.message = 'World'; // 这会触发setter,进而调用dep.notify()通知所有观察者。
通过这种方式,Vue能够确保当数据变化时,所有依赖于这些数据的视图都能够得到正确的更新。这是实现高效和响应式视图的关键机制。
9.React和Vue的异同?
(1)相同点
都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
都要自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
都使用了虚拟DOM提高重绘性能;
都要props的概念,允许组件间的数据传递;
都鼓励组件化应用,将应用拆分成一个个功能明确的模块,提高复用性。
(2)不同点
数据流:Vue默认支持数据双向绑定;React一直提倡单向数据流。
虚拟DOM:Vue在渲染的过程中会追踪每一个组件的依赖关系,不需要重新渲染整个组件树;React中每当应用状态被改变时,全部子组件都会重新渲染。当然,这可以通过
PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
组件化:Vue是近似于HTML的模板;React推荐模板通用JavaScript的语法扩展-JSX书写。
高阶组件:Vue需要通过mixins来扩展;React通过高阶组件(HOC)来扩展。
构建工具:Vue|vue-cli;React|Create React App
跨平台:Vue-Weex;React-React Native
10.Vue的优点?
(1)轻量级框架:只关注视图层。是一个构建数据的视图集合,大小只有几十kb;
(2)简单易学:国人开发,中文文档,不存在语言障碍,易于理解和学习;
(3)双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
(4)组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用有着独特的优势;
(5)视图、数据、结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
(6)虚拟DOM:不在使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom,不过是换了另一种方式;
(7)运行速度更快:相比较于react,就操作虚拟dom的性能而言,vue存在很大的优势。
11.assets和static的区别?
(1)相同点
assets和static两个都是存放静态资源文件。项目中所需的资源文件图片,字体图标,样式文件等都可以放在这两个文件下。
(2)不同点
assets中存放的静态资源文件在项目打包时,会将静态资源文件进行压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在static文件中,跟着index.html一同上传至服务器
static中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是static中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于assets中打包后的文件提交较大点。在服务器中就会占据更大的空间。
建议:将项目中template需要的样式文件js文件等都可以放置在assets中,走打包这一流程,减少体积。而项目中引入的第三方的资源文件如iconfoont.css等文件可以放置在static中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。
12.delete和Vue.delete删除数组的区别?
delete只是被删除的元素变成了empty/undefined,其他的元素的键值还是不变。
var items = ['a', 'b', 'c'];
delete items[1]; // 删除索引为1的元素,但长度不变,位置变为undefined
console.log(items); // ['a', undefined, 'c']
Vue.delete直接删除了元素改变了数组的键值。Vue.delete
是 Vue 提供的全局方法,用于删除对象的属性并确保删除操作是响应式的。当你使用 Vue.delete(object, key)
方法时,它会确保删除操作能够触发视图更新。
var vm = new Vue({data: {items: ['a', 'b', 'c']}
});// 使用 Vue.delete 删除数组中的元素
Vue.delete(vm.items, 1); // 删除索引为1的元素,即'b'
在Vue中管理数组时,推荐使用 Vue.delete
或 splice
方法来确保数据的响应式更新,避免使用 delete
操作符直接操作数组索引。这样可以保证当数组内容变化时,相关的视图也能正确更新。
13.对SSR的理解?
SSR(Server-Side Rendering,服务器端渲染)是一种将应用程序的界面在服务器上进行预先渲染并以 HTML 形式发送到客户端的技术。与传统的客户端渲染(CSR)相比,SSR 在服务器端生成完整的 HTML 页面,然后将其发送到浏览器,以提供更好的性能和搜索引擎优化。
在传统的客户端渲染中,浏览器会下载一个包含 JavaScript 代码的文件,并在客户端执行该代码来构建和呈现页面。这意味着页面初始加载时只是一个空壳,页面内容需要在浏览器中通过 JavaScript 进行渲染。
而在 SSR 中,服务器接收到请求后,会根据请求的路由和数据,预先生成完整的 HTML 页面,其中包含了初始状态下的页面内容。服务器将这个完整的 HTML 页面发送给浏览器,浏览器无需再执行额外的 JavaScript,即可直接展示出页面内容。
SSR 的优势包括:
- 更快的首次渲染:由于服务器在响应请求时已经生成了完整的 HTML 页面,所以用户打开页面时可以立即看到内容,无需等待 JavaScript 下载和执行。
- 更好的搜索引擎优化(SEO):搜索引擎爬虫能够抓取到完整的 HTML 页面,并且页面内容可直接被搜索引擎索引。
- 更好的用户体验:页面内容在服务器端渲染完成后即可展示,减少了白屏时间和加载等待。
SSR的缺点:
- 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
- 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
- 更多的服务端负载。
14.Vue的性能优化有哪些?
(1)编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
SPA页面采用keep-alive缓存组件
在更多情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
(2)SEO优化
预渲染
服务端渲染SSR
(3)打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化
(4)用户体验
骨架屏
PWA
还可以使用缓存(客服端缓存、服务端缓存)优化、服务端开启gzip压缩等
15.template和jsx的有什么分别?
对于runtime来说,只需要保证组件存在render函数即可,而有了预编译之后,只需要保证构建过程中生成render函数就可以。在webpack中,使用vue-loader编译.vue文件,内部依赖的vue-template-compiler模块,在webpack构建过程中,将template预编译成render函数。与react类似,在添加了jsx的语法糖解析器babel-plugin-transform-vue-jsx之后,就可以直接手写render函数。
所以,template和jsx的都是render的一种表现形式,不同的是:JSx相对于template而言,具有更高的灵活性,在复杂的组件中,更具有优势,而template虽然显得有些呆滞。但是template在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。
💕💕💕持续更新中......
若文章对你有帮助,点赞❤️、收藏⭐加关注➕吧!