武汉某厂前端中高级面试题一面

Vue / 面试题库 / 2022-11-03

1 自我介绍

略。。。

2 vue组件通信怎么使用双向数据流通信,尽可能不用emit把方法暴露出去

.sync 修饰符

文档:https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6

2.3.0+ 新增

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件两侧都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>

为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

<text-document v-bind:title.sync="doc.title"></text-document>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:

<text-document v-bind.sync="doc"></text-document>

这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

3 vue父子组件v-model传值

文档:https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model
通常子组件某个变量更新,并需要告知父组件时,需要子组件触发事件并父组件监听该事件。
但是熟悉上面 v-model 的实现原理后,我们可以巧妙地运用这一原理(v-model 在内部使用不同的属性为不同的输入元素并抛出不同的事件)
image.png

在父组件中

<DomDialog v-model="isDomDialog"></DomDialog>    

等同于如下常规写法:

<DomDialog v-bind:value="isDomDialog" v-on:input="isDomDialog=$event"></DomDialog> 

或者

<DomDialog :value="isDomDialog" @input="isDomDialog=$event"></DomDialog>

在子组件中的接收与传值:

props:{
    value:{type: Boolean,},
  },
  data(){
    return {
      dialogVisible:false,
    }
  },
  watch:{
    value(val){
      this.dialogVisible = val
    },
  },
  methods: {
    // 关闭弹窗触发
    confrim(){
      this.$emit('input',false)  // 通过 this.$emit() 向父组件传值
    }
  },

总结:
1.子组件设 value 为props属性,并且不主动改变 value 值
2.子组件通过 this.$emit('input', 'updateValue') 将 updateValue 值传给父组件
3.父组件通过 v-model="localValue" 绑定一个本地变量,即可实现子组件value值与父组件updateValue 值同步更新

4 keepalive 有关的生命周期

16674603751.png
当组件在 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。
1.activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
2.deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated

5 keepalive有几个属性 作用是什么

文档:https://v2.cn.vuejs.org/v2/api/?#keep-alive

keep-alive可以接收3个属性做为参数进行匹配对应的组件进行缓存:

  • include 包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)
  • exclude 排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)
  • max 缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数)
// 只缓存组件name为a和b的组件
<keep-alive include="a,b"> 
  <component />
</keep-alive>

// 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染)
<keep-alive exclude="c"> 
  <component />
</keep-alive>

// 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件
<keep-alive include="a,b" exclude="b"> 
  <component />
</keep-alive>

// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
<keep-alive exclude="c" max="5"> 
  <component />
</keep-alive>

配合router使用

router-view也是一个组件,如果直接被包在keepalive里面,那么所有路径匹配到的视图组件都会被缓存,用法与缓存组件相同

  • 使用 include/exclude
  • 使用 meta 属性
    第一种方法:使用 include
//只有路径匹配到的 name 为 a 组件会被缓存
<keep-alive include="a">
    <router-view></router-view>
</keep-alive>

第一种方法:使用 meta 属性

// routes 配置文件
export default [
  {
    path: '/',
    name: 'home',
    component: Home,
    meta: {
      keepAlive: true // 需要被缓存
    }
  }, {
    path: '/user',
    name: 'user',
    component: User,
    meta: {
      keepAlive: false // 不需要被缓存
    }
  }
]
// App.vue
<keep-alive>
    <router-view v-if="$route.meta.keepAlive">
        <!-- 这里组件会被缓存,比如 Home! -->
    </router-view>
</keep-alive>

<router-view v-if="!$route.meta.keepAlive">
    <!-- 这里组件不会被缓存,比如 User! -->
</router-view>

6 怎么销毁keepalive

在使用keep-alive的过程中,有时我们需要当前页面缓存,但是再某种情况下又不需要当前页面缓存,keep-alive并未提供销毁包含组件的方法,那我们可以通过手动删除cache数组中的当前页面的key值实现消除某个页面缓存的功能。

function removeKeepAliveCache () {
    if (this.$vnode && this.$vnode.data.keepAlive && this.$vnode.parent) {
        const tag = this.$vnode.tag;
        let caches = this.$vnode.parent.componentInstance.cache;
        let keys = this.$vnode.parent.componentInstance.keys;
        for (let [key, cache] of Object.entries(caches)) {
            if (cache.tag === tag) {
                if (keys.length > 0 && keys.includes(key)) {
                    keys.splice(keys.indexOf(key), 1);
                }
                delete caches[key];
            }
        }
    }
    this.$destroy();
};

然后,在组件里的路由守卫调用removeKeepAliveCache,同时也不需要去修改from.meta.keepAlive

beforeRouteLeave(to, from, next) {
    if (to.name === 'selectAddr') {
        // from.meta.keepAlive = true;
    } else {
        // from.meta.keepAlive = false;
        removeKeepAliveCache.call(this);
    }
    next();
},

参考文献:
https://v2.cn.vuejs.org/v2/api/?#keep-alive
https://juejin.cn/post/6844903649517240328
https://segmentfault.com/a/1190000022957086?sort=votes
https://blog.csdn.net/mengweizhao/article/details/120758368

7 webpack对静态资源的处理

在我们的项目结构里,有两个静态文件的路径,分别是:src/assets 和 static/。那这两个到底有什么区别呢?

Webpacked 资源

为了回答这个问题,我们首先需要理解webpack是怎样处理静态资源的。在*.vue组件中,所有的templates和css都会被vue-html-loader 和 css-loader解析,寻找资源的URL。举个例子,在<img src="./logo.png"> 和 background: url(./logo.png)"./logo.png"中,都是相对资源路径,都会被Webpack解析成模块依赖 。

由于logo.png不是JavaScript,当被看成一个模块依赖的时候,我们需要使用url-loader 和 file-loader进行处理。 该模板已经配置好了这些loaders,所以你能够使用相对/模块路径时不需要担心部署的问题。

由于这些资源可能在构建的时候被内联/复制/重命名, 所以它们从本质上来说是你源码的一部分。这就是为什么我们建议将交由webpack处理的静态资源和其它源文件一样放在/src路径下面。实际上,你甚至不需要把它们全都放在/src/assets路径下:你可以基于模块/组件的使用来组织文件结构。例如,你可以把每个组件和属于它的静态资源放在它自己的目录下。

资源处理规则

  • 相对URL, e.g. ./assets/logo.png 将会被解释成一个模块依赖。它们会被一个基于你的Webpack输出配置自动生成的URL替代。

  • 没有前缀的URL, e.g. assets/logo.png 将会被看成相对URL,并且转换成./assets/logo.png

  • 前缀带~的URL 会被当成模块请求, 类似于require('some-module/image.png'). 如果你想要利用Webpack的模块处理配置,就可以使用这个前缀。例如,如果你有一个对于assets的路径解析,你需要使用<img src="~assets/logo.png">来确保解析是对应上的。

  • 相对根目录的URL, e.g. /assets/logo.png 是不会被处理的.

在JavaScript里获取资源路径

为了能让Webpack返回正确的资源路径,你需要使用require('./relative/path/to/file.jpg'),由file-loader进行解析,然后返回处理过的URL。例如:

computed: {
  background () {
    return require('./bgs/' + this.id + '.jpg')
  }
}

"真实的" 静态资源

作为对比,在static/下的文件都不会被Webpack处理:它们使用相同的文件名,直接拷贝到最终的路径。你必须使用绝对路径来引用这些文件,取决于在config.js里面加入的build.assetsPublicPath 和 build.assetsSubDirectory

举个例子,下面的默认值是:

注意上面的例子,在最终的构建时将会包含./bgs/路径下的所有图片 这是因为Webpack不能猜出来在运行时会用到其中的哪个,所以会包含所有的。

// config/index.js
module.exports = {
  // ...
  build: {
    assetsPublicPath: '/',
    assetsSubDirectory: 'static'
  }
}

所有放在 static/目录下的文件都应该是使用绝对URL/static/[filename]引用的。如果你将assetSubDirectory的值改成assets, 那么这些URL就会被变成 /assets/[filename]

8 webpack的属性有哪些,有什么作用

属性类型作用
entrystring、object(多入口)指定入口文件
outputobject指定文件的出口(详细信息看下文output配置)
modestringdevelopment、 production(默认, 对代码进行压缩)、none(不进行处理)
moduleobject解析除了js、json之外的文件(详细信息查看下文module.rules 配置)
pluginsarray使用各种插件 eg:[new htmlWebpackPlugin()]
devServerobject开发环境配置(详细配置查看下文devServe配置)
devtoolstring配置辅助工具(详细配置查看下文courseMap类型)
optimizationobject集中配置webpack优化功能(详细配置查看下文optimization配置)

常用loader

  1. css-loder 处理css
  2. style-loader 生成style标签并且添加到项目中
  3. file-loader 处理图片、字体等资源文件
  4. url-loader 压缩图片、字体等资源文件并且转换成base64格式,减少请求次数
  5. babel-loader js转换loader (需要配置’@babel/preset-env’)
  6. html-loader 对html文件进行处理(默认会处理img src属性)

常用plugin

  1. clean-webpack-plugin 自动清除输出目录
  2. html-webpack-plugin 自动生成html文件
  3. copy-webpack-plugin 拷贝不需要处理的文件, 指定输出目录([‘public’])
  4. mini-css-extract-plugin 实现css的按需加载(样式文件不是很大建议不要单独提取150kb)
  5. optimize-css-assets-webpack-plugin 压缩css文件

常用插件
webpack-dev-server 提供了一个简单的web服务器,能够热更新当前项目

output配置

属性类型作用
filenamestring指定文件名称
pathstring指定输入文件目录(path.join(__dirname, ‘dist’))
publicPathstring指定根目录(‘dist/’), 一般不需要指定,可以自动引入

module.rules 配置

属性类型作用
test正则表达式判断文件类型
usestring、array、object处理不同文件的loader,loader为数组时执行顺序从后往前执行(类型为object详细信息查看下文use配置)

use配置(Object)

属性类型作用
loaderstring文件转换
optionarray基础配置
eg: 图片、字体等资源处理,小于10KB 进行base64转换并且嵌入到页面中, 大于10KB不进行压缩

devServe 配置

属性类型作用
contentBasestring为开发服务器指定查找资源目录
portnumber端口
proxyobject配置代理
hotboolean是否开启热更新(需要在plugin中调用 new webpack.HotModuleReplacementPlugin())
hotOnlyboolean当页面出现错误时不会执行热更新

courseMap类型

属性类型是否生成对应的sourceMap文件是否源代码特点
evelstringnono使用evel函数包装
evel-source-mapstringyesno能定位到行列信息
cheap-evel-source-mapstringyesno只能定位到行
cheap-module-evel-source-mapstringyesyes能定位到源码错误位置
inline-source-mapstringyesno嵌入到url中
hidden-source-mapstringyesno影藏sourceMap文件
nosource-source-mapstringyesno看不到代码信息
建议:

开发环境:cheap-module-eval-source-map(会显示出源码错误位置)
生成坏境: nosource-source-map(不会暴漏源码,只会抛出错误位置)

optimization配置

属性类型作用
usedExportsboolean标记未使用代码
minimizeboolean、array(自定义压缩插件)去除无用代码
concatenateModulesboolean尽可能将所有的模块放到同一个函数中
sideEffectsboolean判断是否有副作用
splitChunksobject设置spllitChunks.chunks = all提取公共模块

html-webpack-plugin 属性配置

属性类型作用
titlestringhtml文件标题
metaobject指定窗口大小
templatestring指定模板路径
filenamestring指定文件名称(默认index.html)
// webpack.config.js中配置
// npm install html-webpack-plugin --save-dev

const htmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
	new htmlWebpackPlugin({
     title: 'cache',
     meta: {
      	viewport: 'width-device-width'
     },
     template: './src/index.html'
  })
]

9 webpack中loader和plugin的区别

不同的作用:
Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。

Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

loader比较单一就是用来加载文件

不同的用法:
Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)

Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入

10 require和import的区别

无论是require 还是 import 都是不同规范下导入模块的方法,主要有以下的区别:

1、require对应导出的方法是module.exports,
      import对应的方法是export default/export

2、require 是CommonJs的语法
      import 是 ES6 的语法标准。

3、require是运行运行时加载模块里的所有方法(动态加载),
      import 是编译的时候调用(静态加载),不管在哪里引用都会提升到代码顶部。

4、require 是CommonJs的语法,引入的是的是整个模块里面的对象,
     import 可以按需引入模块里面的对象

5、require 导出是值的拷贝,
      import 导出的是值的引用

参考资料:https://zhuanlan.zhihu.com/p/121770261

11 vue怎么给对象添加属性

vue.$set()
文档:https://v2.cn.vuejs.org/v2/api/?#vm-set

在开发过程中,经常遇到这样的情况:当data里边已经声明或赋值过对象或者数组(数组里边的值是对象)时,再向对象中添加新的属性,如果更新此属性的值,是不会更新视图的。根据官方文档定义:如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。

当你把一个普通的js对象传入Vue实例作为data选项,vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter。
受现代js的限制,vue不能检测到对象属性的添加或删除。由于Vue会在初始化实例时对属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让vue转换它,这样才能让它是响应的。

比如:

data () {
    return {
        student: {
            name: '',
            age: ''
        }
    }
},
mounted () {
    this.student.age = 24
}

众所周知,直接给student赋值操作,虽然可以新增属性,但是不会触发视图( 页面 )
更新原因是:vue.js的属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。

解决方法
vue.$set(obj,key,value)

mounted () {
    this.$set(this.student,"age", 24)
}
第一个参数:改变的对象
第二个参数:改变的对象中的属性
第三个参数:改变的属性值