前端面试·项目相关

4,260 阅读18分钟

前端实习面试中碰到的和项目有关的问题,合集参考:前端小白的面试问题集

1.项目中的优化?

参考:送你一张图,搞懂性能优化,再也不怕面试官拷问

1.1编码层优化

  • v-if 和 v-show 区分使用场景:

v-if 如果初始为假则不被渲染,v-show无论如何都会被渲染,因此v-if适用于条件很少改变的情况,v-show适用于频繁变化的场景;

  • computed 和 watch 区分使用场景:

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if:

在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff .

v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性。

  • 长列表性能优化:

Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

  • 事件销毁:

Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,使用removeEventListener

  • 图片懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vue 的 vue-lazyload 插件

  • 路由懒加载

Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。

const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})
  • 第三方插件按需引入

我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 babel-plugin-component ,然后可以只引入需要的组件,以达到减小项目体积的目的。以下为项目中引入 element-ui 组件库为例:

//.babelrc
{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

//main.js
import Vue from 'vue';
import { Button, Select } from 'element-ui';

 Vue.use(Button)
 Vue.use(Select)
  • 优化无限列表性能

如果你的应用存在非常长或者无限滚动的列表,那么需要采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。你可以参考以下开源项目 vue-virtual-scroll-list 和 vue-virtual-scroller 来优化这种无限列表的场景的。

  • 服务端渲染SSR或预渲染

服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染的优点:

更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

服务端渲染的缺点:

更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;

更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。

如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO,如果你的 Vue 项目只需改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染。

1.2Webpack层优化

  • Webpack 对图片进行压缩:

在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片。

  • 减少 ES6 转为 ES5 的冗余代码:

Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法

在默认情况下, Babel 会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过 require('babel-runtime/helpers/createClass') 的方式导入,这样就能做到只让它们出现一次。babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减小 babel 编译出来的代码的文件大小。

  • 提取公共代码:

如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

相同的资源被重复加载,浪费用户的流量和服务器的成本。

每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。

所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin,我们在项目中 CommonsChunkPlugin 的配置如下:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
})

值得注意的是,这一插件被Webpack4废弃,4以上的版本采用optimization.splitChunks和optimization.runtimeChunk来代替。

  • 模板预编译:

当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。 预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。 如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader,它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数。

  • 提取CSS:

当使用单文件组件时,组件内的 CSS 会以 style 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,如果你使用服务端渲染,这会导致一段 “无样式内容闪烁 (fouc) ” 。将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存。 查阅这个构建工具各自的文档来了解更多:

webpack + vue-loader ( vue-cli 的 webpack 模板已经预先配置好)

Browserify + vueify

Rollup + rollup-plugin-vue

  • 优化sourcemap

我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的。

开发环境推荐:cheap-module-eval-source-map

生产环境推荐:cheap-module-source-map

原因如下:

cheap:源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息;

module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module 配置;

soure-map :source-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性;

eval-source-map:eval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。

  • 构建结果分析

Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。接下来讲解我们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer 。

1.3基于web的优化

  • 开启gzip压缩

gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右

  • 浏览器缓存

为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,协商缓存)

  • CDN 的使用

浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率 。

2.团队有没有出现配合困难?怎么解决的

  • 前后端、数据处理分离,各司其职;
  • 版本控制;
  • 开发文档;

3.git的基本操作

  • 初始化:git-init
  • 添加1.txt文件到缓冲区:git add .\1.txtgit add .)(添加当前目录下所有文件到缓冲区)
  • 提交: git commit -m "注释"
  • 连接github:git remote add origin http://....
  • 查看状态:git status
  • 查看当前分支:git branch -v
  • 创建并转到分支b1 git checkout -b b1
  • 合并分支:转到master分支后,git merge b1
  • 回滚:git reset (--mixed(归档区和缓冲区)/hard(工作区缓冲区和归档区)/soft(归档区))版本号
  • 所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作):git relog
  • 查看所有提交过的版本信息:git log
  • 创建一次新的提交,回到之前的某个版本,但是不影响之后的提交:git revert
  • 将本地与远端同步:git pull(实际上时两个代码的合并: git fetch 查看远端版本 git merge origin/master合并远端主干)
  • 将远端分支dev与本地dev同步:git push origin dev
  • 请求合并:pull request(可以用SSH生成密钥来实现远程免密登录)

如果分支要合并与主干有冲突的内容,将会失败并提示,需要手动修改

  • git merge :不会保留merge的分支的commit。使用merge命令合并分支,解决完冲突,执行git add .git commit -m'fix conflict'。这个时候会产生一个commit。
  • git rebase:使用rebase命令合并分支,解决完冲突,执行git add .git rebase --continue,不会产生额外的commit。这样的好处是,‘干净’,分支上不会有无意义的解决分支的commit;坏处,如果合并的分支中存在多个commit,需要重复处理多次冲突。
  • merge --no-ff指的是强行关闭fast-forward方式,使得每一次的合并都创建一个新的commit记录,即要求git merge即使在fast forward条件下也要产生一个新的merge commit,用来避免丢失信息。 如果不加 --no-ff 则被合并的分支之前的commit都会被抹去,只会保留一个解决冲突后的 merge commit。

4.图片加载有哪些优化方法?

图片相关优化:

  • 不用图片:直接使用CSS替代图片。
  • 使用矢量图替代位图:对于绝大多数图案、图标等,矢量图更小,且可缩放而无需生成多套图。
  • 使用恰当的图片格式:别忘了使用优秀的图片编码器及合适的参数。好的图片编码器,尤其是有损图片格式的编码器,能通过算法或手动调整,获得更高的压缩比。

通用优化:

  • 按照HTTP协议设置合理的缓存:具体的缓存策略(如永久缓存+重命名)、部署策略(如反向代理、CDN等)这里就不展开了。
  • 使用支持SPDY的服务器:SPDY可认为是未来的HTTP 2.0的早期实现,Chrome、Firefox 13+、Opera 12+、IE 11+均已支持SPDY。
  • 资源的lazyload或postpone:(lazyload:延迟到其他资源下载完成后再加载,postpone:延迟到元素可见再加载。)目前基本上都要用脚本控制。未来HTML和CSS会增加相关的控制属性,见:Resource Priorities。
  • 资源的prefetch:可用,见www.whatwg.org/specs/web-a… 9则是对该资源的hostname进行DNS预解析。如果你真的需要更强的控制,则得用脚本。注意:Chrome支持与prefetch相近但更进一步的,另外SPDY加入了与prefetch相近但语义不同的subresource link支持。
  • 使用data url:资源内嵌于CSS或HTML中,而不必单独请求。注意,多个地方都要使用的资源不一定适合用此优化方式,因为图片数据重复多了,增加流量。另外许多浏览器对data url有长度限制,注意资源的大小。

5.性能评估

监测用户的FPS帧率

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;

6.组件设计原则

单一职责

单一职责可以保证组件是最细的粒度,且有利于复用。但太细的粒度有时又会造成组件的碎片化。

因此单一职责组件要建立在可复用的基础上,对于不可复用的单一职责组件,我们仅仅作为独立组件的内部组件即可。

通用性

组件开发要服务于业务,为了更好的复用,又要从业务中抽离。

封装

良好的组件封装应该隐藏内部细节和实现意义,并通过props来控制行为和输出。

减少访问全局变量:因为它们打破了封装,创造了不可预测的行为,并且使测试变得困难。可以将全局变量作为组件的props,而不是直接引用。

高内聚、低耦合

  • 具有多个功能的组件,应该转换为多个小组件。

  • 将网络请求和组件渲染分离,只将数据传递给组件,保证组件职责的单一性,也能将非纯代码从组件中隔离。

可测试

测试不仅仅是自动检测错误,更是检测组件的逻辑。

如果一个组件测试不易于测试,很大可能是你的组件设计存在问题。 复制代码

语义化

开发人员大部分时间都在阅读和理解代码,而不是实际编写代码。 有意义的函数、变量命名,可以让代码具有良好的可读性。

扁平化的state和props

给组件传递props时,建议用更扁平化的props,而不要用嵌套的对象或数组。

<DetailModal
    {...modalData}
    visible={showModal}
    tagType={ROBOT_TYPE}
    sceneList={sceneList}
    handleCloseModal={() => this.handleCloseModal()} />
复制代码

如上面的代码所示,传递的数据结构是modalData,但用户不清楚modalData中包含哪些属性,且还可能存在多余的属性,开发中应尽量避免传递给组件不需要的属性。