SPRINT-9

98 阅读7分钟

353、Webpack有哪些核心概念

Webpack是一个功能强大的打包工具,它能够将多个 JavaScript、CSS、图片等资源打包成一个或多个 bundle 文件。

它有一些核心概念贯穿整个构建流程。下面是Webpack中的几个核心概念:

  1. Entry(入口):Webpack在打包时需要从哪个文件开始构建依赖关系图,就是入口。可以设置多个入口文件,以生成多个输出文件。

  2. Output(输出):打包后的文件放在哪里,以及如何命名这些文件。可以指定输出目录、文件名、公共路径等。

  3. Loader(模块加载器):Webpack只能处理JavaScript文件,而其他类型的文件如CSS、图片等需要通过Loader转换才能被Webpack处理。Loader用于对模块内容进行转换处理。

  4. Plugin(插件):Plugin可以用于执行各种任务,例如打包优化、错误处理和环境变量注入等。Webpack本身只提供了一些基本的Plugin,但社区中有很多第三方Plugin可供使用。

  5. Mode(模式):Webpack提供了三种模式:development、production和none。不同的模式会启用不同的Webpack内置Plugin和Loader,以便于开发和生产环境的优化。

  6. Chunk(代码块):Webpack在打包时会把所有相关联的模块组成一个Chunk。可以通过Code Splitting技术将代码拆分成多个Chunk,以实现按需加载。

  7. Module(模块):Webpack把每个文件都看作一个模块,它可以是JavaScript、CSS、图片等。这些模块通过依赖关系进行组合,构成整个应用程序。

以上是Webpack中的七个核心概念,它们共同构成了Webpack的打包机制。熟练掌握这些概念可以帮助我们更好地了解和使用Webpack。

354、【阿里巴巴】Webpack中具体要配哪些东西

Webpack 是一个模块打包工具,它的主要作用就是将多个模块打包成一个或多个文件,以便于浏览器加载和执行。要对 Webpack 进行配置,需要配合一些常用的插件和加载器来完成不同的任务。

以下是配置 Webpack 时需要用到的一些常见组件:

  • entry:入口文件,Webpack 会从入口文件开始递归地寻找所有依赖的模块进行打包。
  • output:输出文件,Webpack 打包后的文件将被输出到指定的目录中。
  • module:模块配置,指定各种类型的文件应该使用哪些加载器进行处理和转换,例如 CSS、图片、字体等。
  • plugins:插件列表,用于执行各种扩展任务,例如压缩、混淆代码、提取公共代码等。
  • resolve:路径解析,用于指定 Webpack 如何寻找模块,可以自定义查找路径和文件后缀名等。
  • devServer:开发服务器,提供热更新、代理请求、自动刷新等功能,方便开发调试。

以上是 Webpack 配置时需要用到的主要组件,当然还有很多其他的配置选项和插件,根据具体项目需求可选择添加或修改。同时,在配置 Webpack 时还需要注意一些优化技巧,如分离 vendor 和 webpack runtime,使用 ES6 模块化语法等,以提高 Webpack 打包的性能和效率。

355、Webpack构建流程简单说一下

  • 初始化参数: 解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果;

  • 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译;

  • 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去;

  • 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

  • 完成模块编译:递归完成后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry或分包配置生成代码块chunk

  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。

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

356、Webpack 的热更新原理

Webpack的热更新(Hot Module Replacement,HMR)是指在应用程序运行过程中,对被修改的模块进行增量更新,而不需要进行整体的刷新或重启。
Webpack的热更新原理主要分为以下两个方面:

  1. Webpack Dev Server:是Webpack官方提供的一个小型Express服务器,它可以在开发环境下模拟真实的生产环境,支持热更新、自动刷新等功能。在配置文件中设置devServer.hot为true即可开启热更新功能。

  2. Hot Module Replacement:是Webpack Dev Server的核心插件,它通过WebSocket在客户端和服务器之间建立一个通信管道,把修改后的代码实时传输到客户端,在不刷新页面的情况下替换掉原有的模块,从而实现热更新。

下面是HMR的工作原理:

  • 当开发者修改了一个模块并保存后,Webpack会编译这个模块并向Webpack Dev Server发送更新请求。
  • Webpack Dev Server接收到更新请求后,会使用Hot Module Replacement插件,将更新的部分以JSON格式返回到客户端,通知客户端进行热更新。
  • 客户端收到更新通知后,会先尝试使用新模块进行热更新,如果无法更新,则会自动刷新整个页面。

总之,Webpack的热更新机制实现了对修改后的模块进行增量更新,更新速度快,效率高,并且不会造成页面的重载或刷新。这不仅可以提高开发效率,也可以提高应用程序的性能和用户体验。

357、Webpack常见的优化方案

  1. 升级 webpack 版本,3升4,实测是提升了几十秒的打包速度
  2. 使用Tree Shaking和Scope Hoisting来减少代码体积和模块构建时间,其中Tree Shaking可以去除未使用的代码,而Scope Hoisting可以将模块内的代码尽量合并到一个函数(单一作用域)中,以减少函数声明和闭包的数量。
  3. 使用splitChunksPlugin插件来将公共代码抽离成单独的chunk,以减少代码重复和提高缓存命中率。
  4. 合理配置resolve.alias和resolve.extensions选项来减少Webpack查找文件的时间。
  5. 针对生产环境,可以开启代码压缩以及多进程并行处理等优化方式,以减少构建时间和服务器负载。
  6. 使用DLLPlugin和DllReferencePlugin来预先编译一些稳定不变的代码,如类库,以减少每次构建的时间。
  7. 使用HappyPack来启用多线程并发处理,以加速代码构建和增强开发体验。
  8. 对于图片、字体等资源文件,可以通过url-loader和file-loader等loader设置较小的limit值,将文件转换成base64编码的字符串内嵌在js文件中,以减少http请求次数。

358、Webpack优化之减少打包时间

  1. 优化 Loader
  2. HappyPack:将 Loader 的同步执行转换为并行的
  3. DllPlugin: 可以将特定的类库提前打包然后引入
  4. 代码压缩
  5. 可以通过一些小的优化点来加快打包速度:
    • resolve.extensions:用来表明文件后缀列表
    • resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
    • module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

技术详解

优化 Loader

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

首先我们优化 Loader 的文件搜索范围

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夹下查找
        include: [resolve('src')],
        // 不会去查找的路径
        exclude: /node_modules/
      }
    ]
  }
}

对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。

当然这样做还不够,还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

loader: 'babel-loader?cacheDirectory=true'

HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的内容对应下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]

DllPlugin

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

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: '[name]-[hash]',
      // 该属性需要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

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

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出来的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

代码压缩

在 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 不扫描该文件,这种方式对于大型的类库很有帮助。

359、Webpack优化之减少打包体积

Webpack 优化主要包括减少打包体积和提高打包速度两个方面。针对减少打包体积这个问题,可以采取以下措施:

修改 mode

在 Webpack 配置文件中设置 mode 字段,可以让 Webpack 自动启用一些生产环境的优化功能,例如代码压缩和 tree shaking。将 mode 设置为 production 可以让 Webpack 对代码进行更好的优化,从而减少打包体积。

使用 tree shaking

Tree shaking 是一个通过静态分析剔除没有用到的代码的过程。在 Webpack 中,通过使用 ES6 的 import/export 语法,可以实现 tree shaking。确保引入的模块只导出了需要的代码,并在配置文件中启用 optimization.minimize 和 optimization.usedExports 选项,以实现更好的削减。

排除无用的 Polyfill

在项目中可能会使用一些 Polyfill 来支持旧版本浏览器。但是一些完全不需要的 Polyfill,例如 Promise、Map 等,会增加打包体积。可以通过 Babel 的 useBuiltIns 选项或者直接在代码中判断浏览器是否支持相应的 API 来实现优化。

按需加载

按需加载是指只在需要时才加载代码。Webpack 提供了三种按需加载方式:分离代码(splitChunks)、动态导入和使用第三方库(例如 lodash)的按需加载功能。分离代码和动态导入可以让某些代码在需要时才加载,从而减少打包体积。

以上是减少 Webpack 打包体积的一些常见方法,实际上还有很多其他的方法可以用来优化打包体积。需要根据实际情况选择合适的优化方案。

360、如何对bundle体积进行监控和分析

可以使用 Webpack 插件 webpack-bundle-analyzer 对打包后的 bundle 进行体积监控和分析。具体步骤如下:

安装 webpack-bundle-analyzer:在命令行工具中运行 npm install --save-dev webpack-bundle-analyzer
在 Webpack 配置文件中引入该插件:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

运行 Webpack 打包命令,并观察输出的信息:

webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json dist

其中,第一行命令将 Webpack 打包过程的 profile 数据以 JSON 格式输出到 stats.json 文件中;
第二行命令使用 webpack-bundle-analyzer 工具读取 stats.json 文件并生成 HTML 报告文件,报告文件会在浏览器中自动打开。

通过查看报告文件,可以清晰地看到打包后的 bundle 中各个模块的大小及占比情况,从而提供优化方案。
除了使用 webpack-bundle-analyzer 插件外,还可以使用其他一些工具进行 bundle 体积分析,例如 Source Map Explorer、rollup-plugin-visualizer 等。

361、有哪些常见的Loader?你用过哪些Loader?

Webpack 中有很多常见的 Loader 可以用来处理各种类型的文件。以下是几个常见的 Loader:

  • babel-loader:将 ES6+ 的 JavaScript 代码转换成 ES5 代码;
  • css-loader / style-loader:用于处理 CSS 文件,css-loader 可以将 CSS 文件转换为模块,然后通过 JavaScript 动态加载,style-loader 可以将 CSS 样式插入到 HTML 的 head 中;
  • file-loader / url-loader:用于处理图片、字体等文件,file-loader 将文件输出到指定的目录中,并返回文件的 URL 地址,url-loader 可以将小的图片或字体文件转为 base64 编码的 DataURL,从而减少 HTTP 请求。
  • less-loader / sass-loader:用于处理 LESS 或 SCSS 等预处理器的样式文件,将其转化为 CSS 文件;
  • ts-loader / awesome-typescript-loader:用于处理 TypeScript 文件,ts-loader 是官方提供的 TypeScript 加载器,awesome-typescript-loader 支持更多的编译选项和快速编译功能。

以上是一些常见的 Loader,同时还有很多其他的 Loader 在 Webpack 社区中得到广泛的使用。

362、【阿里巴巴】Webpack 怎么匹配到对应的loader的

Webpack 匹配 Loader 的过程是通过 Module Rules(模块规则)实现的,其中包括两个关键的属性:test 和 use。

  • test 属性:用于指定需要匹配的文件类型(通常是正则表达式),例如:/.(js|jsx)/,/.css/, /.css/, /.scss$/ 等等;
  • use 属性:用于指定需要使用的 Loader,可以是一个或多个 Loader,例如:'babel-loader', 'style-loader', 'css-loader', 'sass-loader' 等等。

当 Webpack 要加载一个文件时,它会根据文件的路径和配置中的 test 属性进行匹配,如果匹配成功就会使用对应的 Loader 进行转换。这些 Loader 会被串联起来,按照从右到左的顺序执行,最终返回转换后的代码。

例如,假设我们有一个 JavaScript 文件 main.js,我们希望通过 Babel 将其转换为 ES5 的代码。那么我们可以在 Webpack 配置文件中添加以下规则:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      }
    ]
  }
}

上述配置告诉 Webpack,当遇到一个以 .js 结尾的文件时,就会使用 babel-loader 进行转换。Webpack 在读取文件时,会根据文件路径和配置中的 test 属性进行匹配,如果能够匹配成功,则会调用 use 属性指定的 Loader 进行转换。

需要注意的是,当使用多个 Loader 进行转换时,它们的执行顺序非常重要。如果顺序不正确,可能会导致转换失败或者产生意想不到的结果。因此,我们在配置 Loader 时一定要仔细检查它们的执行顺序是否正确,尤其是在复杂的项目中。

363、在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作

在实际工程中,配置文件经常会很长,其中包含了各种 Loader、Plugin、参数等等。为了保证这些 Loader 按照预想方式工作,我们可以采用以下几种方式:

  1. 熟悉 Loader 的使用和配置方式。不同的 Loader 有不同的参数配置,需要仔细查看官方文档,熟悉其功能和使用方法。根据不同的需求合理配置参数,保证 Loader 能够按照预期工作。

  2. 模块化配置。将 Webpack 配置文件拆分成多个模块进行管理,每个模块负责处理一个特定的 Loader 或 Plugin。这样不仅可以使 Webpack 配置更为清晰,还可以方便地重用和维护。

  3. 使用 Webpack Loader 和 Plugin 的调试功能。Webpack 提供了许多 Loader 和 Plugin 的调试功能,可以方便快捷地找到问题所在。例如,使用 --display-error-details 参数可以在控制台输出详细的错误信息,使用 --debug 参数可以启用调试模式,输出更详细的构建过程信息等等。

  4. 及时更新 Loader 和 Plugin。Web 技术发展很快,Loader 和 Plugin 也在不断更新迭代。及时更新这些工具可以解决一些已知的问题和 Bug,同时还可以获得更好的性能和更多的功能。

通过以上几种方式,可以提高配置文件的可读性和可维护性,保证各个 Loader 能够按照预想工作,以及最终生成正确的构建结果。

364、简单描述一下编写loader的思路

自定义 Loader 是 Webpack 的一个重要扩展能力,它可以让你自定义处理模块的方法,并返回最终的模块。下面是编写 Loader 的基本思路:

  1. 确定需要处理的资源类型:Loader 可以用于处理各种类型的资源,例如 JavaScript、CSS、图片等等,首先需要确定你要处理的资源类型。

  2. 编写 Loader 函数:在编写 Loader 时,需要将其作为一个 Node.js 模块导出一个函数。这个函数接受一个参数(通常命名为 source),source 参数表示源代码或者资源文件的内容。

  3. 在函数中实现处理逻辑:在函数中实现对源内容的处理逻辑,可以使用各种 Node.js 模块和第三方库来操作源码。

  4. 返回处理后的结果:处理完成后,要使用 this.callback() 方法返回处理后的结果,或者将它传递给下一个 Loader。callback() 方法接受两个参数:错误信息和处理后的结果。

  5. 配置 Webpack 中的 Loader:在 Webpack 配置文件中,使用 module.rules 数组配置 Loader,每个 Loader 的配置包含 test、use、options 等属性,其中 test 属性用于指定需要匹配的文件类型,use 属性用于指定使用哪些 Loader 进行处理,options 则可以用来指定 Loader 的选项。

总体而言,编写 Loader 主要的思路就是确定需要处理的资源类型,编写 Loader 函数,实现处理逻辑,最终将处理结果返回,并在 Webpack 中进行配置。实际上,编写 Loader 的具体细节会因 Loader 的种类和功能而有所不同,但以上的步骤是通用的。

365、Webpack中babel-loader都做了哪些事情

Es6+js代码转为 es5,以便在更多的浏览器上运行
jsx ts 等转为普通的js
代码压缩,以减小项目体积 cacheCompression
代码缓存,为了使重新编译的时间更短 cacheDirectory
支持异步操作,以便在项目中更好地处理异步操作

366、有哪些常见的Plugin?你用过哪些Plugin?

Webpack 是一个非常灵活和可扩展的构建工具,它提供了许多内置的和第三方的 Plugin,用于增强其功能和扩展性。以下是一些常见的 Webpack Plugin:

  1. HtmlWebpackPlugin:用于生成 HTML 文件,并将打包后的 JavaScript 和 CSS 自动引入到 HTML 文件中。

  2. MiniCssExtractPlugin:用于将样式文件从 JavaScript 中提取出来,并分离出单独的 CSS 文件。

  3. CleanWebpackPlugin:用于清除构建目录中的无用文件,在重新构建之前保持代码库干净和整洁。

  4. DefinePlugin:用于定义全局常量,例如环境变量等等。

  5. CopyWebpackPlugin:用于复制文件和文件夹到构建目录中,例如图片、字体等等。

  6. UglifyJsPlugin:用于压缩 JavaScript 代码,减少文件大小。

  7. ProvidePlugin:用于自动加载模块,例如 jQuery、lodash 等等。

  8. ExtractTextWebpackPlugin:用于将样式表从 JavaScript 中提取出来,并生成单独的 CSS 文件。

  9. BundleAnalyzerPlugin:用于分析构建结果,并生成一个交互式的网页报告,帮助开发者找到构建过程中的优化点。

  10. HotModuleReplacementPlugin:用于启用热模块替换功能,在开发过程中实现快速热更新,提升开发效率。

以上是一些常见的 Webpack Plugin,当然还有许多其他的插件可供使用。作为一个优秀的前端工程师,需要根据项目的需求来选择和配置合适的 Plugin,以提高构建效率、减少代码体积、优化用户体验等等。

367、简单描述一下编写Plugin的思路

编写 Plugin 的基本思路如下:

  1. 确定需要监听的 webpack 事件:Plugin 可以监听多个 webpack 事件,例如构建完成、文件生成前后等等,首先需要确定需要监听的事件。

  2. 编写 Plugin 类:Plugin 是一个 JavaScript 类,需要定义一个名称并实现 apply 方法,apply 方法接受一个 compiler 参数,该参数包含了 webpack 的整个生命周期。

  3. 在 apply 方法中实现监听逻辑:在 apply 方法中使用 compiler 对象监听 webpack 事件,并且可以访问 webpack 内部的数据结构和 API,实现相应的处理逻辑。

  4. 定义 Plugin 配置选项:如果需要为 Plugin 提供配置选项,可以在 apply 方法中提供一个 options 参数,接收配置选项的值。

  5. 添加插件到 webpack 配置中:将自定义的 Plugin 添加到 webpack 配置的 plugins 数组中即可生效。

总体而言,编写 Plugin 的过程就是确定需要监听的 webpack 事件,编写 Plugin 类并实现 apply 方法,在 apply 中实现监听逻辑并访问 webpack 的 API,提供配置选项并添加插件到 webpack 配置中。不同的 Plugin 类实现起来可能有所不同,但以上的步骤是通用的。

368、说一说Loader和Plugin的区别

Loader和Plugin是Webpack中两个重要的概念,它们都能够用于处理模块或扩展Webpack的功能,但两者的区别还是比较明显的。

  • Loader:Loader在Webpack中的作用是将各种类型的文件转换为Webpack能够处理的模块。Webpack本身只能处理JS和JSON文件,对于其他类型的文件(如CSS、图片、字体等),需要使用特定的Loader进行转换。比如,可以使用 css-loaderstyle-loader 将CSS文件转换为JS代码,并注入到HTML中。使用Loader时可以通过配置匹配规则来指定需要转换的文件类型,以及使用哪些Loader对其进行处理。

  • Plugin:Plugin可以用于扩展Webpack的功能,例如压缩代码、生成HTML文件、拷贝静态资源等操作。Plugin的实现通常是通过钩子机制,在Webpack构建的不同阶段中注入自定义的代码来实现。Plugin一般需要在Webpack的配置中进行声明和引入,而且一个Plugin可能会涉及多个Webpack阶段的处理。

总的来说,Loader主要用于文件的转换,而Plugin则更多用于扩展Webpack功能,例如优化打包大小、自动生成HTML文件、拷贝静态资源等等。两者结合起来可以完成复杂的构建任务,使得Webpack具备更强大的定制和扩展性。

369、source map是什么?生产环境怎么用?

Source Map是一种映射关系(mapping)文件,可以将编译后的JavaScript代码映射回原始的源码。当生产环境出现错误时,Source Map可以帮助我们快速定位错误的代码行,并且不需要查看压缩、混淆后的代码。

在开发环境中,一般都会开启Source Map,以便在调试代码时能够更容易地定位问题。具体来说,我们可以在Webpack等构建工具的配置中,设置 devtool 选项为 eval-source-mapcheap-module-eval-source-map 等值,这样会在生成的代码中添加 sourceMappingURL,指向原始的源码文件。

而在生产环境中,Source Map虽然也有很大的帮助作用,但是由于其可能会暴露重要的信息,因此一般不会直接使用。相反,我们可以只在出现错误时才让浏览器请求Source Map文件,而且只允许特定的用户或IP地址访问这些文件。同时,我们还可以使用一些工具将Source Map文件与JavaScript代码分开存储,避免它们被攻击者利用。

线上环境一般有三种处理方案:

  • hidden-source-map:借助第三方错误监控平台 Sentry 使用
  • nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
  • sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)
  • 注意:避免在生产中使用 inline-eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

综上所述,虽然Source Map在生产环境中的使用受到一定限制,但它仍然是一个非常有用的调试工具,可以极大提高代码的可维护性和开发效率。

370、Webpack中的模块打包原理

Webpack的核心功能就是将多个JS文件打包成一个或多个JS文件,实现模块化开发和依赖管理。它在处理模块时,主要执行以下步骤:

  1. 解析入口文件:Webpack会根据entry配置项找到入口文件,并从该文件开始递归解析其他模块,形成一个依赖关系图。

  2. 解析模块依赖:Webpack会遍历入口文件和其依赖的所有模块,通过解析require/import语句找到依赖的模块,然后递归解析这些模块的依赖关系,直到所有依赖都被找到为止。

  3. 转换代码:Webpack会把所有的模块转换成一个函数,并为每个模块添加唯一标识符,用于在运行时区分不同的模块。

  4. 模块编译:Webpack会按照模块之间的依赖关系,把它们打包成一个或多个文件,输出给浏览器执行。

  5. 输出文件:Webpack会根据output选项指定的目录和文件名,把打包后的文件存储到指定的位置。

以上是Webpack的基本模块打包原理,其中第二步的模块依赖关系的解析过程尤为重要,因为这关系到Webpack是否能够正确的把所有的模块打包进最终的输出文件中。
在解析依赖关系时,Webpack会根据不同的模块类型使用不同的模块解析器,例如可以使用Babel解析ES6语法,使用CSS Loader解析CSS文件等等。

总的来说,Webpack的模块打包原理就是通过递归解析模块之间的依赖关系,将所有的模块打包成为一个或多个文件,并通过一系列的插件和loader对代码进行处理和优化。
这样可以减少HTTP请求次数、提高页面加载速度,并大大提高了前端开发的效率和可维护性。

371、Webpack中的文件监听原理

Webpack的文件监听是指在代码发生变化时,Webpack自动重新构建代码,并将最新的代码输出到目标文件夹中。这个功能在开发过程中非常有用,可以避免手动刷新页面。

Webpack的文件监听原理主要分为以下两个步骤:

  • 监听文件变化

Webpack使用Node.js内置的fs模块对指定的目录下的文件进行轮询检查。当文件有变化时,Webpack会发出一个文件变化的事件。

  • 对模块进行重新构建和输出

当Webpack检测到文件发生变化时,它会重新构建所有的模块,并将最新的代码输出到目标目录中。这个过程涉及到以下几个步骤:

  • 重新编译发生变化的模块

Webpack会根据模块之间的依赖关系图,重新编译发生变化的模块以及受影响的模块。

  • 输出最新的代码

当所有的模块都编译完成后,Webpack会将最新的代码输出到目标目录中。如果使用了watch选项,则Webpack会保持运行状态,等待下一次文件变化事件的到来。

需要注意的是,Webpack监听文件变化是通过轮询实现的,这意味着Webpack需要不断地扫描文件夹中的文件,对于大型项目或者文件夹较多的项目,这种方式会对性能造成一定的影响。因此,建议在开发过程中只监听需要处理的文件,避免对整个项目进行监听。

372、Wepack中的代码分割的本质是什么

Webpack中的代码分割是指将代码拆分成多个小块(chunks),以实现按需加载,从而提高页面加载速度和性能。代码分割的本质是将应用程序打包成多个文件,在应用程序运行时根据需要动态加载。

代码分割的意义在于:

加快初始加载速度

将JavaScript代码进行拆分后,可以减小每个文件的大小,从而缩短文件下载的时间,加快初始加载速度。

优化用户体验

通过按需加载,可以使页面更快地响应交互操作,提升用户体验。

降低成本维护

随着代码量的增大,应用程序的复杂度也会增加,代码分割可以降低代码维护的成本,提高代码的可读性和可维护性。

Webpack中实现代码分割的方式有多种,其中最常用的是使用动态导入(Dynamic Imports)机制。通过动态导入机制,可以将应用程序拆分成多个代码块,当需要时再动态地加载相应的代码块。

总之,代码分割是Webpack中一个非常重要的特性,它可以优化用户体验、提高页面性能,并且减少代码维护的成本。

什么是动态导入机制

动态导入(Dynamic Imports)机制是指在代码运行时动态地导入 JavaScript 模块。与传统的静态导入方式不同,动态导入可以让开发者只在需要使用模块时再去加载它们,避免一开始就加载所有的模块,从而提高应用程序的性能。

动态导入有多种形式,其中最常见的是使用 import() 方法,这个方法返回一个 Promise 对象,在 Promise 对象的 then 方法中可以获取到动态导入的模块。例如:

import('./modules/myModule.js')
  .then((module) => {
    // 使用动态导入的模块
  })
  .catch((err) => {
    // 处理错误
  });

另一种常见的动态导入形式是使用 require.ensure 方法,这个方法可以将一个或多个模块包装成一个代码块(chunk),并指定这个代码块的名称和在需要时加载它的回调函数。例如:

require.ensure(['./modules/myModule.js'], function(require) {
  var myModule = require('./modules/myModule.js');
  // 使用动态导入的模块
}, function(err) {
  // 处理错误
}, 'myChunkName');

需要注意的是,浏览器对动态导入的支持还不够完善,可能不是所有的浏览器都支持。因此,在使用动态导入时需要特别注意浏览器的兼容性。

373、文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
  • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
  • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

JS的文件指纹设置,设置 output 的 filename,用 chunkhash

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    }
}

CSS的文件指纹设置,设置 MiniCssExtractPlugin 的 filename,使用 contenthash。

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    },
    plugins:[
        new MiniCssExtractPlugin({
            filename: `[name][contenthash:8].css`
        })
    ]
}

图片的文件指纹设置,设置file-loader的name,使用hash。

占位符名称及含义

  • ext 资源后缀名

  • name 文件名称

  • path 文件的相对路径

  • folder 文件所在的文件夹

  • contenthash 文件的内容hash,默认是md5生成

  • hash 文件内容的hash,默认是md5生成

  • emoji 一个随机的指代文件内容的emoji

    const path = require('path'); module.exports = { entry: './src/index.js', output: { filename:'bundle.js', path:path.resolve(__dirname, 'dist') }, module:{ rules:[{ test:/.(png|svg|jpg|gif)$/, use:[{ loader:'file-loader', options:{ name:'img/[name][hash:8].[ext]' } }] }] } }

374、dev的时候webpack做了什么事情

当我们在Terminal中输入npm run dev时,实际上是在运行package.json中的scripts中定义的dev指令。我们可以在package.json文件中看到类似如下的代码:

{
  "scripts": {
    "dev": "webpack-dev-server --mode development --open"
  }
}

在运行npm run dev命令时,npm会按照scripts中定义的指令进行执行。

具体来说,在这个例子中,运行指令npm run dev会启动 webpack-dev-server,并使用它提供的功能实现如下:

  1. 根据配置文件(通常为webpack.config.js)读取项目中的所有代码,并将其打包成一个或多个JavaScript文件。这些文件通常包含了项目中所有的JavaScript、CSS、图片和其他资源。

  2. 启动本地服务器,并使用 webpack-dev-middleware 将所有生成的文件提供给服务器。webpack-dev-middleware 会根据请求动态生成文件内容,并使用浏览器实时刷新页面。

  3. 启动浏览器,并打开指定的URL。这通常是localhost:3000或localhost:8080等。

  4. 监听文件更改事件。当我们修改项目中的任何文件时,Webpack Dev Server会检测到更改并重新编译相关的代码。同时,浏览器也会自动刷新以显示最新的更改。

总之,运行npm run dev命令实际上是启动了Webpack Dev Server,并使用它提供的功能来实现快速开发和自动构建。由于Webpack Dev Server提供了很多有用的工具和功能,使得我们能够更加高效地进行前端开发。

375、什么是神奇注释或者魔术注释

  • 神奇注释(Magic Comments)是在 JavaScript 源代码中添加特定注释,以影响 Webpack 打包时的行为的一种技术。
  • Webpack 是一种现代 JavaScript 应用程序的静态模块打包器。它将整个应用程序视为一个依赖关系图谱,并生成一个或多个 bundles。
  • 神奇注释可以在这些 Bundles 中添加一些额外的信息,从而影响 Webpack 打包时的行为。

具体来说,神奇注释是一种特殊类型的注释,它们以“webpack-”作为前缀,后跟一个特定的指令来控制 Webpack 的打包行为。

例如,webpack-chunk-name注释可以指示 Webpack 在打包时使用指定的名称来命名动态加载的代码块

下面是一个使用神奇注释的例子:

import(/* webpackChunkName: "my-chunk" */ './modules/my-module.js')
  .then((myModule) => {
    // do something with myModule
  });

上述代码中的webpackChunkName注释告诉 Webpack 使用my-chunk作为生成的代码块的名称。

除了webpackChunkName之外,还有其他一些常用的神奇注释,例如:webpackPrefetchwebpackPreloadwebpackIgnorewebpackMode等。

使用神奇注释可以更好地控制和优化 Webpack 打包行为,但同时也可能会增加代码的复杂性和维护难度,需要根据具体的项目情况进行权衡和决策。

376、解释一下Vite的原理

Vite 的原理主要分为两方面:开发环境和生产环境。

开发环境

在开发环境下,Vite 基于浏览器原生支持的 ES Modules,利用轻量级的 Web 服务器实现了快速的 Hot Module Replacement(HMR)热更新功能。其具体原理如下:

  1. Vite 启动一个轻量的 web 服务器(默认是基于 Koa2 实现的)并监听源代码文件的变化。
  2. 当浏览器请求页面时,Vite 拦截 <script>标签的请求,并通过相应的模块解析器对导入的模块解析成浏览器可识别的格式。
  3. Vite 根据解析的模块动态生成一个 client(客户端)脚本,并注入到页面中。
  4. 当源代码文件改变时,Vite 会使用 Rollup 将修改过的模块打包,再通过 Socket.IO 或者基于 EventSource 的服务端推送机制将修改通知给客户端。
  5. 客户端收到修改通知后,会重新向服务器请求被修改的模块,并使整个过程如无感知地完成刷新。

生产环境

在生产环境下,Vite 利用 Rollup 进行构建,具体原理如下:

  1. Vite 根据不同的指令将代码打包成不同格式的模块(如 ES Module、CommonJS 以及 UMD 等模式)。
  2. Vite 会对打包后的代码进行压缩、混淆、去除无用代码等优化处理,以减小文件大小和提升性能。
  3. 在生产模式下,Vite 不需要启动 web 服务器和模块热更新功能,从而能够更快地构建前端应用。

综上所述,Vite 在开发环境和生产环境下都是基于 Rollup 来处理代码的。在开发环境下,利用浏览器原生支持的 ES Modules 实现了快速的 HMR;而在生产环境中,则通过打包、优化和压缩等方式来优化前端应用。

377、Vite 和 Webpack 的区别

1、基于ESM的Dev server
Vite出来之前,传统的打包工具如Webpack是先解析依赖、打包构建再启动开发服务器,Dev Server 必须等待所有模块构建完成,当我们修改了 bundle模块中的一个子模块, 整个 bundle 文件都会重新打包然后输出。项目应用越大,启动时间越长。
Vite利用浏览器对ESM的支持,当 import 模块时,浏览器就会下载被导入的模块。先启动开发服务器,当代码执行到模块加载时再请求对应模块的文件,本质上实现了动态加载。灰色部分是暂时没有用到的路由,所有这部分不会参与构建过程。随着项目里的应用越来越多,增加route,也不会影响其构建速度。
2、基于ESM 的 HMR 热更新
目前所有的打包工具实现热更新的思路都大同小异:主要是通过WebSocket创建浏览器和服务器的通信监听文件的改变,当文件被修改时,服务端发送消息通知客户端修改相应的代码,客户端对应不同的文件进行不同的操作的更新。

Webpack: 重新编译,请求变更后模块的代码,客户端重新加载
Vite: 请求变更的模块,再重新加载
Vite 通过 chokidar 来监听文件系统的变更,只用对发生变更的模块重新加载, 只需要精确的使相关模块与其临近的 HMR边界连接失效即可,这样HMR 更新速度就不会因为应用体积的增加而变慢而 Webpack 还要经历一次打包构建。所以 HMR 场景下,Vite 表现也要好于 Webpack

3、基于esbuild的依赖预编译优化

4、基于 Rollup的 Plugins

使用Vite插件可以扩展Vite能力,通过暴露一些构建打包过程的一些时机配合工具函数,让用户可以自定义地写一些配置代码,执行在打包过程中。比如解析用户自定义的文件输入,在打包代码前转译代码,或者查找。

最后总结下Vite相关的优缺点:

  • 优点:

    • 快速的冷启动: 采用No Bundleesbuild预构建,速度远快于Webpack
    • 高效的热更新:基于ESM实现,同时利用HTTP头来加速整个页面的重新加载,增加缓存策略
    • 真正的按需加载: 基于浏览器ESM的支持,实现真正的按需加载
  • 缺点

    • 生态:目前Vite的生态不如Webapck,不过我觉得生态也只是时间上的问题。
    • 生产环境由于esbuildcss和代码分割不友好使用Rollup进行打包

378、介绍一下vite

Vite 是一种快速的现代化前端构建工具,相比传统的打包工具(如 webpack)具有以下优点:

  1. 快速的冷启动:Vite 利用 ES 模块的特性,采用了基于原生 ES 模块的开发服务器。它使用浏览器原生支持的 ES 模块加载方式,无需预构建和打包,直接在浏览器中运行。这样,在开发过程中保持了快速的冷启动时间,加快了开发反馈速度。

  2. 按需编译:Vite 只会编译当前正在编辑的文件,而不是重新构建整个项目。当你保存修改后,只有被修改的文件会被重新编译,大大缩短了每次保存的构建时间,提高了开发效率。

  3. 真正的模块化热更新:Vite 支持真正的模块级热更新,它通过在开发服务器中搭建一个简单的 WebSocket 服务器,与运行在浏览器中的开发服务之间建立了一个实时的连接。这使得只有相关模块发生更改时才会触发热更新,而不会影响其他模块,提供了更快速、可靠的热更新体验。

  4. 零配置:Vite 提供了一种零配置的开发体验。它内置了对常见的前端框架(如 Vue、React 和 Preact)的支持,不需要复杂的配置即可开始开发。同时,Vite 也提供了可扩展的插件系统,以便进行更高级的定制和配置。

  5. 构建速度快:虽然 Vite 在开发过程中不需要进行传统的打包,但它仍然提供了一个生产环境的构建命令。Vite 使用 Rollup 进行构建,通过 ESBuild 进行快速的 JavaScript 编译,以及采用了增量构建的方式,使得构建速度非常快。

总的来说,Vite 提供了一种现代化的开发体验,通过利用浏览器原生支持的特性,加速了项目的冷启动时间和热更新速度,从而提高了开发效率。

379、聊一聊Babel原理吧

Babel 是一个 JavaScript 编译器,可以将 ES6/ES7 代码转换为兼容各种浏览器的 ES5 代码,从而实现在目前的 JavaScript 运行环境下使用最新特性的目的。Babel 的原理主要分为三个部分:解析、转换和生成

  1. 解析:Babel 首先需要将输入的源码解析成抽象语法树(AST),这个过程使用的是一个名为 Babylon 的 JavaScript 解析器,通过遍历源码并将其转换为语法树形式,Babel 可以方便地对其进行后续的处理。

  2. 转换:在将源码转换成 AST 后,Babel 可以对 AST 进行各种操作,例如插件功能、语法转换等。插件是 Babel 处理 JavaScript 代码的核心机制,它们可以用来处理 AST 中的节点,例如添加、删除、替换节点等等。Babel 还支持一些常见的语法转换,例如将箭头函数转换成普通函数、解构赋值转换等等。

  3. 生成:最后,Babel 将处理好的 AST 转换成浏览器可识别的 JavaScript 代码,并输出到文件中,这个过程使用的是一个名为 babel-generator 的库,通过遍历 AST 并将其转换为 JavaScript 代码,Babel 可以方便地将转换后的代码输出到文件中。

总体而言,Babel 的原理就是将输入的 JavaScript 代码解析成 AST、对 AST 进行操作和变换,并最终将处理好的 AST 转换回 JavaScript 代码输出。Babel 功能强大,可以通过插件机制来扩展它的功能,因此能够广泛应用于各种 JavaScript 开发场景中。

380、【阿里巴巴】Webpack 中 babel 属于什么,以什么样的方式存在?

在 Webpack 中,Babel 通常被视为一个加载器(loader),用于将 ES6+ 的代码转换成浏览器可执行的 ES5 代码。

在 Webpack 的配置中,需要使用 babel-loader 来处理 JavaScript 文件,同时配合其他 Babel 相关的工具和插件来完成转换任务。

Babel 可以通过配置不同的 preset(预设)和 plugin(插件)来支持不同的语法和特性。例如,

  • @babel/preset-env 是一个支持大多数最新的 JavaScript 语法特性的预设,同时也支持自动根据浏览器兼容性要求进行转换;
  • @babel/plugin-transform-runtime 可以避免全局污染,同时在代码重用时能够减少代码体积。

在 Webpack 中使用 babel 的方式可以通过如下的配置实现:

module.exports = {
  // ... 其他配置
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime']
          }
        }
      }
    ]
  }
}

以上配置说明了当 Webpack 发现一个 .js 文件被加载时,会使用 babel-loader 进行转换,并且指定了使用 @babel/preset-env@babel/plugin-transform-runtime 插件来实现转换过程。同时,exclude 选项可以排除不需要转化的模块,避免不必要的性能损失。

381、介绍一下core.js

core.js 是一个 polyfill 库,它提供了一些 ECMAScript 新特性的兼容实现,可以让我们在旧版本的浏览器上使用新特性,例如 Promise、Object.assign 等等。core.js 部分包含了大量新特性的 polyfill 实现,许多新特性需要通过这个库来实现兼容性。

Babel编译代码的转换阶段,如果需要将 ECMAScript 新特性转换为浏览器可识别的语法,同时也需要在旧版本浏览器中使用这些新特性,就需要使用 polyfill 库来提供兼容实现。其中,core.js 是一个广泛使用的 polyfill 库,可以提供大量新特性的兼容实现。

Babel 与 core.js 的关系是,Babel 可以通过配置使用 core.js 来自动添加相关的 polyfill,从而保证编译后的代码在低版本浏览器上也能正常运行。在 Babel 7 中,这个功能被称为 "@babel/polyfill",可以在项目中安装,并在入口文件中引入,就可以自动使用 core.js 提供的 polyfill。

因此,在 Babel 的转换阶段中,如果通过配置启用了 core.js 来自动添加相关的 polyfill,则 Babel 会在转换代码之前,添加所需的 polyfill 实现,以保证编译后的代码可以在低版本浏览器上安全地运行。

总之,在 Babel 转换代码的过程中,如果需要在旧版本浏览器中使用新特性,就需要通过启用 polyfill 实现来提供兼容性支持,而 core.js 是常用的 polyfill 库之一,可以提供丰富的 ECMAScript 新特性的兼容实现。

382、浏览器缓存机制

强缓存

在发送 http 请求下载资源之前首先检查强缓存。使用的字段在 http/1.0 和 http/1.1 中分别是 Expires 和 Cache-Control。

Expires

http/1.0 中使用的字段是 Expires 即过期时间,存在于服务器返回的响应头中,浏览器在这个过期时间前再次请求同一资源时将直接从缓存里面获取数据,无需再次发送 http 请求。事例如下:

Expires: Wed, 21 Oct 2020 07:28:00 GMT

使用此字段的缺陷是服务器的时间和浏览器的时间可能不一致,这样服务器返回的过期时间就不一定是准确的。因此这种方式在 http/1.1 中被废弃。

Cache-Control

http/1.1 中使用的字段是 Cache-Control, 也存在于服务器返回的响应头中,但采用过期时长而非具体过期时间点,示例如下:

Cache-Control:public, max-age=86400

表示在响应返回后的 86400 秒也就是 24 小时之内可以直接使用缓存。

public 是另一个缓存指令,表示响应可以被任何对象(包括发送请求的客户端、代理服务器等等)缓存,即使是通常不可缓存的内容。此外,还可组合如下的缓存指令:

private: 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。

no-cache: 跳过当前的强缓存检查,发送 http 请求,即直接进入 协商缓存阶段。

no-store: 缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

注意:当 Expires 和 Cache-Control 同时存在的时候,Cache-Control 会被优先考虑。

协商缓存

当强缓存失效,浏览器在 http 请求头中加入某些字段,服务端根据这些字段确定浏览器是否能够使用缓存,这就是协商缓存。这样的字段有两对:Last-Modified/If-Modified-Since和 ETag/If-None-Match。

Last-Modified/If-Modified-Since

  • 浏览器第一次给服务端发送请求后,服务器会在响应头中加上 Last-Modified 这个字段和值。
  • 浏览器接收到这个字段并在第二次给服务端发送请求时用 If-Modified-Since 字段携带该值。
  • 服务端接收到后会和服务端中该资源的最后修改时间作对比,
  • 如果请求头中的这个值小于最后修改时间,则返回新的资源,否则返回304,告诉浏览器直接用缓存。

ETag/If-None-Match

  • ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,一旦文件内容有改动,这个值就会变化。
  • 服务器通过响应头把这个值传给浏览器,浏览器接收到ETag的值,会在下次请求时将这个值放到请求头 If-None-Match 字段中,然后发给服务器。
  • 服务端接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行比对,如果两者不一样,返回新的资源,否则返回304,告诉浏览器直接用缓存。
  • 另外,在 ETag 和 If-Match 请求头的帮助下,可以检测到"空中碰撞"的编辑冲突。

Last-Modified/If-Modified-SinceETag/If-None-Match的比较

  • 比较这两种方式,精确度上 ETag 要优于 Last-Modified。因为 Last-Modified 的最小单位是秒,假如文件在一秒内改变了多次,这个时候 Last-Modified 的值并没有变化。

  • 性能上 Last-Modified 要优于 ETag,因为 Last-Modified 仅仅只是记录一个时间点,而 Etag 需要根据文件的具体内容生成哈希值。

383、强制缓存和协商缓存使用场景是什么?

浏览器缓存分为强制缓存和协商缓存两种,下面是它们的使用场景总结:

类型

场景

强制缓存

1. 静态资源不经常变更,如图片、字体等;

2. 响应头中设置了强制缓存指令,并且缓存时间合理;

3. 对于数据不敏感或错误不会对用户产生实质性影响的接口返回。

协商缓存

1. 动态数据频繁变更,如新闻、微博等;

2. 需要验证缓存的有效性,如判断资源是否被修改。

需要注意的是,对于一些需要频繁更新的数据接口,可以将缓存时间设置得很短,让缓存过期后及时获取最新数据,以保证数据的实时性。在设置缓存时间时要根据实际情况进行调整,过长可能导致数据过期无法更新,而过短则会增加服务器压力和浏览器请求量。

384、强缓存和协商缓存的区别

强缓存和协商缓存是 HTTP 缓存机制中的两种不同策略,用于控制缓存的有效性和减少网络请求。它们的区别如下:

  1. 强缓存:

    • 客户端在发起请求之前,直接通过检查本地缓存的元数据(比如 Expires、Cache-Control:max-age)来确定是否可以使用缓存。
    • 如果缓存仍然有效,客户端将从本地缓存中获取资源,并且不会向服务器发送请求。
    • 强缓存不需要与服务器进行通信,因此可以提供更快的响应速度。
  2. 协商缓存:

    • 客户端在发起请求时,会向服务器发送一个带有缓存标识的请求(比如 If-None-Match、If-Modified-Since)。
    • 服务器根据请求中的缓存标识进行判断,如果资源没有发生变化,则返回一个 304 Not Modified 的响应,并告知客户端可以使用缓存。
    • 如果资源发生了变化,服务器将返回新的资源,并在响应头部中包含新的缓存标识,客户端会将该标识保存起来以供下次请求时使用。
    • 协商缓存需要与服务器进行通信,并且会有一定的延迟。

总结:

  • 强缓存是在客户端直接使用本地缓存,不需要与服务器进行通信,速度快。
  • 协商缓存是在客户端和服务器之间进行通信,通过缓存标识来验证资源是否发生变化,可以减少带宽消耗。
  • 强缓存适用于那些不频繁变化的静态资源,而协商缓存适用于经常变化的动态资源或者需要频繁更新的内容。

在 HTTP 响应头中,使用 Cache-Control 和 Expires 控制强缓存,使用 ETag 和 Last-Modified 控制协商缓存。根据具体的场景和需求,可以选择合适的缓存策略来提升性能和减少网络请求。

385、浏览器垃圾回收机制

浏览器的垃圾回收机制是一种自动管理内存的机制,用于解决 JavaScript 中的内存泄漏和内存溢出问题。采用“标记清除”算法实现内存的自动回收。

在 JavaScript 中,当一个变量不再被引用时,就会变成“垃圾”,需要将其占用的内存空间释放出来。由于 JavaScript 是一种动态语言,无法像 C++ 等静态语言那样手动管理内存,因此需要使用垃圾回收机制来自动管理内存。

浏览器的垃圾回收机制采用的是“标记清除”算法。该算法通过标记“活动对象”,然后清除“非活动对象”的方式来实现内存的自动回收。

具体来说,当 JavaScript 引擎创建一个对象时,会为该对象分配一块内存空间,并将该内存空间标记为“活动对象”。当该对象不再被引用时,JavaScript 引擎会将其标记为“非活动对象”,并在适当的时候将其占用的内存空间释放出来。

垃圾回收机制并不是完美的,它仍然存在一些问题。例如,如果一个“非活动对象”引用了一个“活动对象”,那么这个“非活动对象”也不能释放掉,即使它本身已经没有用了。这样就可能出现一些内存泄漏的问题。

另外,垃圾回收机制也会对性能产生一定的影响。当需要进行垃圾回收时,JavaScript 引擎必须暂停当前的程序执行,这就会导致一些可见的卡顿和延迟。

综上所述,浏览器的垃圾回收机制是一种自动管理内存的机制,采用“标记清除”算法实现内存的自动回收。但是,它仍然存在一些问题,需要开发者在编写代码时注意避免内存泄漏和内存溢出问题。

1、根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同分代采用不同的回收算法

2、新生代采用空间换时间的 scavenge 算法:整个空间分为两块,变量仅存在其中一块,回收的时候将存活变量复制到另一块空间,不存活的回收掉,周而复始轮流操作

3、老生代使用标记清除和标记整理,标记清除:遍历所有对象标记标记可以访问到的对象(活着的),然后将不活的当做垃圾进行回收。回收完后避免内存的断层不连续,需要通过标记整理将活着的对象往内存一端进行移动,移动完成后再清理边界内存

虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。

  • 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
  • object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
  • 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。

386、v8垃圾回收处理机制

V8 是一种开源的 JavaScript 引擎,它被用于许多现代浏览器(如 Chrome)和 Node.js 环境中。V8 引擎采用了一种高效的垃圾回收机制来自动管理内存。

V8 的垃圾回收机制主要基于以下两个原则:

  1. 分代回收:V8 将内存对象分为几个不同的代(Generation),通常是新生代(New Generation)和老生代(Old Generation)。新生代包含较短寿命的对象,而老生代包含较长寿命的对象。由于大多数对象在短时间内就会变得不可达,因此将对象分类可以更有效地进行垃圾回收。

  2. 停止-复制式垃圾回收:V8 使用了一种称为停止-复制(Stop-The-World Copying)的垃圾回收算法。这种算法将堆内存分为两个空间,一个是活动空间(From Space),一个是空闲空间(To Space)。在垃圾回收过程中,V8 会先暂停 JavaScript 执行,然后将所有还存活的对象从活动空间复制到空闲空间,并且对它们进行整理和压缩。最后,活动空间和空闲空间的角色会互换,完成垃圾回收过程。

具体来说,V8 的垃圾回收过程可以分为以下几个阶段:

  1. 标记阶段(Marking Phase):V8 从根对象开始遍历,标记所有可达的对象。这个过程使用了一种称为追踪垃圾回收算法(Tracing Garbage Collection Algorithm),通过追踪引用链来确定哪些对象是存活的。

  2. 清除阶段(Sweeping Phase):在标记阶段完成后,V8 开始清除那些未被标记的对象,将它们的内存空间释放出来。

  3. 压缩阶段(Compacting Phase):对于老生代的对象,V8 还会进行额外的压缩操作,将已标记的对象整理到内存的一端,以便更好地利用内存空间。

值得注意的是,在进行垃圾回收的过程中,V8 会暂停 JavaScript 的执行,这个暂停时间称为停顿(Pause)。为了减少停顿时间并提高性能,V8 引入了增量标记(Incremental Marking)和并发标记(Concurrent Marking)等技术,允许在执行垃圾回收的同时,让 JavaScript 继续执行一部分代码。

总结起来,V8 垃圾回收处理机制采用了分代回收和停止-复制式垃圾回收算法。它通过标记、清除和压缩阶段来管理内存,并使用增量标记和并发标记等技术来减少停顿时间,提高性能。这些机制使得 V8 能够高效地管理 JavaScript 代码的内存,并且提供良好的性能表现。

V8 引擎是由 Google 开发的用于执行 JavaScript 代码的高性能引擎,常用于 Chrome 浏览器和 Node.js 等环境中。V8 引擎的垃圾回收算法是其内存管理的关键部分,下面详细解释一下 V8 引擎的垃圾回收算法:

1. 新生代和老生代内存区域:

  • V8 引擎将堆内存分为两部分:新生代(Young Generation)和老生代(Old Generation)。
  • 新生代用于存储临时对象,包括大部分短期存活的对象;老生代用于存储长期存活的对象。

2. 新生代垃圾回收算法(Scavenge):

  • 新生代采用 Scavenge 算法进行垃圾回收,将新分配的对象放入 From 空间,当 From 空间满时触发垃圾回收。
  • 在垃圾回收过程中,将存活的对象复制到 To 空间,清空 From 空间,然后交换 From 和 To 空间的角色。

3. 老生代垃圾回收算法:

  • 老生代中存储的是经过多次新生代回收仍然存活的对象,因此需要更复杂的垃圾回收算法来处理。
  • V8 引擎使用标记-清除(Mark-and-Sweep)和标记-整理(Mark-Compact)结合的方式进行老生代的垃圾回收。

4. 标记-清除算法(Mark-and-Sweep):

  • 首先,标记阶段遍历所有对象,并标记所有可达对象。
  • 然后,在清除阶段,未被标记的对象将被清除,并释放其占用的内存空间。

5. 标记-整理算法(Mark-Compact):

  • 标记-整理算法在标记阶段同样标记所有可达对象。
  • 不同的是,在整理阶段,将存活的对象向一端移动,然后清理掉边界外的内存空间,从而减少内存碎片化。

6. 增量标记算法(Incremental Marking):

  • 为了减少垃圾回收造成的卡顿,V8 引擎还实现了增量标记算法,将标记过程分解为多个阶段,在执行 JavaScript 代码的同时逐步完成标记过程。

V8 引擎的垃圾回收算法采用了多种技术手段来提高垃圾回收的效率和性能,并尽量减少对 JavaScript 应用的影响。深入了解这些算法有助于优化代码以避免频繁的垃圾回收,提高应用的性能和稳定性。

387、前端性能优化常用的方法有哪些

js方面:

  • 减少http请求 :节流、防抖、缓存(keep-alive);
  • 及时消除对象引用,清除定时器,清除事件监听器;
  • 使用常量,避免全局变量;
  • 减少dom 操作,
  • 删除冗余代码(没有使用到的代码)
  • 推迟js 加载:defer

css 方面

  • 使用<link>不使用@import
  • 减少重绘和回流,减少table 表格布局,html 层级嵌套不要太深;
  • 合理配置图片加载方式(图片压缩上传、iconfont、base64、file文件、cdn、预加载、懒加载)
  • 开启硬件加速(GPU加速)

工程化方面

  • webpack :打包压缩、Loader 、插件;
  • 合理利用浏览器缓存(首次缓存)
  • 开启gzip压缩(减少文件访问体积)
  • 使用ssr服务端渲染。
  • 路由、组件、长页面使用懒加载
  • 减少重定向请求

388、浏览器中LightHouse v8/v9性能优化指标有哪些

  • FCP(First Contentful Paint)
    • FCP衡量的是,在用户导航到页面后,浏览器呈现第一块DOM内容所需的时间。
    • 页面上的图片非白色<canvas>元素svg都被认为是DOM内容;
    • iframe内的任何内容都不包括在内
    • 优化手段:缩短字体加载时间
  • SI(Speed Index)
    • SI指数衡量内容在页面加载期间视觉显示的速度。Lighthouse首先在浏览器中捕获页面加载的视频,并计算帧之间的视觉进展
    • 优化手段:1. 减少主线程工作 2. 减少JavaScript的执行时间
  • LCP(Largest Contentful Paint)
    • LCP测量视口中最大的内容元素何时呈现到屏幕上。这接近于用户可以看到页面的主要内容
  • TTI(Time to Interactive)
    • TTI测量一个页面变成完全交互式需要多长时间
    • 当页面显示
    • 有用的内容(由First Contentful Paint衡量),
    • 为大多数可见的页面元素注册了事件处理程序
    • 并且页面在50毫秒内响应用户交互时,
    • 页面被认为是完全交互式的。
  • TBT(Total Blocking Time)
    • TBT 测量页面被阻止响应用户输入(例如鼠标点击、屏幕点击或按下键盘)的总时间。总和是FCPTTI之间所有长时间任务的阻塞部分之和
    • 任何执行时间超过 50 毫秒的任务都是长任务。50 毫秒后的时间量是阻塞部分。
    • 例如,如果检测到一个 70 毫秒长的任务,则阻塞部分将为 20 毫秒
  • CLS(Cumulative Layout Shift)
    • 累积布局偏移 (CLS) 是测量视觉稳定性的一个以用户为中心的重要指标
    • CLS 较差的最常见原因为:
    • 1.无尺寸的图像
    • 2.无尺寸的嵌入和 iframe
    • 3.动态注入的内容
    • 优化手段1. 除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容 2. 倾向于选择transform动画

389、如何优化LCP

导致 LCP 不佳的最常见原因是:

  1. 缓慢的服务器响应速度
  2. 阻塞渲染的 JavaScriptCSS
  3. 缓慢的资源加载速度
  4. 客户端渲染

390、ssr优缺点,实现的具体过程

SSR(Server Side Rendering,服务器端渲染)是一种将页面的生成工作移至服务器端的技术,它可以提高页面的首屏加载速度、SEO效果和用户体验。下面是SSR的优缺点以及实现的具体过程:

优点:

  1. 首屏加载速度快:由于SSR在服务器端生成页面并输出HTML,所以可以在客户端接收到响应前就已经有一部分页面内容展示了出来,从而提高了首屏加载速度。

  2. SEO优化效果好:由于搜索引擎主要抓取HTML文本,因此使用SSR可以将完整的HTML文本返回给搜索引擎,提高网站的搜索排名和曝光率。

  3. 用户体验更好:由于首屏加载速度快,用户可以更快地看到页面内容,从而提高了用户的满意度和留存率。

缺点:

  1. 服务器压力大:由于SSR需要在服务器端进行页面的生成和输出,因此会增加服务器的负载压力,需要考虑服务器性能和扩展性。

  2. 开发成本高:SSR需要在服务器端进行页面模板的编写和数据的获取,需要更多的后端开发人力投入。

实现过程:

  1. 配置服务器环境:需要在服务器端搭建Node.js环境,并安装相关依赖模块。可以使用Express或Koa等Web框架来简化开发。

  2. 创建服务器入口:创建一个服务器入口文件,通过监听HTTP请求并返回相应的HTML文本来实现SSR。可以在入口文件中使用渲染引擎(如EJS、Pug等)来处理HTML模板,并将数据传递给模板进行渲染。

  3. 配置路由:在服务器端配置路由,根据不同的请求路径返回相应的HTML文本。可以通过路由参数、查询字符串或POST请求等方式传递数据给服务器端进行处理。

  4. 客户端脚本:在HTML文本中插入客户端脚本,用于处理后续的页面交互和异步请求。

  5. SEO优化:为了优化SEO效果,需要在HTML文本中添加关键字、描述和标题等元数据,并且确保页面内容能够正常被搜索引擎抓取和解析。

以上是SSR的基本实现过程,当然,具体实现还需要根据项目的需求和技术栈做出相应的调整和优化。

391、怎么分析性能?

在前端面试中,常常会被问及如何分析网页性能以及如何进行性能优化。以下是关于如何分析前端性能的一些详细解释:

1. 使用浏览器开发者工具:

  • Network 面板: 查看页面加载过程中各个资源文件的加载时间、大小以及请求情况,定位网络性能瓶颈。
  • Performance 面板: 记录页面加载过程中各个阶段的性能数据,包括 JavaScript 执行时间、渲染性能等,帮助识别性能瓶颈。

2. 利用 Web 性能优化工具:

  • Lighthouse: 一个由 Google 提供的开源工具,可评估网站的性能、可访问性、最佳实践等,提供改进建议。
  • WebPageTest 在全球各地运行网站性能测试,提供详细的加载时间、水平瀑布图等分析报告。

3. 分析页面渲染过程:

  • 查看关键渲染路径: 确认首屏加载所需资源,并优化这些资源的加载顺序和方式,缩短首屏渲染时间。
  • 减少重排和重绘: 通过减少 DOM 操作、合并样式等方式避免不必要的重排和重绘,提高页面性能。

4. 性能监控与数据分析:

  • 引入监控工具: 使用工具如 Google Analytics、Sentry 等来监控用户行为和页面性能数据,发现问题并作出调整。
  • 分析性能数据: 结合实际用户访问数据,进行性能数据分析,找出瓶颈并优化关键路径。

5. 代码审查与优化:

  • 优化关键资源: 压缩 JavaScript、CSS 文件,优化图片等静态资源的加载方式。
  • 减少不必要请求: 减少不必要的网络请求,合并文件、延迟加载等优化方式。
  • 优化 JavaScript 执行: 避免长任务阻塞主线程,优化代码逻辑以提高性能。

通过以上方法,可以全面分析和评估页面性能,及时发现问题并采取相应措施进行性能优化,从而提升网站的加载速度和用户体验。

392、实现前端监控 SDK 技术要点有哪些

实现前端监控 SDK 主要包括以下技术要点:

1.错误捕获:监控页面中的 JavaScript 错误,包括语法错误、运行时错误和异步错误等,并将错误信息上报到服务器,以便于开发者及时发现和解决这些错误。

2.性能统计:监控页面的加载时间、渲染时间、资源加载时间、DNS 查询时间、TCP 连接时间、首次字节时间、白屏时间、用户可交互时间等指标,并将这些指标上报到服务器,以便于开发者优化页面的性能。

3.用户行为跟踪:监控用户在页面中的行为,例如点击、滚动、输入、下拉等操作,并将这些行为的数据上报到服务器,以便于开发者分析用户行为和用户体验。

4.接口监控:监控页面中通过 Ajax 或 Fetch 等方式请求的接口的状态码、返回数据、响应时间等信息,并将这些信息上报到服务器,以便于开发者实时了解接口的调用情况。

5.资源监控:监控页面中静态资源的加载情况,例如图片、CSS、JavaScript 等文件的加载时间、大小等,并将这些信息上报到服务器,以便于开发者优化资源加载的流程和效率。

6.日志上报:监控页面中的日志输出,例如 console.log、console.error 等信息,并将这些信息上报到服务器,以便于开发者及时查看和解决问题。

7.数据加密:为了保障用户数据的隐私和安全,监控 SDK 应该对上报的数据进行加密处理,以防止敏感数据被恶意获取和利用。

8.稳定性和容错:监控 SDK 应具有一定的稳定性和容错能力,在网络不稳定或服务器出现异常时,能够自动重试、断点续传等机制,以确保监控数据的准确性和完整性。

总之,实现前端监控 SDK 需要综合考虑技术、安全、稳定性等多个方面的要素,注重细节和极致体验,以为用户提供更加优质的服务。

393、监听scoll事件的优化

使用监听scroll事件的方式来触发可视区域中数据的更新,当滚动发生后,scroll事件会频繁触发,很多时候会造成重复计算的问题,从性能上来说无疑存在浪费的情况。

可以使用IntersectionObserver替换监听scroll事件,IntersectionObserver可以监听目标元素是否出现在可视区域内,在监听的回调事件中执行可视区域数据的更新,并且IntersectionObserver的监听回调是异步触发,不随着目标元素的滚动而触发,性能消耗极低。

394、如何实现图片懒加载

实现图片懒加载的核心是使用 Web API 里面提供的Intersection Observer API

Intersection Observer API的功能包括:

  • 异步观察目标元素与祖先元素或文档视口间的交集变化
  • 提供回调函数功能,当目标元素与祖先元素或文档视口的交集发生变化时,自动触发回调函数并返回IntersectionObserverEntry对象。
  • 可以同时监控多个元素的交集变化,并在变化时依次执行回调函数。
  • 可以通过传递options参数来定制Intersection Observer的根元素、margin、threshold等参数,从而更加灵活地监控目标元素与祖先元素的交集变化。
  • Intersection Observer API可以有效提高性能,避免频繁监听scroll事件所带来的性能问题和代码复杂度。

技术详解

在Vue3中实现图片懒加载,可以使用Web API提供的Intersection Observer API来实现。

该API可以同时监听多个元素是否进入视窗,并在元素进入视窗时触发回调函数,从而实现图片的懒加载。

具体实现步骤如下:

在Vue组件中引入Intersection Observer API:

import { ref, onMounted } from 'vue'

const observer = new IntersectionObserver(callback, options)

在组件的data选项中定义需要懒加载的图片列表:

data() {
  return {
    lazyImages: [
      {
        id: 1,
        src: 'xxxxx1.jpg',
        loaded: false
      },
      {
        id: 2,
        src: 'xxxxx2.jpg',
        loaded: false
      }
      // ...
    ]
  }
}

在组件的template中,使用v-for指令循环渲染图片列表,并为每个图片元素添加ref属性,以便在后续使用Intersection Observer API进行监听:

<template>
  <div>
    <img v-for="image in lazyImages"
         :key="image.id"
         :src="image.loaded ? image.src : ''"
         ref="lazyImage"
         :data-src="image.src">
  </div>
</template>

在组件的setup函数中,使用onMounted钩子函数监听页面加载完成,并将每个图片元素添加到Intersection Observer API的监听列表中:

setup() {
  const lazyImageList = ref(null)

  onMounted(() => {
    const images = lazyImageList.value.querySelectorAll('img')

    images.forEach((image) => {
      observer.observe(image)
    });
  })

  return {
    lazyImageList
  }
}

定义Intersection Observer API的回调函数callback,当某个图片元素进入视窗时,会触发回调函数,并将进入视窗的图片元素传递给回调函数。在回调函数中,根据需要加载图片,加载完成后标记图片元素为已加载:

const callback = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const image = entry.target

      image.src = image.dataset.src;
      image.onload = () => {
        image.loaded = true
      }

      observer.unobserve(image)
    }
  })
}

到此为止,Vue3中图片懒加载的实现就完成了。利用Intersection Observer API可以很方便、高效地实现图片的懒加载,提升用户体验和页面性能。

395、【阿里巴巴】如果要开发一个类似 cli 的交互命令行工具怎么实现?

要开发一个类似 CLI 的交互式命令行工具,可以借助 Node.js 平台提供的 readline 模块和其他相关模块来实现。以下是一个简单的示例代码:

const readline = require('readline');

// 创建 readline 实例
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

// 设置提示符
rl.setPrompt('> ');

// 显示提示符
rl.prompt();

// 处理用户输入
rl.on('line', (input) => {
  console.log(`用户输入:${input}`);
  // 继续显示提示符
  rl.prompt();
});

// 处理关闭事件
rl.on('close', () => {
  console.log('再见!');
  process.exit(0);
});

在这个示例中,我们使用 readline 模块创建了一个 readline 实例,并设置了提示符。然后通过监听 'line' 事件来处理用户输入,并在控制台输出。最后通过监听 'close' 事件来处理关闭事件。

当然,只有这些内容是远远不够的,一个完整的命令行工具还需要处理命令解析、参数解析、命令执行结果展示、错误处理等等问题。可以结合其他模块和库来实现这些功能,例如 commanderinquirerchalk 等等。

396、大文件上传如何做分片上传、断点继传

大文件上传的分片上传和断点续传是指将一个大文件拆分成多个小文件进行上传,在遇到网络抖动、掉线等异常情况时,可以实现上传进度的恢复和文件传输的续传。

以下是基本的分片上传和断点续传实现思路:

分片上传:

  • 将大文件拆分成固定大小的小文件,例如每个小文件大小为 5MB。
  • 使用 HTTP 协议的 PUT 方法,上传数据到服务器,其中请求体中携带了文件分片的具体内容。
  • 在每次上传前,需要携带分片序号、MD5 值等信息,以便服务器端进行文件校验和拼接。
  • 在所有分片上传完成后,服务器端对所有分片进行校验和拼接,生成完整的文件。

断点续传:

  • 记录上传进度,即已经成功上传的分片与其对应的下标,通常可在客户端使用 localStorage 或 cookie 进行存储。
  • 当网络断开后,下一次上传时,先向服务器请求已经上传的分片和进度,然后从上一次上传的位置继续上传下一片。
  • 如果某个分片上传失败,则重新上传该分片即可,不必将之前已经上传的分片重复上传。

具体实现中,需要借助一些第三方库或者框架,如 OSS、Webuploader、plupload 等。其中,OSS 是阿里云对象存储服务提供的上传 SDK,具有支持断点续传、自动分片、安全稳定等特性;
Webuploader 和 plupload 则是前端上传库,支持多文件上传、分片上传、拖拽上传等功能。

397、206状态码底层原理,分片上传原理

206状态码底层原理

HTTP状态码206表示Partial Content(部分内容),它是一种表示服务器成功处理了部分GET请求的状态码。当客户端发送一个包含Range头字段的GET请求时,服务器可以使用206状态码来返回部分请求的内容。

底层原理是这样的:客户端在请求头中通过Range字段指定需要获取的数据范围,例如"Range: bytes=0-999"表示获取文件的前1000个字节。服务器收到带有Range字段的请求后,会检查请求中指定的范围是否合法,如果合法则返回206状态码,同时在响应头中包含Content-Range字段和Content-Length字段,用于描述返回的部分内容的范围和长度。

例如,响应头可能是这样的:
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-999/5000
Content-Length: 1000

这样,客户端就知道服务器返回的是文件的一部分内容,并且知道该部分内容在整个文件中的范围和总长度。

206状态码常用于文件下载、断点续传等场景,在网络传输大文件时可以分多次请求获取文件的不同部分,从而提高下载效率和用户体验。

需要注意的是,服务器并不一定支持返回部分内容,如果服务器不支持或者请求的范围无效,服务器会返回其他适当的状态码,例如200(OK)表示返回整个文件内容。

分片上传原理

分片上传是一种将大文件切分成小块进行上传的技术,它可以提高上传效率并降低失败率。下面是分片上传的原理介绍:

  1. 文件切片:在客户端将要上传的文件被切分成多个固定大小的块,每个块通常是几百KB或几MB大小。切片的大小可以根据实际情况进行调整,不同的应用可能有不同的策略。

  2. 上传请求:客户端将切片上传到服务器时,会发送一个初始的上传请求。该请求包含文件的基本信息和切片的数量等参数。

  3. 切片上传:客户端按照事先确定的顺序逐个上传切片。每个切片都会带有一个唯一的标识符,用于在服务器端进行关联和组装。客户端可以使用HTTP协议的POST请求或其他上传接口来发送每个切片。

  4. 服务器接收:服务器接收到每个切片后,会将其暂存到临时存储区,比如内存或磁盘中,然后进行校验。校验可以是简单的校验和,也可以是更复杂的哈希算法,以确保切片完整无误。

  5. 切片组装:当所有切片上传完成后,服务器开始进行切片的组装。它通过标识符将所有切片按顺序组合成原始的文件。在组装过程中,服务器可能会进行一些额外的检查,如完整性验证和去重操作。

  6. 上传完成:一旦文件完整地被组装,服务器就会通知客户端上传成功,并返回相应的成功响应。客户端可以根据需要进行后续的处理,比如展示上传结果、保存文件信息等。

分片上传技术广泛应用于各种场景,如云存储服务、大型文件传输、视频直播等。它能够提高上传速度、减少传输失败的影响范围,并且允许断点续传,使得大文件的上传更加可靠和高效。

398、移动端如何实现上拉加载,下拉刷新

上拉加载的本质是页面触底,或者快要触底时的动作

判断页面触底我们需要先了解一下下面几个属性

  • scrollTop:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值
  • clientHeight:它是一个定值,表示屏幕可视区域的高度;
  • scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)

综上我们得出一个触底公式:

scrollTop + clientHeight >= scrollHeight

下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发的动作

关于下拉刷新的原生实现,主要分成三步:

  • 监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY
  • 监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
  • 监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置

399、设计模式

一、单例模式很常用,比如全局缓存、全局状态管理等等这些只需要一个对象,就可以使用单例模式。

单例模式的核心:就是保证全局只有一个对象可以访问。因为 JS 是门无类的语言,所以别的语言实现单例的方式并不能套入 JS 中,我们只需要用一个变量确保实例只创建一次就行。

class Singleton {
  constructor() {}
}

Singleton.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new Singleton()
    }
    return instance
  }
})()

let s1 = Singleton.getInstance()
let s2 = Singleton.getInstance()
console.log(s1 === s2) // true

在 Vuex 源码中,你也可以看到单例模式的使用,虽然它的实现方式不大一样,通过一个外部变量来控制只安装一次 Vuex

let Vue // bind on install

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    // 如果发现 Vue 有值,就不重新创建实例了
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

二、发布-订阅模式也叫做观察者模式。通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。在现实生活中,也有很多类似场景,比如我需要在购物网站上购买一个产品,但是发现该产品目前处于缺货状态,这时候我可以点击有货通知的按钮,让网站在产品有货的时候通过短信通知我。

在实际代码中其实发布-订阅模式也很常见,比如我们点击一个按钮触发了点击事件就是使用了该模式

<ul id="ul"></ul>
<script>
    let ul = document.querySelector('#ul')
    ul.addEventListener('click', (event) => {
        console.log(event.target);
    })
</script>

在 Vue 中,如何实现响应式也是使用了该模式。对于需要实现响应式的对象来说,在get的时候会进行依赖收集,当改变了对象的属性时,就会触发派发更新。

以下是如何实现发布-订阅模式的例子

class EventCenter{
  // 1. 定义事件容器,用来装事件数组
  let handlers = {}
​
  // 2. 添加事件方法,参数:事件名 事件方法
  addEventListener(type, handler) {
    // 创建新数组容器
    if (!this.handlers[type]) {
      this.handlers[type] = []
    }
    // 存入事件
    this.handlers[type].push(handler)
  }
​
  // 3. 触发事件,参数:事件名 事件参数
  dispatchEvent(type, params) {
    // 若没有注册该事件则抛出错误
    if (!this.handlers[type]) {
      return new Error('该事件未注册')
    }
    // 触发事件
    this.handlers[type].forEach(handler => {
      handler(...params)
    })
  }
​
  // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
  removeEventListener(type, handler) {
    if (!this.handlers[type]) {
      return new Error('事件无效')
    }
    if (!handler) {
      // 移除事件
      delete this.handlers[type]
    } else {
      const index = this.handlers[type].findIndex(el => el === handler)
      if (index === -1) {
        return new Error('无该绑定事件')
      }
      // 移除事件
      this.handlers[type].splice(index, 1)
      if (this.handlers[type].length === 0) {
        delete this.handlers[type]
      }
    }
  }
}

三、以下是一个简单工厂模式的例子

class Man {
  constructor(name) {
    this.name = name
  }
  alertName() {
    alert(this.name)
  }
}

class Factory {
  static create(name) {
    return new Man(name)
  }
}

Factory.create('yck').alertName()

当然工厂模式并不仅仅是用来 new 出实例

可以想象一个场景。假设有一份很复杂的代码需要用户去调用,但是用户并不关心这些复杂的代码,只需要你提供给我一个接口去调用,用户只负责传递需要的参数,至于这些参数怎么使用,内部有什么逻辑是不关心的,只需要你最后返回我一个实例。这个构造过程就是工厂。

工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。

在 Vue 源码中,你也可以看到工厂模式的使用,比如创建异步组件

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

  return vnode
}

在上述代码中,我们可以看到我们只需要调用createComponent传入参数就能创建一个组件实例,但是创建这个实例是很复杂的一个过程,工厂帮助我们隐藏了这个复杂的过程,只需要一句代码调用就能实现功能。

四、适配器模式用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。

getName() {
    return '港版插头'
}

class Target {
  constructor() {
    this.plug = new Plug()
  }
  getName() {
    return this.plug.getName() + ' 适配器转二脚插头'
  }
}

let target = new Target()
target.getName() // 港版插头 适配器转二脚插头

在 Vue 中,我们其实经常使用到适配器模式。比如父组件传递给子组件一个时间戳属性,组件内部需要将时间戳转为正常的日期显示,一般会使用computed来做转换这件事情,这个过程就使用到了适配器模式。

五、装饰器模式不需要改变已有的接口,作用是给对象添加功能。就像我们经常需要给手机戴个保护套防摔一样,不改变手机自身,给手机添加了保护套提供防摔功能。

以下是如何实现装饰模式的例子,使用了 ES7 中的装饰器语法

function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Test {
  @readonly
  name = 'yck'
}

let t = new Test()

t.yck = '111' // 不可修改

在 React 中,装饰模式其实随处可见

import { connect } from 'react-redux'
class MyComponent extends React.Component {
    // ...
}
export default connect(mapStateToProps)(MyComponent)

六、代理是为了控制对对象的访问,不让外部直接访问到对象。在现实生活中,也有很多代理的场景。比如你需要买一件国外的产品,这时候你可以通过代购来购买产品。

在实际代码中其实代理的场景很多,也就不举框架中的例子了,比如事件代理就用到了代理模式。

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
<script>
    let ul = document.querySelector('#ul')
    ul.addEventListener('click', (event) => {
        console.log(event.target);
    })
</script>

因为存在太多的li,不可能每个都去绑定事件。这时候可以通过给父节点绑定一个事件,让父节点作为代理去拿到真实点击的节点。

七、外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。

举个例子来说,我们现在需要实现一个兼容多种浏览器的添加事件方法

function addEvent(elm, evType, fn, useCapture) {
  if (elm.addEventListener) {
    elm.addEventListener(evType, fn, useCapture)
    return true
  } else if (elm.attachEvent) {
    var r = elm.attachEvent("on" + evType, fn)
    return r
  } else {
    elm["on" + evType] = fn
  }
}

对于不同的浏览器,添加事件的方式可能会存在兼容问题。如果每次都需要去这样写一遍的话肯定是不能接受的,所以我们将这些判断逻辑统一封装在一个接口中,外部需要添加事件只需要调用addEvent即可。

400、Node.js的三大特性

Node.js的三大特性是:非阻塞I/O、事件驱动和单线程。

  1. 非阻塞I/O:Node.js的非阻塞I/O直接利用了操作系统的异步I/O接口,这个接口会在后台执行I/O操作,并且让CPU继续处理其他任务。因此,当一个I/O操作执行完成时,Node.js会立即通知应用程序,而不需要等待I/O操作完成。这种I/O方式能够提高应用程序的并发处理能力,并且在处理大量的请求时,具有很好的性能表现。
  2. 事件驱动:Node.js的事件驱动机制是将事件作为一种基础的编程模式,通过引入事件监听器、事件队列和回调函数来实现应用程序的事件驱动。当一个事件发生时,Node.js会自动触发一个对应的事件,然后执行与该事件相关联的回调函数。这种方式能够使应用程序更加灵活、高效、可扩展,并且易于维护。
  3. 单线程:Node.js使用单线程模型,这意味着应用程序只会在一个主线程上运行,而不会创建额外的线程或进程。虽然单线程模型看起来可能会限制应用程序的并发处理能力,但是Node.js通过异步I/O和事件驱动机制提供了高效的解决方案。此外,单线程模型可以降低应用程序的内存占用和上下文切换的开销,因此具有更好的性能。

总之,Node.js的三大特性使其成为一种高效、可扩展、事件驱动的编程平台,适用于构建高性能、实时、分布式和可伸缩的网络应用程序。

401、Node.js中事件循环的流程

Node事件循环的流程如下:

  1. Node应用程序在启动时,会创建一个事件循环(Event Loop)和一个主线程(Main Thread)。

  2. Node应用程序会将所有的I/O请求、定时器请求和异步代码放入一个任务队列(Task Queue)中,并按照先进先出(FIFO)的顺序进行排列。

  3. 当主线程完成一次同步代码执行后,会检查任务队列是否有可执行的任务。如果有,就从队列的最前面取出一个任务并执行,如果没有,则继续等待。

  4. 如果取出的任务是一个I/O请求或一个定时器请求,Node.js会调用操作系统的异步I/O接口或定时器接口,并将该任务设置为挂起状态,并立即返回到主线程,让主线程继续执行其他任务。

  5. 一旦异步I/O操作或定时器超时,操作系统会通知Node.js该任务已经完成,此时Node.js会将该任务加入到任务队列中,并等待主线程处理。

  6. 当事件循环检测到任务队列中有可执行任务时,它会将该任务取出,并调用相应的回调函数执行该任务,然后再次进入轮询等待阶段,直到所有任务都被执行完毕。

总之,Node事件循环是一个重要的机制,它允许Node应用程序通过异步I/O和事件驱动方式高效地处理大量的请求,并在处理大量的请求时具有很好的性能表现。

402、Node.js中整个异步I/O的流程

异步I/O是Node.js中常用的一种非阻塞式I/O处理方式,其流程如下:

  1. 发起I/O请求的线程(或进程)通过调用Node.js提供的接口,发出对底层操作系统的I/O请求。

  2. 操作系统内核接受I/O请求并把该请求挂起,然后立即返回给发起线程,使得发起线程不会被阻塞,继续执行其他任务。

  3. 在I/O操作完成之后,操作系统内核向Node.js发送一个信号,通知其I/O操作已经完成,并将I/O结果保存在一块特定的内存区域中。

  4. Node.js接收到I/O完成信号之后,会将I/O操作结果从特定的内存区域中读取出来,然后再回调指定的回调函数,并将I/O结果传递给回调函数进行处理。

  5. 回调函数接收到I/O结果之后,会根据需要执行相应的业务逻辑,最终将处理结果返回给客户端。

  6. 当Node.js有大量的I/O请求时,它可以使用事件循环机制,将这些不同的I/O请求按照顺序加入到任务队列中,并通过事件循环机制异步地处理这些请求,从而提高了程序的并发性能和处理效率。

总之,异步I/O是一种高效、非阻塞的I/O操作方式,在Node.js中发挥了重要的作用。

403、模块化规范中require和import的区别

  • 导入require 导出 exports/module.exportsCommonJS 的标准,通常适用范围如 Node.js
  • import/exportES6 的标准,通常适用范围如 React
  • require赋值过程并且是运行时才执行,也就是***同步加载***
  • require 可以理解为一个全局方法,因为它是一个方法所以意味着可以在任何地方执行。
  • import解构过程并且是编译时执行,理解为***异步加载***
  • import 会提升到整个模块的头部,具有置顶性,但是建议写在文件的顶部。

404、pnpm原理

pnpm 是一个 JavaScript 包管理工具,它的原理可以概括为以下几个方面:

  1. 硬链接(Hard Linking):pnpm 使用硬链接的方式来共享依赖包。当项目 A 和项目 B 都依赖同一个版本的包时,pnpm 会在硬盘上只存储一份该版本的包,并使用硬链接将其链接到两个项目中,以节省磁盘空间。

  2. 冗余移除(Pruning):pnpm 在安装依赖时,会移除不再需要的依赖包。当一个项目不再依赖某个包时,pnpm 会检测并将该包从硬盘上移除,以避免产生冗余。

  3. 压缩存储(Content Addressable Storage):pnpm 使用内容可寻址存储(Content Addressable Storage)的方式来存储已下载的依赖包。每个包都会被哈希,并用哈希值作为目录名,这样可以避免重复下载相同的包,同时也方便进行缓存和共享。

  4. 并行安装(Parallel Installation):pnpm 支持并行安装依赖包。它会同时下载和构建多个包,以加快安装速度,提高效率。

  5. 锁定文件(Lockfile):pnpm 使用 lockfile 来记录项目的依赖关系和版本信息。这个文件会被锁定,确保在后续安装或构建过程中使用相同的依赖版本。

综上所述,pnpm 通过硬链接、冗余移除、压缩存储、并行安装和锁定文件等机制,实现了高效、可共享的依赖管理方式,有效地减少了磁盘空间占用和重复下载的问题,并提高了安装速度。

405、node是单线程的,如何让他在多核CPU上跑满

使用 Cluster 模块:

Node.js 提供了 Cluster 模块,可以创建子进程来利用多核 CPU。通过 Cluster 模块,可以在主进程中创建多个子进程,每个子进程运行在一个 CPU 核心上,从而实现负载均衡和提高性能。

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork();
  });
} else {
  // 运行子进程的代码
  // 可以监听端口,接收请求等
}

使用 PM2 进程管理工具:

PM2 是一个流行的 Node.js 进程管理工具,它可以帮助管理应用程序的生命周期,并支持多进程部署。通过 PM2,可以轻松启动多个 Node.js 实例,每个实例运行在不同的 CPU 核心上,实现负载均衡和提高性能。

# 安装 PM2
npm install pm2 -g

# 启动应用程序
pm2 start app.js -i max

使用反向代理负载均衡:

通过 Nginx、Apache 等反向代理服务器,可以将请求转发给多个 Node.js 实例,实现负载均衡。反向代理服务器会根据负载情况将请求分发到不同的 Node.js 实例上,从而充分利用多核 CPU 资源。

使用消息队列:

将任务分解为独立的作业,并通过消息队列系统(如 RabbitMQ、Redis 等)分发给多个 Node.js 实例处理。这样可以将工作负载分布到多个进程上,提高系统的并发处理能力。

通过以上方法,可以让 Node.js 在多核 CPU 上充分利用资源,提高系统的处理能力和性能,从而更好地应对高并发场景。

406、【阿里巴巴】自定义lint 的工具怎么做的,就官方会有一些rules,怎么做一个自定义的一个 rules

要自定义一个lint规则,可以使用ESLint来创建自己的规则集。

以下是大体步骤:

安装ESLint:npm install eslint --save-dev
创建一个新文件夹作为你的规则集,例如"my-rules"
在这个文件夹里面创建一个新的JS文件,例如"my-rule.js",用于定义你的规则
在"my-rule.js"中定义你的规则,例如:

module.exports = {
  meta: {
    docs: {
      description: "disallow the use of console.log",
      category: "Best Practices",
      recommended: true,
    }
  },
  
  create: function(context) {
    return {
      CallExpression(node) {
        if (node.callee.object && node.callee.object.name === "console" && node.callee.property.name === "log") {
          context.report({
            node,
            message: "Unexpected console.log statement."
          });
        }
      }
    };
  }
};

在"my-rules"文件夹里面创建一个package.json文件,并添加一些基本信息和依赖项:

{
  "name": "my-rules",
  "version": "0.1.0",
  "description": "My custom ESLint rules",
  "main": "./my-rule.js",
  "dependencies": {
    "eslint": "^7.8.1"
  }
}

运行npm link在全局范围内安装你的规则集。
然后在你的项目中使用npm link my-rules命令将其链接到你的项目中。
最后在你的ESLint配置文件中使用你的规则,例如:

module.exports = {
  "rules": {
    "my-rules/my-rule": "error"
  }
};

407、有哪些可能引起前端安全的问题

前端安全问题是影响 Web 应用程序安全的主要因素之一,以下是一些可能引起前端安全问题的常见因素:

  1. XSS(Cross-Site Scripting)跨站脚本攻击:攻击者通过在网站中注入恶意脚本,使得其他用户在访问该网站时受到攻击。XSS 攻击通常利用用户输入验证不严格、没有过滤敏感字符等漏洞实现。

  2. CSRF(Cross-Site Request Forgery)跨站请求伪造:攻击者通过各种手段欺骗用户在已登录的状态下,在另一个网站上执行某些非法的操作。CSRF 攻击通常利用浏览器自动发送 Cookie 的机制和缺乏验证机制等漏洞实现。

  3. 点击劫持攻击:攻击者通过将恶意网站覆盖在正常的网站之上,诱骗用户进行点击操作,从而达到窃取用户数据或执行恶意代码的目的。

  4. DOM(Document Object Model)操作不当:攻击者可以通过窃取或修改 DOM 来控制页面的显示和行为,例如篡改表单数据、更改页面结构等。

  5. 不安全的密码管理:如果密码存储在 cookie、localStorage 或 sessionStorage 中,那么它们很容易被攻击者窃取,因此合适的安全处理和存储密码非常重要。

  6. 不安全的第三方插件和库:如果使用不安全的第三方插件和库,可能会带来潜在的漏洞和安全隐患,因此应该选择可信任的和经过验证的插件和库。

为了避免上述问题,可以采取一些措施,如严格过滤和验证用户输入、使用 HTTPS 协议、设置 CSP 策略、启用 CSRF Token 机制、禁止 iframe 嵌套等,以提高 Web 应用程序的安全性。

408、如何防御 XSS 攻击

XSS攻击(Cross-Site Scripting),又称为跨站脚本攻击,是一类常见的网络安全漏洞。攻击者通过注入恶意脚本,使得正常的网页代码执行了这些恶意脚本,从而达到盗取用户cookie、窃听用户敏感信息和篡改网页内容等恶意目的的攻击手段。

XSS攻击主要分为三种类型:

  1. 反射型 XSS 攻击:攻击者构造出一条包含恶意脚本的 URL,并诱导用户单击访问该链接,此时恶意脚本会被注入到响应页面的内容中,用户的浏览器解析后执行该脚本,完成攻击操作。

  2. 存储型 XSS 攻击:攻击者将恶意脚本或代码存储到服务器中,当其他用户访问该网站时,该脚本会被注入到相应的页面中,用户的浏览器解析后执行该脚本,完成攻击操作。

  3. DOM 型 XSS 攻击:攻击者通过恶意脚本修改了页面的 DOM 结构,使得用户的浏览器执行了恶意脚本,从而完成攻击操作。

为了防范 XSS 攻击,我们可以采用以下措施:

  • 对输入、输出数据进行必要的过滤与验证,避免恶意脚本的注入。
  • 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。CSP 用 HTTP 头部中的 Content-Security-Policy 属性来指定网页中允许加载和执行的外部资源和脚本。
  1. CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。
  2. 通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式
  • 对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。
  • 启用浏览器自带的 XSS 过滤器,例如在 HTTP 头部中设置 X-XSS-Protection 属性。

409、CSRF工作过程

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全漏洞,攻击者利用受害者在已认证的情况下发送恶意请求,而受害者并不知情。

下面是CSRF攻击的一般工作过程:

  1. 用户登录:用户在某个网站A中进行登录,并成功获取到了认证凭证,如Cookies。

  2. 攻击者构造攻击页面:攻击者创建一个恶意网站B,并在网站B中构建一个针对网站A的攻击页面。这个页面可能包含一个隐藏的表单或脚本。

  3. 受害者访问攻击页面:受害者在不知情的情况下,访问了攻击者构造的恶意网站B。

  4. 恶意请求发送:攻击页面中的恶意表单或脚本会自动触发浏览器向目标网站A发送请求,其中包含了用户的认证凭证。

  5. 目标网站接收请求:目标网站A接收到恶意请求后,并认为是合法的用户操作,因为请求中包含了有效的认证凭证。

  6. 恶意操作执行:目标网站A根据请求的内容执行相应的操作,可能会导致一些恶意行为,比如修改用户信息、删除数据等。

由于攻击者控制了恶意页面,使得受害者的浏览器自动发送了请求,因此目标网站无法判断请求的真实意图,从而被误认为是受害者的合法操作。

为了防止CSRF攻击,可以采取以下措施:

  • 验证请求来源:在服务器端对请求进行验证,确保请求来自合法的源站,可以通过检查请求头中的Referer字段或使用同源策略进行验证。
  • 添加随机令牌:在表单中添加一个随机生成的令牌(CSRF Token),并将其与用户会话关联。服务器在接收到请求时验证令牌的有效性。
  • 使用验证码:要求用户在某些敏感操作之前进行验证码验证,以增加攻击的难度。
  • 限制敏感操作的HTTP方法:对于执行敏感操作的请求,应该使用POST、PUT等非幂等性的方法,而不是GET请求。

通过以上措施的综合应用,可以有效地减少CSRF攻击的风险。

410、什么是 CSRF 攻击

(1)概念

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。

CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。

(2)攻击类型常见的 CSRF 攻击有三种:

  • GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。
  • POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。
  • 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

411、token为什么能够解决CSRF

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的网络攻击方式,攻击者通过欺骗用户在其他网站上执行恶意请求来利用用户在目标网站上的身份验证凭据。为了解决CSRF攻击,可以使用token机制。

Token(令牌)是一种验证机制,由服务器生成并返回给客户端,在后续的请求中客户端需要携带该令牌进行验证。使用token解决CSRF的原理如下:

  1. 服务器生成一个唯一的token,并将其嵌入到表单或请求头中,发送给客户端。
  2. 客户端收到token后,将其保存在Cookie或其他地方。
  3. 当客户端发送请求时,将token作为参数或者放入请求头中。
  4. 服务器在接收到请求后,验证请求中的token与服务器保存的token是否一致。

使用token可以有效地防止CSRF攻击,原因如下:

  1. Token是服务器生成的,攻击者无法获取服务器生成的token,因此无法伪造有效的请求。
  2. 每个用户的token都是唯一的,攻击者无法猜测或复用其他用户的token。
  3. 服务器需要验证请求中的token是否与服务器保存的token一致,如果不一致则拒绝请求,确保只有合法的请求被处理。
  4. 攻击者在其他网站上伪造的请求无法携带正确的token,因此服务器可以轻松识别和拒绝这些恶意请求。

通过使用token,服务器能够验证请求的合法性,确保请求来自于可信任的来源,并有效地防止CSRF攻击。

412、XSS 和 CSRF 区别和防范

XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)是常见的 Web 安全漏洞,它们都可以对网站和用户造成严重的安全威胁。下面我将介绍它们的区别以及防范措施:

XSS(跨站脚本攻击)

区别:

  • XSS 攻击是指攻击者向 Web 页面注入恶意脚本,当用户访问包含恶意脚本的页面时,这些脚本就会在用户的浏览器上执行,从而对用户造成危害。
  • XSS 攻击主要针对用户,通过注入恶意脚本获取用户的敏感信息、伪造用户身份等。

防范措施:

  • 对输入输出进行过滤和转义:确保在向页面输出用户输入内容时进行适当的转义,以防止恶意脚本的执行。
  • 使用 Content Security Policy(CSP):CSP 是一种通过设置 HTTP 头部来减少或消除跨站点脚本攻击的技术。
  • 输入验证:对用户输入进行验证,只接受符合特定格式的输入数据。

CSRF(跨站请求伪造)

区别:

  • CSRF 攻击是指攻击者利用受害者的登录状态,在受害者不知情的情况下以受害者的名义发送恶意请求,如转账、修改密码等。
  • CSRF 攻击主要针对网站,通过伪造用户的请求来执行恶意操作。

防范措施:

  • 使用 CSRF Token:在每个表单中生成一个随机的 token,并将其与用户的会话关联起来,提交表单时需要验证该 token 的有效性,从而防止 CSRF 攻击。
  • 合理使用 HTTP 方法:GET 请求不应该产生副作用,比如修改数据,而是用来获取资源。POST 请求用来产生副作用,比如提交表单。合理使用这两种方法可以减少 CSRF 的风险。

总的来说,XSS 主要针对用户,攻击手段是通过在页面中注入恶意脚本;而 CSRF 主要针对网站,攻击手段是伪造用户的请求。防范措施包括对输入输出进行过滤和转义、使用安全策略头、使用 CSRF Token 等。在实际开发中,开发者需要综合考虑各种安全漏洞,采取综合的安全措施来保护 Web 应用程序和用户的安全。

413、什么是中间人攻击

中间人攻击(Man-in-the-middle Attack,MITM)是一种常见的网络攻击方式,攻击者在通信的两个终端之间插入自己的设备或程序,并假扮成真正的通信方与另一个终端交互,从而获取数据或者操纵通信内容。

在这种攻击中,攻击者可以窃取双方的敏感信息,比如用户名、密码、银行卡号等,也可以篡改双方的通信内容,比如替换网页、更改交易金额等。由于攻击者能够通过伪造证书或欺骗用户的方式进行中间人攻击,因此这种攻击方式具有很高的隐蔽性和危险性。

为了防止中间人攻击,我们可以采用以下措施:

  1. 使用加密传输协议,如 HTTPS,SSL/TLS 等,确保通信内容不能被窃取或篡改;

  2. 避免在公共 Wi-Fi 网络上处理敏感信息,因为公共 Wi-Fi 网络容易被恶意人士利用进行中间人攻击;

  3. 定期更新防病毒软件,确保自己的设备不会被攻击者利用进行中间人攻击;

  4. 小心处理来路不明的链接、邮件或文件,以免误操作导致受到中间人攻击。

414、浏览器有哪些安全措施

安全措施

描述

同源策略

防止不同源的脚本互相操作

内容安全策略

设置可信内容,减少XSS攻击

CSRF防护

通过令牌验证请求的合法性

XSS防护

内置机制减少XSS攻击

HTTPS

使用SSL/TLS加密传输层

证书错误提示

SSL证书问题时警告用户

密码管理器

生成和存储复杂密码

隐私模式

不保存浏览数据,保护隐私

弹窗拦截器

拦截不需要的弹窗广告

下载扫描

扫描下载文件,确保安全

插件/扩展安全检查

防止恶意插件和扩展

表单自动填充保护

限制自动填充,防止数据被窃取

浏览器作为用户与互联网交互的重要工具,内置了多种安全措施来保护用户的隐私、数据和设备安全。以下是一些浏览器中常见的安全措施:

同源策略(Same-Origin Policy)

  • 限制来自不同源(协议、域名、端口)的文档或脚本互相操作,以防止恶意文档窃取数据。

内容安全策略(Content Security Policy, CSP)

  • 允许网站设置哪些内容是可信的,如脚本、样式、图片等,减少XSS攻击的风险。

跨站请求伪造(Cross-Site Request Forgery, CSRF)防护

  • 通过令牌(Token)验证请求的合法性,确保请求是由用户主动发起的。

跨站脚本(Cross-Site Scripting, XSS)防护

  • 浏览器内置了一些机制来减少XSS攻击,如HttpOnly标志的Cookie。

安全的传输层(HTTPS)

  • 使用SSL/TLS加密传输层,保护数据在传输过程中的安全。

证书错误提示

  • 当网站的SSL证书存在问题时,浏览器会警告用户不要继续访问。

密码管理器

  • 浏览器内置密码管理器,帮助用户生成和存储复杂密码。

隐私模式(Incognito Mode)

  • 不保存浏览历史、Cookies和表单数据,保护用户隐私。

弹窗拦截器(Pop-up Blocker)

  • 自动拦截不需要的弹窗广告,减少恶意软件的传播。

下载扫描

  • 浏览器会扫描下载的文件,确保没有携带恶意软件。

插件/扩展安全检查

  • 浏览器会对插件和扩展进行安全检查,防止恶意软件安装。

表单自动填充保护

  • 浏览器限制自动填充功能,以防止表单数据被恶意脚本窃取。

最佳实践:

  • 始终使用HTTPS来保护用户数据。
  • 开启CSP并合理配置,以增强网站安全。
  • 对于重要操作,使用CSRF令牌来验证请求。
  • 教育用户定期更新密码,并使用密码管理器。
  • 定期审查和更新浏览器插件和扩展,以保持最新安全状态。

通过这些安全措施,浏览器能够为用户提供一个相对安全和私密的网络环境。

415、渲染Canvas底层原理

Canvas 是 HTML5 提供的一种绘图API,它可以用于动态生成图形、图像和动画等视觉效果。Canvas 的底层原理主要包括以下几个方面:

  1. HTML 解析和 DOM 树构建:当浏览器解析 HTML 代码时,会创建 DOM(Document Object Model)树结构。在 DOM 树中,如果遇到 <canvas> 标签,浏览器会创建一个对应的 Canvas 元素。

  2. 像素缓冲区创建:Canvas 元素创建后,浏览器会为其分配一个像素缓冲区,这个缓冲区是一个二维网格,每个网格单元称为像素。像素缓冲区的大小可以通过设置 Canvas 元素的宽度和高度属性来指定。

  3. 2D 或 3D 上下文获取:通过 JavaScript 代码,我们可以获取到 Canvas 元素的上下文对象,即 CanvasRenderingContext2D(2D 上下文)或 WebGLRenderingContext(3D 上下文),后者用于基于 WebGL 绘制 3D 图形。

  4. 绘制命令执行:通过调用上下文对象的绘制方法,比如 fillRect()drawImage() 等,我们可以向像素缓冲区绘制内容。这些绘制命令包括绘制形状、绘制文本、绘制图像等。

  5. 栅格化和渲染:当执行了绘制命令后,浏览器会将绘制的图像转换成栅格(raster)或位图。这个过程称为栅格化。栅格化后的图像会被传递给图形处理单元(GPU)进行渲染,以便在屏幕上显示。

  6. 显示和更新:最后,渲染好的图像会被显示在用户的屏幕上。如果有必要,当 Canvas 内容发生变化时,可以通过重新执行绘制命令来更新画布上的内容。

需要注意的是,Canvas 的底层原理可能因浏览器而异,不同浏览器对于渲染管线和优化等方面可能有所差异。但总体来说,Canvas 的原理是通过将绘制命令转换成栅格图像,再进行渲染和显示,实现在网页中动态绘制图形的功能。