ToB企服应用市场:ToB评测及商务社交产业平台

标题: 2024年前端真实面试题集合(Vue篇02) [打印本页]

作者: 老婆出轨    时间: 2024-12-23 23:23
标题: 2024年前端真实面试题集合(Vue篇02)
第一章 v-if和v-for的优先级是什么?



一、作用

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染
v-for 指令基于一个数组来渲染一个列表。v-for 指令必要使用 item in items 形式的特别语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名
在 v-for 的时候,建议设置key值,而且保证每个key值是独一无二的,这便于diff算法进行优化
两者在用法上
  1. <Modal v-if="isShow" />
  2. <li v-for="item in items" :key="item.id">
  3.     {{ item.label }}
  4. </li>
复制代码
二、优先级

v-if与v-for都是vue模板系统中的指令
在vue模板编译的时候,会将指令系统转化成可实行的render函数
示例

编写一个p标签,同时使用v-if与 v-for
  1. <div id="app">
  2.     <p v-if="isShow" v-for="item in items">
  3.         {{ item.title }}
  4.     </p>
  5. </div>
复制代码
创建vue实例,存放isShow与items数据
  1. const app = new Vue({
  2.   el: "#app",
  3.   data() {
  4.     return {
  5.       items: [
  6.         { title: "foo" },
  7.         { title: "baz" }]
  8.     }
  9.   },
  10.   computed: {
  11.     isShow() {
  12.       return this.items && this.items.length > 0
  13.     }
  14.   }
  15. })
复制代码
模板指令的代码都会生成在render函数中,通过app.$options.render就能得到渲染函数
  1. ƒ anonymous() {
  2.   with (this) { return
  3.     _c('div', { attrs: { "id": "app" } },
  4.     _l((items), function (item)
  5.     { return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e() }), 0) }
  6. }
复制代码
_l是vue的列表渲染函数,函数内部都会进行一次if判定
开端得到结论:v-for优先级是比v-if高
再将v-for与v-if置于不同标签
  1. <div id="app">
  2.     <template v-if="isShow">
  3.         <p v-for="item in items">{{item.title}}</p>
  4.     </template>
  5. </div>
复制代码
再输出下render函数
  1. ƒ anonymous() {
  2.   with(this){return
  3.     _c('div',{attrs:{"id":"app"}},
  4.     [(isShow)?[_v("\n"),
  5.     _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
  6. }
复制代码
这时候我们可以看到,v-for与v-if作用在不同标签时候,是先进行判定,再进行列表的渲染
我们再在查看下vue源码
源码位置:\vue-dev\src\compiler\codegen\index.js
  1. export function genElement (el: ASTElement, state: CodegenState): string {
  2.   if (el.parent) {
  3.     el.pre = el.pre || el.parent.pre
  4.   }
  5.   if (el.staticRoot && !el.staticProcessed) {
  6.     return genStatic(el, state)
  7.   } else if (el.once && !el.onceProcessed) {
  8.     return genOnce(el, state)
  9.   } else if (el.for && !el.forProcessed) {
  10.     return genFor(el, state)
  11.   } else if (el.if && !el.ifProcessed) {
  12.     return genIf(el, state)
  13.   } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
  14.     return genChildren(el, state) || 'void 0'
  15.   } else if (el.tag === 'slot') {
  16.     return genSlot(el, state)
  17.   } else {
  18.     // component or element
  19.     ...
  20. }
复制代码
在进行if判定的时候,v-for是比v-if先进行判定
最闭幕论:v-for优先级比v-if高
三、留意事项

  1. <template v-if="isShow">
  2.     <p v-for="item in items">
  3. </template>
复制代码
如果条件出现在循环内部,可通过盘算属性computed提前过滤掉那些不必要显示的项
  1. computed: {
  2.     items: function() {
  3.       return this.list.filter(function (item) {
  4.         return item.isShow
  5.       })
  6.     }
  7. }
复制代码
第二章 SPA首屏加载速度慢的怎么解决?



一、什么是首屏加载

        首屏时间(First Contentful Paint),指的是欣赏器从相应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不肯定要全部渲染完成,但必要展示当前视窗必要的内容
首屏加载可以说是用户体验中最重要的环节
关于盘算首屏时间

使用performance.timing提供的数据:


通过DOMContentLoad或者performance来盘算出首屏时间
  1. // 方案一:
  2. document.addEventListener('DOMContentLoaded', (event) => {
  3.     console.log('first contentful painting');
  4. });
  5. // 方案二:
  6. performance.getEntriesByName("first-contentful-paint")[0].startTime
  7. // performance.getEntriesByName("first-contentful-paint")[0]
  8. // 会返回一个 PerformancePaintTiming的实例,结构如下:
  9. {
  10.   name: "first-contentful-paint",
  11.   entryType: "paint",
  12.   startTime: 507.80000002123415,
  13.   duration: 0,
  14. };
复制代码
二、加载慢的缘故原由
在页面渲染的过程,导致加载速度慢的因素大概如下:
   
  三、解决方案

常见的几种SPA首屏优化方式
   
  减小入口文件体积

常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增长


在vue-router配置路由的时候,采用动态加载路由的形式
  1. routes:[
  2.     path: 'Blogs',
  3.     name: 'ShowBlogs',
  4.     component: () => import('./components/ShowBlogs.vue')
  5. ]
复制代码
以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件
静态资源本地缓存

后端返回资源题目:
   
  前端公道使用localStorage
UI框架按需加载

在日常使用UI框架,比方element-UI、或者antd,我们经常性直接引用整个UI库
  1. import ElementUI from 'element-ui'
  2. Vue.use(ElementUI)
复制代码
但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用
  1. import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
  2. Vue.use(Button)
  3. Vue.use(Input)
  4. Vue.use(Pagination)
复制代码
组件重复打包
假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载
解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置
  1. minChunks: 3
复制代码
minChunks为3表现会把使用3次及以上的包抽离出来,放进公共依赖文件,克制了重复加载组件
图片资源的压缩

图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素,对于所有的图片资源,我们可以进行适当的压缩,对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将浩繁小图标归并到同一张图上,用以减轻http请求压力。
开启GZip压缩

拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin
  1. cnmp i compression-webpack-plugin -D
复制代码
在vue.congig.js中引入并修改webpack配置
  1. const CompressionPlugin = require('compression-webpack-plugin')
  2. configureWebpack: (config) => {
  3.         if (process.env.NODE_ENV === 'production') {
  4.             // 为生产环境修改配置...
  5.             config.mode = 'production'
  6.             return {
  7.                 plugins: [new CompressionPlugin({
  8.                     test: /\.js$|\.html$|\.css/, //匹配文件名
  9.                     threshold: 10240, //对超过10k的数据进行压缩
  10.                     deleteOriginalAssets: false //是否删除原文件
  11.                 })]
  12.             }
  13.         }
复制代码
在服务器我们也要做相应的配置 如果发送请求的欣赏器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用
  1. const compression = require('compression')
  2. app.use(compression())  // 在其他中间件使用之前调用
复制代码
使用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到欣赏器
从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染
小结:

减少首屏渲染时间的方法有许多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
下图是更为全面的首屏优化的方案


大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化
 
第三章 为什么data属性是一个函数而不是一个对象?



一、实例和组件定义data的区别

vue实例的时候定义data属性既可以是一个对象,也可以是一个函数
  1. const app = new Vue({
  2.     el:"#app",
  3.     // 对象格式
  4.     data:{
  5.         foo:"foo"
  6.     },
  7.     // 函数格式
  8.     data(){
  9.         return {
  10.              foo:"foo"
  11.         }
  12.     }
  13. })
复制代码
组件中定义data属性,只能是一个函数
如果为组件data直接定义为一个对象
  1. Vue.component('component1',{
  2.     template:`<div>组件</div>`,
  3.     data:{
  4.         foo:"foo"
  5.     }
  6. })
复制代码
则会得到警告信息


警告说明:返回的data应该是一个函数在每一个组件实例中
二、组件data定义函数与对象的区别

上面讲到组件data必须是一个函数,不知道大家有没有思索过这是为什么呢?
在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例
这里我们模仿组件构造函数,定义data属性,采用对象的形式
  1. function Component(){
  2. }
  3. Component.prototype.data = {
  4.         count : 0
  5. }
复制代码
创建两个组件实例
  1. const componentA = new Component()
  2. const componentB = new Component()
复制代码
修改componentA组件data属性的值,componentB中的值也发生了改变
  1. console.log(componentB.data.count)  // 0
  2. componentA.data.count = 1
  3. console.log(componentB.data.count)  // 1
复制代码
产生这样的缘故原由这是两者共用了同一个内存地址,componentA修改的内容,同样对componentB产生了影响
如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不雷同)
  1. function Component(){
  2.         this.data = this.data()
  3. }
  4. Component.prototype.data = function (){
  5.     return {
  6.                    count : 0
  7.     }
  8. }
复制代码
修改componentA组件data属性的值,componentB中的值不受影响
  1. console.log(componentB.data.count)  // 0
  2. componentA.data.count = 1
  3. console.log(componentB.data.count)  // 0
复制代码
vue组件大概会有许多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染
三、原理分析

起首可以看看vue初始化data的代码,data的定义可以是函数也可以是对象
源码位置:/vue-dev/src/core/instance/state.js
  1. function initData (vm: Component) {
  2.   let data = vm.$options.data
  3.   data = vm._data = typeof data === 'function'
  4.     ? getData(data, vm)
  5.     : data || {}
  6.     ...
  7. }
复制代码
data既能是object也能是function,那为什么还会出现上文警告呢?
别急,继承看下文
组件在创建的时候,会进行选项的归并
源码位置:/vue-dev/src/core/util/options.js
自定义组件会进入mergeOptions进行选项归并
  1. Vue.prototype._init = function (options?: Object) {
  2.     ...
  3.     // merge options
  4.     if (options && options._isComponent) {
  5.       // optimize internal component instantiation
  6.       // since dynamic options merging is pretty slow, and none of the
  7.       // internal component options needs special treatment.
  8.       initInternalComponent(vm, options)
  9.     } else {
  10.       vm.$options = mergeOptions(
  11.         resolveConstructorOptions(vm.constructor),
  12.         options || {},
  13.         vm
  14.       )
  15.     }
  16.     ...
  17.   }
复制代码
定义data会进行数据校验
源码位置:/vue-dev/src/core/instance/init.js
这时候vm实例为undefined,进入if判定,若data范例不是function,则出现警告提示
  1. strats.data = function (
  2.   parentVal: any,
  3.   childVal: any,
  4.   vm?: Component
  5. ): ?Function {
  6.   if (!vm) {
  7.     if (childVal && typeof childVal !== "function") {
  8.       process.env.NODE_ENV !== "production" &&
  9.         warn(
  10.           'The "data" option should be a function ' +
  11.             "that returns a per-instance value in component " +
  12.             "definitions.",
  13.           vm
  14.         );
  15.       return parentVal;
  16.     }
  17.     return mergeDataOrFn(parentVal, childVal);
  18.   }
  19.   return mergeDataOrFn(parentVal, childVal, vm);
  20. };
复制代码
四、结论
   
  第四章 动态给vue的data添加一个新的属性时会发生什么?怎样解决?



一、直接添加属性的题目

我们从一个例子开始
定义一个p标签,通过v-for指令进行遍历
然后给botton标签绑定点击变乱,我们预期点击按钮时,数据新增一个属性,界面也 新增一行
  1. <p v-for="(value,key) in item" :key="key">
  2.     {{ value }}
  3. </p>
  4. <button @click="addProperty">动态添加新属性</button>
复制代码
实例化一个vue实例,定义data属性和methods方法
  1. const app = new Vue({
  2.     el:"#app",
  3.            data:()=>{
  4.                item:{
  5.             oldProperty:"旧属性"
  6.         }
  7.     },
  8.     methods:{
  9.         addProperty(){
  10.             this.items.newProperty = "新属性"  // 为items添加新属性
  11.             console.log(this.items)  // 输出带有newProperty的items
  12.         }
  13.     }
  14. })
复制代码
点击按钮,发现效果不及预期,数据虽然更新了(console打印出了新属性),但页面并没有更新
二、原理分析

为什么产生上面的情况呢?
下面来分析一下
vue2是用过Object.defineProperty实现数据相应式
  1. const obj = {}
  2. Object.defineProperty(obj, 'foo', {
  3.         get() {
  4.             console.log(`get foo:${val}`);
  5.             return val
  6.         },
  7.         set(newVal) {
  8.             if (newVal !== val) {
  9.                 console.log(`set foo:${newVal}`);
  10.                 val = newVal
  11.             }
  12.         }
  13.     })
  14. }
复制代码
当我们访问foo属性或者设置foo值的时候都可以大概触发setter与getter
  1. obj.foo   
  2. obj.foo = 'new'
复制代码
但是我们为obj添加新属性的时候,却无法触发变乱属性的拦截
  1. obj.bar  = '新属性'
复制代码
缘故原由是一开始obj的foo属性被设成了相应式数据,而bar是后面新增的属性,并没有通过Object.defineProperty设置成相应式数据
三、解决方案

Vue 不允许在已经创建的实例上动态添加新的相应式属性
若想实现数据与视图同步更新,可采取下面三种解决方案:
   
  Vue.set()

Vue.set( target, propertyName/index, value )
参数
   
  返回值:设置的值
通过Vue.set向相应式对象中添加一个property,并确保这个新 property同样是相应式的,且触发视图更新
关于Vue.set源码(省略了许多与本节不相关的代码)
源码位置:src\core\observer\index.js
  1. function set (target: Array<any> | Object, key: any, val: any): any {
  2.   ...
  3.   defineReactive(ob.value, key, val)
  4.   ob.dep.notify()
  5.   return val
  6. }
复制代码
这里无非再次调用defineReactive方法,实现新增属性的相应式
关于defineReactive方法,内部照旧通过Object.defineProperty实现属性拦截
大抵代码如下:
  1. function defineReactive(obj, key, val) {
  2.     Object.defineProperty(obj, key, {
  3.         get() {
  4.             console.log(`get ${key}:${val}`);
  5.             return val
  6.         },
  7.         set(newVal) {
  8.             if (newVal !== val) {
  9.                 console.log(`set ${key}:${newVal}`);
  10.                 val = newVal
  11.             }
  12.         }
  13.     })
  14. }
复制代码
Object.assign()

直接使用Object.assign()添加到对象的新属性不会触发更新
应创建一个新的对象,归并原对象和混入对象的属性
  1. this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
复制代码
$forceUpdate

如果你发现你自己必要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
$forceUpdate迫使Vue 实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
小结
   
  PS:vue3是用过proxy实现数据相应式的,直接动态添加新属性仍可以实现数据相应式
第五章 Vue中组件和插件有什么区别?



一、组件是什么

回顾以前对组件的定义:
组件就是把图形、非图形的各种逻辑均抽象为一个同一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件
组件的上风
   
  二、插件是什么

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严酷的限制——一般有下面几种:
   
  三、两者的区别

两者的区别主要表现在以下几个方面:
   
  编写形式

编写组件

编写一个组件,可以有许多方式,我们最常见的就是vue单文件的这种格式,每一个.vue文件我们都可以看成是一个组件
vue文件尺度格式
  1. <template>
  2. </template>
  3. <script>
  4. export default{
  5.     ...
  6. }
  7. </script>
  8. <style>
  9. </style>
复制代码
我们还可以通过template属性来编写一个组件,如果组件内容多,我们可以在外部定义template组件内容,如果组件内容并不多,我们可直接写在template属性上
  1. <template id="testComponent">     // 组件显示的内容
  2.     <div>component!</div>   
  3. </template>
  4. Vue.component('componentA',{
  5.     template: '#testComponent'  
  6.     template: `<div>component</div>`  // 组件内容少可以通过这种形式
  7. })
复制代码
编写插件
vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
  1. MyPlugin.install = function (Vue, options) {
  2.   // 1. 添加全局方法或 property
  3.   Vue.myGlobalMethod = function () {
  4.     // 逻辑...
  5.   }
  6.   // 2. 添加全局资源
  7.   Vue.directive('my-directive', {
  8.     bind (el, binding, vnode, oldVnode) {
  9.       // 逻辑...
  10.     }
  11.     ...
  12.   })
  13.   // 3. 注入组件选项
  14.   Vue.mixin({
  15.     created: function () {
  16.       // 逻辑...
  17.     }
  18.     ...
  19.   })
  20.   // 4. 添加实例方法
  21.   Vue.prototype.$myMethod = function (methodOptions) {
  22.     // 逻辑...
  23.   }
  24. }
复制代码
注册形式
组件注册

vue组件注册主要分为全局注册与局部注册
全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项
  1. Vue.component('my-component-name', { /* ... */ })
复制代码
局部注册只需在用到的地方通过components属性注册一个组件
  1. const component1 = {...} // 定义一个组件
  2. export default {
  3.         components:{
  4.                 component1   // 局部注册
  5.         }
  6. }
复制代码
插件注册
插件的注册通过Vue.use()的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项
  1. Vue.use(插件名字,{ /* ... */} )
复制代码
留意的是:
注册插件的时候,必要在调用 new Vue() 启动应用之前完成
Vue.use会自动阻止多次注册雷同插件,只会注册一次
使用场景

详细的实在在插件是什么章节已经表述了,这里在总结一下
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue
插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身
简朴来说,插件就是指对Vue的功能的增强或补充

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4