跳到主要内容

前端工程化

版本管理

版本控制是一种软件工程技巧,借此能在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同步

本地版本控制系统

优点

  • 简单,很多系统中都有内置
  • 适合管理文本,如系统配置

缺点

  • 其不支持远程操作,因此并不适合多人版本开发

集中式版本控制系统

优点

  • 适合多人团队协作开发
  • 代码集中化管理

缺点

  • 单点故障
  • 必须联网,无法单机工作

代表工具

  • SVN
  • CVS

分布式版本控制系统

分布式版本管理系统每个计算机都有一个完整的仓库,可本地提交,可以做到离线工作,则不用像集中管理那样因为断网情况而无法工作

优点

  • 适合多人团队协作开发
  • 代码集中化管理
  • 可以离线工作
  • 每个计算机都是一个完整仓库

代表工具 git

git 和 svn 的区别

  • git 和 svn 最大的区别在于 git 是分布式的,而 svn 是集中式的。因此我们不能再离线的情况下使用 svn。如果服务器出现问题,就没有办法使用 svn 来提交代码。
  • svn 中的分支是整个版本库的复制的一份完整目录,而 git 的分支是指针指向某次提交,因此 git 的分支创建开销更小并且分支上的变化不会影响到其他人。svn 的分支变化会影响到所有的人。
  • svn 的指令相对于 git 来说要简单一些,比 git 更容易上手。
  • git把内容按元数据方式存储,而SVN是按文件:因为git目录是处于个人机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。
  • GIT分支和SVN的分支不同:svn会发生分支遗漏的情况,而git可以同一个工作目录下快速的在几个分支间切换,很容易发现未被合并的分支,简单而快捷的合并这些文件。
  • GIT没有一个全局的版本号,而SVN有
  • GIT的内容完整性要优于SVN:GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏

经常使用的 git 命令?

  • git push -u origin [branch] 将分支推送至远程仓库 git merge [branch] 合并分支到目前分支

git pull 和 git fetch 的区别

  • git fetch 只是将远程仓库的变化下载下来,并没有和本地分支合并。
  • git pull 会将远程仓库的变化下载下来,并和当前分支合并。

git rebase 和 git merge 的区别

  • git merge 会新建一个新的 commit 对象,然后两个分支以前的 commit 记录都指向这个新 commit 记录。这种方法会保留之前每个分支的 commit 历史。
  • git rebase 会先找到两个分支的第一个共同的 commit 祖先记录,然后将提取当前分支这之后的所有 commit 记录,然后将这个 commit 记录添加到目标分支的最新提交后面。经过这个合并后,两个分支合并后的 commit 记录就变为了线性的记录了。

Webpack面试题

001 - 说说你对webpack的理解?解决了什么问题?

现代前端开发已经变得十分的复杂,所以我们开发过程中会遇到如下的问题:

  • 需要通过模块化的方式来开发
  • 使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码
  • 监听文件的变化来并且反映到浏览器上,提高开发的效率
  • JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题
  • 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化

是一个用于现代JavaScript应用程序的静态模块打包工具

这里的静态模块指的是开发阶段,可以被 webpack 直接引用的资源(可以直接被获取打包进bundle.js的资源)。当 webpack处理应用程序时,它会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不再局限js文件),并生成一个或多个 bundle

作用

webpack大而全,很多常用的功能做到开箱即用。有两大最核心的特点:一切皆模块和按需加载

  • 智能解析:对 CommonJS 、 AMD 、ES6 的语法做了兼容
  • 万物模块:对 js、css、图片等资源文件都支持打包
  • 开箱即用:HRM、Tree-shaking等功能
  • 代码分割:可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
  • 插件系统,具有强大的 Plugin 接口,具有更好的灵活性和扩展性
  • 易于调试:支持 SourceUrls 和 SourceMaps
  • 快速运行:webpack 使用异步 IO 并具有多级缓存,这使得 webpack 很快且在增量编译上更加快

002 - webpack与grunt、gulp的不同?

  • Grunt、Gulp是基于任务运⾏的⼯具

    • 它们会⾃动执⾏指定的任务,就像流⽔线,把资源放上去然后通过不同插件进⾏加⼯,它们包含活跃的社区,丰富的插件,能⽅便的打造各种⼯作流。
  • Webpack是基于模块化打包的⼯具

    • ⾃动化处理模块,webpack把⼀切当成模块,当 webpack 处理应⽤程序时,它会递归地构建⼀个依赖关系图 (dependency graph),其中包含应⽤程序需要的每个模块,然后将所有这些模块打包成⼀个或多个 bundle。

003 - 与webpack类似的工具还有哪些?区别?

  • Rollup

    • Rollup 是一款 ES Modules 打包器,从作用上来看,Rollup 与 Webpack 非常类似。不过相比于 Webpack,Rollup要小巧的多

      • 优点

        • 代码效率更简洁、效率更高
        • 默认支持 Tree-shaking
      • 缺点

        • 加载其他类型的资源文件或者支持导入 CommonJS 模块,又或是编译 ES 新特性,这些额外的需求 Rollup需要使用插件去完成
      • 总结

        • rollup并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用CommonJs方式导出成员,并且rollup不支持HMR,使开发效率降低
        • 但是在用于打包JavaScript 库时,rollup比 webpack 更有优势,因为其打包出来的代码更小、更快,其存在的缺点可以忽略
  • Parcel

    • Parcel ,是一款完全零配置的前端打包器,它提供了 “傻瓜式” 的使用体验,只需了解简单的命令,就能构建前端应用程序,Parcel 跟 Webpack 一样都支持以任意类型文件作为打包入口,但建议使用HTML文件作为入口

      • 与webpack对比

        • 支持自动安装依赖,像webpack开发阶段突然使用安装某个第三方依赖,必然会终止dev server然后安装再启动。而Parcel则免了这繁琐的工作流程
        • Parcel能够零配置加载其他类型的资源文件,无须像webpack那样配置对应的loader
        • 打包过程是多进程同时工作,构建速度会比Webpack 快,输出文件也会被压缩,并且样式代码也会被单独提取到单个文件中
  • Snowpack

    • Snowpack,是一种闪电般快速的前端构建工具,专为现代Web设计,较复杂的打包工具(如Webpack或Parcel)的替代方案,利用JavaScript的本机模块系统,避免不必要的工作并保持流畅的开发体验

      • 与webpack对比

        • 开发阶段,每次保存单个文件时,Webpack和Parcel都需要重新构建和重新打包应用程序的整个bundle。而Snowpack为你的应用程序每个文件构建一次,就可以永久缓存,文件更改时,Snowpack会重新构建该单个文件
  • Vite

    • 构成

      • 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的 [模块热更新HMR
      • 套构建指令,它使用 Rollup打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源
    • 与webpack对比

      • vite会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快
      • 利用现代浏览器支持ES Module的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时间
      • 在热模块HMR方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高

004 - webpack、rollup、parcel优劣?

  • webpack适⽤于⼤型复杂的前端站点构建: webpack有强⼤的loader和插件⽣态,打包后的⽂件实际上就是⼀个⽴即执⾏函数,这个⽴即执⾏函数接收⼀个参数,这个参数是模块对象,键为各个模块的路径,值为模块内容。⽴即执⾏函数内部则处理模块之间的引⽤,执⾏模块等,这种情况更适合⽂件依赖复杂的应⽤开发。
  • rollup适⽤于基础库的打包,如vue、d3等: Rollup 就是将各个模块打包进⼀个⽂件中,并且通过 Tree-shaking 来删除⽆⽤的代码,可以最⼤程度上降低代码体积,但是rollup没有webpack如此多的的如代码分割、按需加载等⾼级功能,其更聚焦于库的打包,因此更适合库的开发。
  • parcel适⽤于简单的实验性项⽬: 他可以满⾜低⻔槛的快速看到效果,但是⽣态差、报错信息不够全⾯都是他的硬伤,除了⼀些玩具项⽬或者实验项⽬不建议使⽤。

005 - 如何⽤webpack来优化前端性能?

  • ⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

  • 通过webpack优化前端的手段

    • 代码压缩

      • JS代码压缩

        • 利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件
      • CSS代码压缩

        • 利⽤ cssnano (css-loader?minimize)来压缩css
      • Html文件代码压缩

        • 使用HtmlWebpackPlugin插件来生成HTML的模板时候,通过配置属性minify进行html优化
    • 文件大小压缩

      • 对文件的大小进行压缩,减少http传输过程中宽带的损耗
    • 图片压缩

    • Tree Shaking

      • 将代码中永远不会⾛到的⽚段删除掉(消除死代码)。可以通过在启动webpack时追加参数 --optimize-minimize 来实现;
    • 代码分离

      • 代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
    • 提取公共第三⽅库

      • SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

006 - webpack的构建流程?

Webpack 的运⾏流程是⼀个串⾏的过程,从启动到结束会依次执⾏以下流程,并在这个过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。

  1. 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;

  2. 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编译;

  3. 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;

  4. 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,再找出该模块依赖的模块,再递归本步骤直到所有⼊⼝依赖的⽂件都经过了本步骤的处理;

  5. 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

  6. 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;

  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

007 - 如何提⾼webpack的构建速度?

优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译、提高压缩速度,优化代码等方面入手

  • 优化loader配置

    • 在使用loader时,可以通过配置include、exclude、test属性来匹配文件,缩小文件的搜索范围,优化搜索时间
  • 通过 externals 配置来提取常⽤库,脱离webpack打包,不被打⼊bundle中,从⽽减少打包时间

  • 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 ,让⼀些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间

  • 使⽤ Happypack 实现多线程加速编译

  • 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。

  • 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

  • 利⽤缓存提⾼rebuild效率

008 - 如何提⾼webpack的打包速度?

  • 优化 Loader

    • 对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。

      • 优化 Loader 的文件搜索范围

        • 对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。
      • 还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

  • HappyPack

    • 受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

  • DllPlugin

    • DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

      • 然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中
  • 代码压缩

    • 在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。
  • 其他

    • resolve.extensions

      • 用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
    • resolve.alias

      • 可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
    • module.noParse

      • 如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

009 - Loader和Plugin的不同?

  • 不同的作⽤

    • Loader直译为"加载器"。loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。Webpack将⼀切⽂件视为模块,但是webpack原⽣是只能解析js⽂件,如果想将其他⽂件也打包的话,就会⽤到 loader 。 所以Loader的作⽤是让webpack拥有了加载和解析⾮JavaScript⽂件的能⼒。
    • Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运⾏的⽣命周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
  • 不同的运行时机

    • loader 运行在打包文件之前

    • plugins 在整个编译周期都起作用

  • 不同的⽤法

    • Loader在 module.rules 中配置,也就是说他作为模块的解析规则⽽存在。 类型为数组,每⼀项都是⼀个 Object ,⾥⾯描述了对于什么类型的⽂件( test ),使⽤什么加载( loader )和使⽤的参数( options )
    • Plugin在 plugins 中单独配置。 类型为数组,每⼀项是⼀个 plugin 的实例,参数都通过构造函数传⼊。

010 - webpack有哪些常⻅的Loader?

  • 什么是loader

    • loader 用于对模块的"源代码"进行转换,在 import 或"加载" 模块时预处理文件。webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。在webpack内部中,任何文件都是模块,不仅仅只是js文件。默认情况下,在遇到import或者require加载模块的时候,webpack只支持对js 和 json 文件打包,像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析,当 webpack 碰到不识别的模块的时候,webpack 会在配置的中查找该文件解析规则。关于loader的配置,我们是写在module.rules属性中

      • 配置方式

        • rules是一个数组的形式,因此我们可以配置很多个loader
        • 每一个loader对应一个对象的形式,对象属性test 为匹配的规则,一般情况为正则表达式
        • 属性use针对匹配到文件类型,调用对应的 loader 进行处理
  • webpack有哪些常⻅的Loader

    • file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件
    • url-loader:和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去
    • source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
    • image-loader:加载并且压缩图⽚⽂件
    • babel-loader:把 ES6 转换成 ES5
    • css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
    • style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
    • eslint-loader:通过 ESLint 检查 JavaScript 代码
    • less-loader:通过less-loader将less转换为css

011 - webpack有哪些常⻅的Plugin

webpack中的plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期,目的在于解决loader 无法实现的其他事

  • webpack有哪些常⻅的Plugin

    • define-plugin:定义环境变量
    • html-webpack-plugin:简化html⽂件创建
    • uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码
    • webpack-parallel-uglify-plugin: 多核压缩,提⾼压缩速度
    • webpack-bundle-analyzer: 可视化webpack输出⽂件的体积
    • mini-css-extract-plugin: CSS提取到单独的⽂件中,⽀持按需加载

012 - 编写loader或plugin的思路?

  • Loader像⼀个"翻译官"把读到的源⽂件内容转义成新的⽂件内容,并且每个Loader通过链式操作,将源⽂件⼀步步翻译成想要的样⼦。编写Loader时要遵循单⼀原则,每个Loader只做⼀种"转义"⼯作。 每个Loader的拿到的是源⽂件内容(source),可以通过返回值的⽅式将处理后的内容输出,也可以调⽤ this.callback() ⽅法,将内容返回给webpack。 还可以通过this.async() ⽣成⼀个 callback 函数,再⽤这个callback将处理后的内容输出出去。 此外 webpack 还为开发者准备了开发loader的⼯具函数集——loader-utils 。
  • Plugin的编写灵活了许多。 webpack在运⾏的⽣命周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

013 - bundle,chunk,module是什么?

  • bundle

    • 是由webpack打包出来的⽂件;
  • chunk

    • 代码块,⼀个chunk由多个模块组合⽽成,⽤于代码的合并和分割;
  • module

    • 是开发中的单个模块,在webpack的世界,⼀切皆模块,⼀个模块对应⼀个⽂件,webpack会从配置的 entry中递归开始找出所有依赖的模块。

014 - 怎么配置单⻚应⽤?怎么配置多⻚应⽤?

  • 单⻚应⽤可以理解为webpack的标准模式,直接在 entry 中指定单⻚应⽤的⼊⼝即可

  • 多⻚应⽤可以使⽤webpack的 AutoWebPlugin 来完成简单⾃动化的构建,但是前提是项⽬的⽬录结构必须遵守他预设的规范。 多⻚应⽤中要注意的是

    • 每个⻚⾯都有公共的代码,可以将这些代码抽离出来,避免重复的加载。⽐如,每个⻚⾯都引⽤了同⼀套css样式表
    • 随着业务的不断扩展,⻚⾯可能会不断的追加,所以⼀定要让⼊⼝的配置⾜够灵活,避免每次添加新⻚⾯还需要修改构建配置

015 - webpack 热更新的实现原理?

HMR全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用,例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失,如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用

如果我们修改并保存css文件,确实能够以不刷新的形式更新到页面中,但是,当我们修改并保存js文件之后,页面依旧自动刷新了,这里并没有触发热模块,HMR并不像 Webpack 的其他特性一样可以开箱即用,需要有一些额外的操作,需要去指定哪些模块发生更新时进行HRM

实现原理

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

016 - webpack proxy工作原理?为什么能解决跨域?

webpackproxy,即webpack提供的代理服务,基本行为就是接收客户端发送的请求后转发给其他服务器,其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制),想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server,是 webpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起,关于配置方面,在webpack配置对象属性中通过devServer属性提供,target:表示的是代理到的目标地址 pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite

devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配,属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为/api,值为对应的代理匹配规则:target:表示的是代理到的目标地址 pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite**

工作原理及为什么能解决跨域问题

在开发阶段中,webpack-dev-server会启动一个本地开发的服务器,即跨域所使用的代理服务器,通过利用http-proxy-middleware代理中间件,代理服务器会响应本地请求,继而转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,代理服务器再将数据返回给本地。在此阶段中不涉及任何跨域问题,因为代理服务器跟本地同源,而服务器之间不存在跨域问题,跨域问题是浏览器安全策略限制。

总结:webpack通过配置webpack proxy这个代理服务器(本地服务器),接受客户端的请求继而转发给目标服务器,解决跨域问题。因为跨域问题是由于浏览器同源安全策略限制所导致的,服务器与服务器之间并不存在跨域问题,浏览器和本地服务器(代理服务器)不存在跨域问题