• 工程化分类面试题


    CommonJS

    关键词:

    • 社区标准
    • 使用函数实现
    • 仅node环境支持
    • 动态依赖(需要代码运行后才能确定依赖)
    • 动态依赖是同步执行的

    原理:

    // require函数的伪代码
    function require(path){
      if(该模块有缓存吗){
        return 缓存结果;
      }
      function _run(exports, require, module, __filename, __dirname){
        // 模块代码会放到这里
      }
      
      var module = {
        exports: {}
      }
      
      _run.call(
        module.exports, 
        module.exports, 
        require, 
        module, 
        模块路径, 
        模块所在目录
      );
      
      把 module.exports 加入到缓存;
      return module.exports;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    ES Module

    关键词:

    • 官方标准

    • 使用新语法实现

    • 所有环境均支持

    • 同时支持静态依赖和动态依赖

      静态依赖:在代码运行前就要确定依赖关系

    • 动态依赖是异步的

    • 符号绑定

    关于符号绑定:

    // module a.js
    export var a = 1;
    export function changeA(){
      a = 2;
    }
    
    // index.js
    // 导入位置的符号和导出的符号并非赋值,它们完全是一个东西
    import {a, changeA} from './a.js';
    console.log(a); // 1
    changeA();
    console.log(a); // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    面试题

    1. commonjs 和 es6 模块的区别是什么?

      参考答案:

      1. CMJ 是社区标准,ESM 是官方标准
      2. CMJ 是使用 API 实现的模块化,ESM 是使用新语法实现的模块化
      3. CMJ 仅在 node 环境中支持,ESM 各种环境均支持
      4. CMJ 是动态的依赖,同步执行。ESM 既支持动态,也支持静态,动态依赖是异步执行的。
      5. ESM 导入时有符号绑定,CMJ 只是普通函数调用和赋值
    2. export 和 export default 的区别是什么?

      参考答案:

      export 为普通导出,又叫做具名导出,顾名思义,它导出的数据必须带有命名,比如变量定义、函数定义这种带有命名的语句。在导出的模块对象中,命名即为模块对象的属性名。在一个模块中可以有多个具名导出

      export default 为默认导出,在模块对象中名称固定为 default,因此无须命名,通常导出一个表达式或字面量。在一个模块中只能有一个默认导出。

    3. 下面的模块导出了什么结果?

      exports.a = 'a';
      module.exports.b = 'b';
      this.c = 'c';
      module.exports = {
        d: 'd'
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      参考答案:

      { d: 'd' }
      
      • 1
    4. 下面的代码输入什么结果?

      // module counter
      var count = 1;
      export {count}
      export function increase(){
        count++;
      }
      
      // module main
      import { count, increase } from './counter';
      import * as counter from './counter';
      const { count: c } = counter;
      increase();
      console.log(count);
      console.log(counter.count);
      console.log(c);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    运行本地命令

    使用npx 命令时,它会首先从本地工程的node_modules/.bin目录中寻找是否有对应的命令

    例如:

    npx webpack
    
    • 1

    上面这条命令寻找本地工程的node_modules/.bin/webpack

    如果将命令配置到package.jsonscripts中,可以省略npx

    临时下载执行

    当执行某个命令时,如果无法从本地工程中找到对应命令,则会把命令对应的包下载到一个临时目录,下载完成后执行,临时目录中的命令会在适当的时候删除

    例如:

    npx prettyjson 1.json
    
    • 1

    npx会下载prettyjson包到临时目录,然后运行该命令

    如果命令名称和需要下载的包名不一致时,可以手动指定报名

    例如@vue/cli是包名,vue是命令名,两者不一致,可以使用下面的命令

    npx -p @vue/cli vue create vue-app
    
    • 1

    npm init

    npm init通常用于初始化工程的package.json文件

    除此之外,有时也可以充当npx的作用

    npm init 包名 # 等效于 npx create-包名
    npm init @命名空间 # 等效于 npx @命名空间/create
    npm init @命名空间/包名 # 等效于 npx @命名空间/create-包名
    
    • 1
    • 2
    • 3

    ESLint官网:https://eslint.org/

    ESLint民间中文网:https://eslint.bootcss.com/

    ESLint的由来

    JavaScript是一个过于灵活的语言,因此在企业开发中,往往会遇到下面两个问题:

    • 如何让所有员工书写高质量的代码?

      比如使用===替代==

    • 如何让所有员工书写的代码风格保持统一?

      比如字符串统一使用单引号

    上面两个问题,一个代表着代码的质量,一个代表着代码的风格。

    如果纯依靠人工进行检查,不仅费时费力,而且还容易出错。

    ESLint由此诞生,它是一个工具,预先配置好各种规则,通过这些规则来自动化的验证代码,甚至自动修复

    ESLint的基本使用

    安装

    npm i -D eslint
    
    • 1

    如何验证

    # 验证单个文件
    npx eslint 文件名
    # 验证全部文件
    npx eslint src/**
    
    • 1
    • 2
    • 3
    • 4

    配置规则

    eslint会自动寻找根目录中的配置文件,它支持三种配置文件:

    • .eslintrc JSON格式
    • .eslintrc.js JS格式
    • .eslintrc.yml YAML格式

    这里以.eslintrc.js为例:

    // ESLint 配置
    module.exports = {
      // 配置规则
      rules: {
        规则名1: 级别,
        规则名2: 级别,
        ...
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    每条规则由名称和级别组成

    规则名称决定了要检查什么

    规则级别决定了检查没通过时的处理方式

    所有的规则名称看这里:

    • 官方:https://eslint.org/docs/rules/
    • 中文:https://eslint.bootcss.com/docs/rules/

    所有级别如下:

    • 0 或 ‘off’:关闭规则
    • 1 或 ‘warn’:验证不通过提出警告
    • 2 或 ‘error’:验证不通过报错,退出程序

    在VSCode中及时发现问题

    每次都要输入命令发现问题非常麻烦

    可以安装VSCode插件ESLint,只要项目的node_modules中有eslint,它就会按照项目根目录下的规则自动检测

    使用继承

    ESLint的规则非常庞大,全部自定义过于麻烦

    一般我们继承其他企业开源的方案来简化配置

    这方面做的比较好的是一家叫Airbnb的公司,他们在开发前端项目的时候自定义了一套开源规则,受到全世界的认可

    我们只需要安装它即可

    # 为了避免版本问题,不要直接安装eslint,直接安装下面的包,会自动安装相应版本的eslint
    npm i -D eslint-config-airbnb
    
    • 1
    • 2

    然后稍作配置

    module.exports = {
    	extends: 'airbnb' # 配置继承自 airbnb
    }
    
    • 1
    • 2
    • 3

    在框架中使用

    一般我们使用脚手架搭建工程,在搭建工程时通常都可以直接设置eslint

    企业开发的实际情况

    我们要做什么?

    • 安装好VSCode的ESLint插件
    • 学会查看ESLint错误提示

    关于webpack的诸多问题

    为什么要学习webpack?

    前端有很多打包工具,其中,webpack生态最完整、使用最广泛。

    学习webpack的意义主要有以下几点:

    1. 理解前端开发中出现的常见问题,以及对应的解决办法
    2. 帮助理解常见的脚手架,如vue-cli、create-react-app、umi-js等
    3. 可以脱离脚手架搭建工程,甚至自己完成脚手架开发
    4. 应对工程化方面的进阶面试题

    webpack学习哪个版本?

    截止到2022-01-04,webpack的版本是webpack5,但目前使用的最广泛的是webpack4。

    webpack的版本会不断更新,但它的核心原理是不变的,因此,学习webpack4成为了最好的选择。

    如何学习webpack?

    需要完整的学习课程「webpack详细版」

    学习过程中,把重心放在第一章「Webpack核心功能」和第五章「性能优化」。

    webpack scope hoisting

    详细介绍:https://webpack.docschina.org/plugins/module-concatenation-plugin/

    面试题

    介绍一下 webpack scope hoisting?

    参考答案:

    scope hoisting 是 webpack 的内置优化,它是针对模块的优化,在生产环境打包时会自动开启。

    在未开启scope hoisting时,webpack 会将每个模块的代码放置在一个独立的函数环境中,这样是为了保证模块的作用域互不干扰。

    而 scope hoisting 的作用恰恰相反,是把多个模块的代码合并到一个函数环境中执行。在这一过程中,webpack 会按照顺序正确的合并模块代码,同时对涉及的标识符做适当处理以避免重名。

    这样做的好处是减少了函数调用,对运行效率有一定提升,同时也降低了打包体积。

    但 scope hoisting 的启用是有前提的,如果遇到某些模块多次被其他模块引用,或者使用了动态导入的模块,或者是非 ESM 的模块,都不会有 scope hoisting。

    清除输出目录

    webpack5清除输出目录开箱可用,无须安装clean-webpack-plugin,具体做法如下:

    module.exports = {
      output: {
        clean: true
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    top-level-await

    webpack5现在允许在模块的顶级代码中直接使用await

    // src/index.js
    const resp = await fetch("http://www.baidu.com");
    const jsonBody = await resp.json();
    export default jsonBody;
    
    • 1
    • 2
    • 3
    • 4

    目前,top-level-await还未成为正式标准,因此,对于webpack5而言,该功能是作为experiments发布的,需要在webpack.config.js中配置开启

    // webpack.config.js
    module.exports = {
      experiments: {
        topLevelAwait: true,
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    打包体积优化

    webpack5对模块的合并、作用域提升、tree shaking等处理更加智能

    打包缓存开箱即用

    webpack4中,需要使用cache-loader缓存打包结果以优化之后的打包性能

    而在webpack5中,默认就已经开启了打包缓存,无须再安装cache-loader

    默认情况下,webpack5是将模块的打包结果缓存到内存中,可以通过cache配置进行更改

    const path = require("path");
    
    module.exports = {
      cache: {
        // 缓存类型,支持:memory、filesystem
        type: "filesystem", 
        // 缓存目录,仅类型为 filesystem 有效
        cacheDirectory: path.resolve(__dirname, "node_modules/.cache/webpack"), 
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    关于cache的更多配置参考:https://webpack.docschina.org/configuration/other-options/#cache

    资源模块

    webpack4中,针对资源型文件我们通常使用file-loaderurl-loaderraw-loader进行处理

    由于大部分前端项目都会用到资源型文件,因此webpack5原生支持了资源型模块

    详见:https://webpack.docschina.org/guides/asset-modules/

    解释一下 npm 模块安装机制是什么?

    参考答案:

    1. npm 会检查本地的 node_modules 目录中是否已经安装过该模块,如果已经安装,则不再重新安装
    2. npm 检查缓存中是否有相同的模块,如果有,直接从缓存中读取安装
    3. 如果本地和缓存中均不存在,npm 会从 registry 指定的地址下载安装包,然后将其写入到本地的 node_modules 目录中,同时缓存起来。

    npm 缓存相关命令:

    # 清除缓存
    npm cache clean -f
    
    # 获取缓存位置
    npm config get cache
    
    # 设置缓存位置
    npm config set cache "新的缓存路径"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    模块联邦

    在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构

    在微前端架构中,不同的工程可能出现下面的场景

    image-20210122172549530

    这涉及到很多非常棘手的问题:

    • 如何避免公共模块重复打包
    • 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
    • 如何管理依赖的不同版本
    • 如何更新模块
    • ......

    webpack5尝试着通过模块联邦来解决此类问题

    示例

    现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。

    初始化工程

    home项目

    安装

    # 初始化 package.json
    npm init -y 
    
    # 安装依赖
    npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
    npm i jquery
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    修改package.json

    "scripts": {
       "build": "webpack",
       "dev": "webpack serve"
     }
    
    • 1
    • 2
    • 3
    • 4

    配置webpack.config.js

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
      entry: './src/index.js',
      mode: 'development',
      devtool: 'source-map',
      devServer: {
        port: 8080,
      },
      output: {
        clean: true,
      },
      plugins: [ new HtmlWebpackPlugin() ]
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码

    // src/now.js
    
    import $ from 'jquery';
    
    export default function (container) {
      const p = $('

    ').appendTo(container).text(new Date().toLocaleString()); setInterval(function () { p.text(new Date().toLocaleString()); }, 1000); } // src/bootstrap.js import $ from 'jquery'; import now from './now'; // 生成首页标题 $('

    ').text('首页').appendTo(document.body); // 首页中有一个显示当前时间的区域 now($('
    ').appendTo(document.body)); // src/index.js import('./bootstrap')
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    active项目

    安装

    # 初始化 package.json
    npm init -y 
    
    # 安装依赖
    npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
    npm i jquery
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    修改package.json

    "scripts": {
       "build": "webpack",
       "dev": "webpack serve"
     }
    
    • 1
    • 2
    • 3
    • 4

    配置webpack.config.js

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
      entry: './src/index.js',
      mode: 'development',
      devtool: 'source-map',
      devServer: {
        port: 3000,
      },
      output: {
        clean: true,
      },
      plugins: [ new HtmlWebpackPlugin() ]
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码

    // src/news.js
    
    import $ from 'jquery';
    
    export default function (container) {
      const ul = $('
      ').appendTo(container); let html = ''; for (var i = 1; i <= 20; i++) { html += `
    • 新闻${i}
    • `
      ; } ul.html(html); } // src/bootstrap.js import $ from 'jquery'; import news from './news'; // 生成活动页标题 $('

      ').text('活动页').appendTo(document.body); // 活动页中有一个新闻列表 news($('
      ').appendTo(document.body)); // src/index.js import('./bootstrap')
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

    暴露和引用模块

    active项目需要使用home项目的now模块

    home项目暴露now模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 模块联邦的名称
          // 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
          name: 'home',
          // 模块联邦生成的文件名,全部变量将置入到该文件中
          filename: 'home-entry.js',
          // 模块联邦暴露的所有模块
          exposes: {
            // key:相对于模块联邦的路径
            // 这里的 ./now 将决定该模块的访问路径为 home/now
            // value: 模块的具体路径
            './now': './src/now.js',
          },
        }),
      ],
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    active项目引入now模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 远程使用其他项目暴露的模块
          remotes: {
            // key: 自定义远程暴露的联邦名
            // 比如为 abc, 则之后引用该联邦的模块则使用 import "abc/模块名"
            // value: 模块联邦名@模块联邦访问地址
            // 远程访问时,将从下面的地址加载
            home: 'home@http://localhost:8080/home-entry.js',
          },
        }),
      ],
    };
    
    // src/bootstrap.js
    // 远程引入时间模块
    import now from 'home/now'
    now($('
    ').appendTo(document.body));
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    home项目需要使用active项目的news模块

    active项目暴露news模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 模块联邦的名称
          // 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
          name: 'active',
          // 模块联邦生成的文件名,全部变量将置入到该文件中
          filename: 'active-entry.js',
          // 模块联邦暴露的所有模块
          exposes: {
            // key:相对于模块联邦的路径
            // 这里的 ./news 将决定该模块的访问路径为 active/news
            // value: 模块的具体路径
            './news': './src/news.js',
          },
        }),
      ],
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    home项目引入news模块

    // webpack.config.js
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 远程使用其他项目暴露的模块
          remotes: {
            // key: 自定义远程暴露的联邦名
            // 比如为 abc, 则之后引用该联邦的模块则使用 import "abc/模块名"
            // value: 模块联邦名@模块联邦访问地址
            // 远程访问时,将从下面的地址加载
            active: 'active@http://localhost:3000/active-entry.js',
          }
        }),
      ],
    };
    
    // src/bootstrap.js
    // 远程引入新闻模块
    import news from 'active/news'
    news($('
    ').appendTo(document.body));
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    处理共享模块

    两个项目均使用了jquery,为了避免重复,可以同时为双方使用shared配置共享模块

    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          // 配置共享模块
          shared: {
            // jquery为共享模块
            jquery: {
              singleton: true, // 全局唯一
            },
          },
        }),
      ],
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    效率技巧│十分钟学会 xmind 思维导图的使用
    模型压缩-对模型结构进行优化
    Linux操作系统之文件系统详解
    数据库系统原理与应用教程(003)—— MySQL 安装与配置:手工配置 MySQL(windows 环境)
    QT连接数据库
    matlab几种求解器的选择fsolve-sole-vpasolve
    TiDB在科捷物流神州金库核心系统的应用与实践
    spring中的bean生命周期
    【Rust基础③】方法method、泛型与特征
    K8S MetalLB工作原理详解:地址分配、广播模式(Layer2模式、BGP模式)、工作過程
  • 原文地址:https://blog.csdn.net/qq_46262422/article/details/126175759