语法降级与Polyfill:消灭低版本浏览器兼容问题

news2024/12/24 7:15:53

提到前端编译工具链方面,可能大家最新想到的是诸如@babel/preset-env、core-js、regenerator-runtime等工具。不过,我们今天要讲的是官方的 Vite 插件@vitejs/plugin-legacy,以及如何将这些底层的工具链接入到 Vite 中,并实现开箱即用的解决方案。

一、浏览器兼容问题

首先我们来复现一下问题场景,下面两张图代表了之前我在线上环境真实遇到的报错案例。

image.png
某些低版本浏览器并没有提供 Promise 语法环境以及对象和数组的各种 API,甚至不支持箭头函数语法,代码直接报错,从而导致线上白屏事故的发生,尤其是在IE 11、iOS 9以及Android 4.4等场景中很容易遇到。

旧版浏览器的语法兼容问题主要分两类: 语法降级问题和 Polyfill 缺失问题。前者比较好理解,比如某些浏览器不支持箭头函数,我们就需要将其转换为function(){}语法;而对后者来说,Polyfill本身可以翻译为垫片,也就是为浏览器提前注入一些 API 的实现代码,如Object.entries方法的实现,这样可以保证产物可以正常使用这些 API,防止报错。

这两类问题本质上是通过前端的编译工具链(如Babel)及 JS 的基础 Polyfill 库(如corejs)来解决的,不会跟具体的构建工具所绑定。也就是说,对于这些本质的解决方案,在其它的构建工具(如 Webpack)能使用,在 Vite 当中也完全可以使用。

构建工具考虑的仅仅是如何将这些底层基础设施接入到构建过程的问题,自己并不需要提供底层的解决方案,正所谓术业有专攻,把专业的事情交给专业的工具去做。接下来,我们就一起熟悉这些所谓专业的工具,以及如何使用它们。

二、底层工具链

2.1 工具概览

解决上述提到的两类语法兼容问题,主要需要用到两方面的工具,分别包括:

  • 编译时工具:代表工具有@babel/preset-env和@babel/plugin-transform-runtime。
  • 运行时基础库:代表库包括core-js和regenerator-runtime。

编译时工具的作用是在代码编译阶段进行语法降级及添加 polyfill 代码的引用语句,如下。

import "core-js/modules/es6.set.js"

由于这些工具只是编译阶段用到,运行时并不需要,我们需要将其放入package.json中的devDependencies中。

而运行时基础库是根据 ESMAScript官方语言规范提供各种Polyfill实现代码,主要包括core-js和regenerator-runtime两个基础库,不过在 babel 中也会有一些上层的封装,包括:

  • @babel/polyfill
  • @babel/runtime
  • @babel/runtime-corejs2
  • @babel/runtime-corejs3

2.2 基本使用

了解了基本概念后,接下来我们来通过代码实操的方式来学习这些工具,代码我也已经放到了github仓库中,可以对照学习。如果你没拉取仓库的代码,也可以先按照如下的命令初始化项目:

mkdir babel-test
npm init -y

然后安装一些必要的依赖:

pnpm i @babel/cli @babel/core @babel/preset-env

下面是各个依赖的作用:

  • @babel/cli: 为 babel 官方的脚手架工具,很适合我们练习用。
  • @babel/core: babel 核心编译库。
  • @babel/preset-env: babel 的预设工具集,基本为 babel 必装的库。

接着,新建 src 目录,在目录下增加index.js文件。

const func = async () => {
  console.log(12123)
}


Promise.resolve().finally();

可以看到,示例代码中既包含了高级语法也包含现代浏览器的API,正好可以针对语法降级和 Polyfill 注入两个功能进行测试。接下来,新建.babelrc.json即 babel 的配置文件,内容如下:

{
  "presets": [
    [
      "@babel/preset-env", 
      {
        // 指定兼容的浏览器版本
        "targets": {
          "ie": "11"
        },
        // 基础库 core-js 的版本,一般指定为最新的大版本
        "corejs": 3,
        // Polyfill 注入策略,后文详细介绍
        "useBuiltIns": "usage",
        // 不将 ES 模块语法转换为其他模块语法
        "modules": false
      }
    ]
  ]
}

其中,有两个比较关键的配置: targets和usage。我们可以通过 targets 参数指定要兼容的浏览器版本,你既可以填如上配置所示的一个对象。

{
  "targets": {
    "ie": "11"
  }
}

也可以用 Browserslist 配置语法:

{ 
  // ie 不低于 11 版本,全球超过 0.5% 使用,且还在维护更新的浏览器
  "targets": "ie >= 11, > 0.5%, not dead"
}

Browserslist 是一个帮助我们设置目标浏览器的工具,不光是 Babel 用到,其他的编译工具如postcss-preset-env、autoprefix中都有所应用。对于Browserslist的配置内容,你既可以放到 Babel 这种特定工具当中,也可以在package.json中通过browserslist声明:

// package.json
{ 
  "browserslist": "ie >= 11"
}

或者通过.browserslistrc进行声明:

// .browserslistrc
ie >= 11

在实际的项目中,一般我们可以将使用下面这些最佳实践集合来描述不同的浏览器类型,减轻配置负担:

// 现代浏览器
last 2 versions and since 2018 and > 0.5%
// 兼容低版本 PC 浏览器
IE >= 11, > 0.5%, not dead
// 兼容低版本移动端浏览器
iOS >= 9, Android >= 4.4, last 2 versions, > 0.2%, not dead

对于这些配置对应的具体浏览器列表,大家可以去 browserslist.dev 站点查看。

image.png

在说明了目标浏览器的配置之后,接下来我们来看另外一个重要的配置——useBuiltIns,它决定了添加 Polyfill 策略,默认是 false,即不添加任何的 Polyfill。你可以手动将useBuiltIns配置为entry或者usage,接下来我们看看这两个配置究竟有什么区别。

首先,我们可以将这个字段配置为entry,需要注意的是,entry配置规定你必须在入口文件手动添加一行这样的代码:

// index.js 开头加上
import 'core-js';

接着在终端执行下面的命令进行 Babel 编译:

npx babel src --out-dir dist

产物输出在dist目录中,你可以去观察一下产物的代码:

image.png

Babel 已经根据目标浏览器的配置为我们添加了大量的 Polyfill 代码,index.js文件简单的几行代码被编译成近 300 行。实际上,Babel 所做的事情就是将你的import "core-js"代码替换成了产物中的这些具体模块的导入代码。

但这个配置有一个问题,即无法做到按需导入,上面的产物代码其实有大部分的 Polyfill 的代码我们并没有用到。接下来我们试试useBuiltIns: usage这个按需导入的配置,改动配置后执行编译命令。

npx babel src --out-dir dist

同样可以看到,产物输出在了dist/index.js中,内容如下所示:

image.png

可以发现 Polyfill 的代码精简了许多,真正地实现了按需 Polyfill 导入。因此,在实际的使用当中,还是推荐大家尽量使用useBuiltIns: “usage”,进行按需的 Polyfill 注入。

我们来梳理一下,上面我们利用@babel/preset-env进行了目标浏览器语法的降级和Polyfill注入,同时用到了core-js和regenerator-runtime两个核心的运行时库。但@babel/preset-env 的方案也存在一定局限性:

  • 如果使用新特性,往往是通过基础库(如 core-js)往全局环境添加 Polyfill,如果是开发应用没有任何问题,如果是开发第三方工具库,则很可能会对全局空间造成污染。
  • 很多工具函数的实现代码(如上面示例中的_defineProperty方法),会在许多文件中重现出现,造成文件体积冗余。

2.3 transform-runtime

接下来,我们要介绍的transform-runtime方案,就是为了解决@babel/preset-env的种种局限性。需要提前说明的是,transform-runtime方案可以作为@babel/preset-env中useBuiltIns配置的替代品,也就是说,一旦使用transform-runtime方案,你应该把useBuiltIns属性设为 false。

首先,安装一些必要的依赖:

pnpm i @babel/plugin-transform-runtime -D
pnpm i @babel/runtime-corejs3 -S

我解释一下这两个依赖的作用:前者是编译时工具,用来转换语法和添加 Polyfill,后者是运行时基础库,封装了core-js、regenerator-runtime和各种语法转换用到的工具函数。

事实上,core-js 有三种产物,分别是core-js、core-js-pure和core-js-bundle。第一种是全局 Polyfill 的做法,@babel/preset-env 就是用的这种产物;第二种不会把 Polyfill 注入到全局环境,可以按需引入;第三种是打包好的版本,包含所有的 Polyfill,不太常用。@babel/runtime-corejs3 使用的是第二种产物。

接着,我们对.babelrc.json作如下的配置:

{
  "plugins": [
    // 添加 transform-runtime 插件
    [
      "@babel/plugin-transform-runtime", 
      {
        "corejs": 3
      }
    ]
  ],
  "presets": [
    [
      "@babel/preset-env", 
      {
        "targets": {
          "ie": "11"
        },
        "corejs": 3,
        // 关闭 @babel/preset-env 默认的 Polyfill 注入
        "useBuiltIns": false,
        "modules": false
      }
    ]
  ]
}

然后,执行终端命令:

npx babel src --out-dir dist

我们可以对比一下 @babel/preset-env下的产物结果,如下图。

image.png

经过对比我们不难发现,transform-runtime 一方面能够让我们在代码中使用非全局版本的 Polyfill,这样就避免全局空间的污染,这也得益于 core-js 的 pure 版本产物特性;另一方面对于asyncToGeneator这类的工具函数,它也将其转换成了一段引入语句,不再将完整的实现放到文件中,节省了编译后文件的体积。

另外,transform-runtime方案引用的基础库也发生了变化,不再是直接引入core-js和regenerator-runtime,而是引入@babel/runtime-corejs3。

三、Vite 语法降级与 Polyfill 注入

其实,Vite官方已经为我们封装好了一个开箱即用的方案: @vitejs/plugin-legacy,我们可以基于它来解决项目语法的浏览器兼容问题。这个插件内部同样使用 @babel/preset-env 以及 core-js等一系列基础库来进行语法降级和 Polyfill 注入,因此我觉得对于上文所介绍的底层工具链的掌握是必要的,否则无法理解插件内部所做的事情,真正遇到问题时往往会不知所措。

3.1 插件使用

首先,在项目安装一下插件,命令如下:

npm i @vitejs/plugin-legacy -D

然后,在项目的vite.config.ts配置文件中添加配置,如下:

// vite.config.ts
import legacy from '@vitejs/plugin-legacy';
import { defineConfig } from 'vite'


export default defineConfig({
  plugins: [
    // 省略其它插件
    legacy({
      // 设置目标浏览器,browserslist 配置语法
      targets: ['ie >= 11'],
    })
  ]
})

我们同样可以通过targets指定目标浏览器,这个参数在插件内部会透传给@babel/preset-env。在引入插件后,我们可以尝试执行npm run build对项目进行打包,可以看到如下的产物信息:

image.png

可以看到,打出的包多出了index-legacy.js、vendor-legacy.js以及polyfills-legacy.js三份产物文件。让我们继续观察一下index.html的产物内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/assets/favicon.17e50649.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <!-- 1. Modern 模式产物 -->
    <script type="module" crossorigin src="/assets/index.c1383506.js"></script>
    <link rel="modulepreload" href="/assets/vendor.0f99bfcc.js">
    <link rel="stylesheet" href="/assets/index.91183920.css">
  </head>
  <body>
    <div id="root"></div>
    <!-- 2. Legacy 模式产物 -->
    <script nomodule>兼容 iOS nomodule 特性的 polyfill,省略具体代码</script>
    <script nomodule id="vite-legacy-polyfill" src="/assets/polyfills-legacy.36fe2f9e.js"></script>
    <script nomodule id="vite-legacy-entry" data-src="/assets/index-legacy.c3d3f501.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
  </body>
</html>

通过官方的legacy插件, Vite 会分别打包出Modern模式和Legacy模式的产物,然后将两种产物插入同一个 HTML 里面,Modern产物被放到 type="module"的 script 标签中,而Legacy产物则被放到带有 nomodule 的 script 标签中。浏览器的加载策略如下图所示:

image.png
这样产物便就能够同时放到现代浏览器和不支持type="module"的低版本浏览器当中执行。当然,在具体的代码语法层面,插件还需要考虑语法降级和 Polyfill 按需注入的问题,接下来我们就来分析一下 Vite 的官方legacy插件是如何解决这些问题的。

3.2 插件执行原理

官方的legacy插件是一个相对复杂度比较高的插件,直接看源码可能会很难理解,这里我梳理了画了一张简化后的流程图。

image.png

可以看到,首先是在configResolved钩子中调整了output属性,这么做的目的是让 Vite 底层使用的打包引擎 Rollup 能另外打包出一份Legacy 模式的产物,实现代码如下:

const createLegacyOutput = (options = {}) => {
  return {
    ...options,
    // system 格式产物
    format: 'system',
    // 转换效果: index.[hash].js -> index-legacy.[hash].js
    entryFileNames: getLegacyOutputFileName(options.entryFileNames),
    chunkFileNames: getLegacyOutputFileName(options.chunkFileNames)
  }
}


const { rollupOptions } = config.build
const { output } = rollupOptions
if (Array.isArray(output)) {
  rollupOptions.output = [...output.map(createLegacyOutput), ...output]
} else {
  rollupOptions.output = [createLegacyOutput(output), output || {}]
}

接着,在renderChunk阶段,插件会对 Legacy 模式产物进行语法转译和 Polyfill 收集。值得注意的是,这里并不会真正注入Polyfill,而仅仅只是收集Polyfill。

{
  renderChunk(raw, chunk, opts) {
    // 1. 使用 babel + @babel/preset-env 进行语法转换与 Polyfill 注入
    // 2. 由于此时已经打包后的 Chunk 已经生成
    //   这里需要去掉 babel 注入的 import 语句,并记录所需的 Polyfill
    // 3. 最后的 Polyfill 代码将会在 generateBundle 阶段生成
  }
}

由于场景是应用打包,这里直接使用 @babel/preset-env 的useBuiltIns: 'usage’来进行全局 Polyfill 的收集是比较标准的做法。

回到 Vite 构建的主流程中,接下来会进入generateChunk钩子阶段,现在 Vite 会对之前收集到的Polyfill进行统一的打包,实现也比较精妙,核心代码主要逻辑集中在buildPolyfillChunk函数中。

// 打包 Polyfill 代码
async function buildPolyfillChunk(
  name,
  imports
  bundle,
  facadeToChunkMap,
  buildOptions,
  externalSystemJS
) {
  let { minify, assetsDir } = buildOptions
  minify = minify ? 'terser' : false
  // 调用 Vite 的 build API 进行打包
  const res = await build({
    // 根路径设置为插件所在目录
    // 由于插件的依赖包含`core-js`、`regenerator-runtime`这些运行时基础库
    // 因此这里 Vite 可以正常解析到基础 Polyfill 库的路径
    root: __dirname,
    write: false,
    // 这里的插件实现了一个虚拟模块
    // Vite 对于 polyfillId 会返回所有 Polyfill 的引入语句
    plugins: [polyfillsPlugin(imports, externalSystemJS)],
    build: {
      rollupOptions: {
        // 访问 polyfillId
        input: {
          // name 暂可视作`polyfills-legacy`
          // pofyfillId 为一个虚拟模块,经过插件处理后会拿到所有 Polyfill 的引入语句
          [name]: polyfillId
        },
      }
    }
  });
  // 拿到 polyfill 产物 chunk
  const _polyfillChunk = Array.isArray(res) ? res[0] : res
  if (!('output' in _polyfillChunk)) return
  const polyfillChunk = _polyfillChunk.output[0]
  // 后续做两件事情:
  // 1. 记录 polyfill chunk 的文件名,方便后续插入到 Modern 模式产物的 HTML 中;
  // 2. 在 bundle 对象上手动添加 polyfill 的 chunk,保证产物写到磁盘中
}

因此,你可以理解为这个函数的作用即通过 vite build 对renderChunk中收集到 polyfill 代码进行打包,生成一个单独的 chunk。

image.png

需要注意的是,polyfill chunk 中除了包含一些 core-js 和 regenerator-runtime 的相关代码,也包含了 SystemJS 的实现代码,你可以将其理解为 ESM 的加载器,实现了在旧版浏览器下的模块加载能力。

现在我们已经能够拿到 Legacy 模式的产物文件名及 Polyfill Chunk 的文件名,那么就可以通过transformIndexHtml钩子来将这些产物插入到 HTML 的结构中。

{
  transformIndexHtml(html) {
    // 1. 插入 Polyfill chunk 对应的 <script nomodule> 标签
    // 2. 插入 Legacy 产物入口文件对应的 <script nomodule> 标签
  }
}

好了,Vite 官方的 legacy 插件的主要原理就介绍到这里,为了方便大家理解,讲解的过程中忽略了一些与主流程关联不大的细节。

  • 当插件参数中开启了modernPolyfills选项时,Vite 也会自动对 Modern 模式的产物进行 Polyfill 收集,并单独打包成polyfills-modern.js的 chunk,原理和 Legacy 模式下处理 Polyfill 一样。
  • Sarari 10.1 版本不支持 nomodule,为此需要单独引入一些补丁代码,点击查看。
  • 部分低版本 Edge 浏览器虽然支持 type=“module”,但不支持动态 import,为此也需要插入一些补丁代码,针对这种情况下降级使用 Legacy 模式的产物。

四、小结

本节主要讲解了 Vite 中语法降级与 Polyfill 相关的内容,涉及的概念比较多,篇幅也比较长,你需要重点掌握以下内容:

  • @babel/preset-env 的使用。
  • useBuiltIns 与 transformRuntime 两种 Polyfill 方案的区别。
  • Vite 降级插件@vitejs/plugin-legacy 的使用及原理。

首先,我们复现了线上的低版本浏览器语法报错情景,主要分为 语法报错 和 Polyfill 缺失 的问题,由此引出了底层的解决方案——使用 Babel 编译工具链 和 JS 运行时基础库来完成。接着具体介绍了 @babel/preset-env的使用,通过实际的代码案例让你体验了它的语法降级和自动 Polyfill 注入的能力,接着,我又给你介绍了一个更优的 Polyfill 方案——transform-runtime方案,并与@babel/preset-env的useBuiltIns方案进行了对比,分析了transform-runtime方案的两个优化点: 不影响全局空间和优化文件体积。

在介绍了底层的解决方案之后,我们开始学习在 Vite 中的解决方案——@vitejs/plugin-legacy,分析了它如何让产物能够同时兼容现代浏览器和不支持 type="module"的低版本浏览器,接着深入地讲解了这个插件的实现原理,你可以发现底层也是通过@babel/preset-env来完成兼容方案的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/673277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【强化学习】——Q-learning算法为例入门Pytorch强化学习

&#x1f935;‍♂️ 个人主页&#xff1a;Lingxw_w的个人主页 ✍&#x1f3fb;作者简介&#xff1a;计算机研究生在读&#xff0c;研究方向复杂网络和数据挖掘&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;CSDN专家博主、人工智能领域优质创作者&#xf…

神经网络:参数更新

在计算机视觉中&#xff0c;参数更新是指通过使用梯度信息来调整神经网络模型中的参数&#xff0c;从而逐步优化模型的性能。参数更新的作用、原理和意义如下&#xff1a; 1. 作用&#xff1a; 改进模型性能&#xff1a;参数更新可以使模型更好地适应训练数据&#xff0c;提高…

python学习——pandas统计分析基础

目录 pandas统计分析基础1. Series数据2.文件读取csv文件Excel文件 3.DataFrame连接数据库读取数据库存入数据库DataFrame的属性访问DataFrame中的数据【实例1】info详细信息和describe描述统计分析【实例2】 排序【实例3】 布尔索引&#xff0c;条件索引【案例】修改数据 3.描…

LIBSVM与LIBLINEAR支持向量机库对模式识别与回归的可视化代码实践

支持向量机(SVM)是一种流行的分类技术。虽然提出时间到现在有70来年了&#xff0c;但在90年代获得了很好的发展和扩展&#xff0c;在人像识别、文本分类、手写字符识别、生物信息学等模式识别问题中有得到应用。然而&#xff0c;对于不熟悉SVM的初学者来说&#xff0c;往往会因…

ThreadPoolExecutor解读

目录 线程池状态 构造方法 newFixedThreadPool newCachedThreadPool newSingleThreadExecutor 提交任务 关闭线程池 其它方法 线程池状态 ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态&#xff0c;低 29 位表示线程数量 状态名 高 3 位 接收新任务 处理…

JavaScript ES10新特性

文章目录 导文Array.prototype.flat()和Array.prototype.flatMap()Object.fromEntries()String.prototype.trimStart()和String.prototype.trimEnd()格式化数字动态导入可选的catch绑定BigIntglobalThis 导文 JavaScript ES10&#xff0c;也被称为ES2019&#xff0c;引入了一些…

javascript被禁用怎么办?怎么启用?||如何解决javascript:void(0)的问题?

javascript被禁用怎么办&#xff1f;怎么启用&#xff1f; 有些小伙伴可能因为浏览器弹窗的凌乱而感到烦恼&#xff0c;想要通过浏览器禁用JavaScript的方式来避免这些广告。有些小伙伴则是因为设置了不知名的设置导致JavaScript被禁用&#xff0c;影响日常的使用。接下来的这…

Vue3的计算属性和监听属性

目录 computed 语法介绍 简写版 完整版 watch 介绍 监听ref式数据代码示例 监听reactive式数据 watchEffect函数 computed 语法介绍 与Vue2.x中computed配置功能一致 import {computed} from vuesetup(){...//计算属性——简写let fullName computed(()>{return per…

【kubernetes】部署kubelet与kube-proxy

前言:二进制部署kubernetes集群在企业应用中扮演着非常重要的角色。无论是集群升级,还是证书设置有效期都非常方便,也是从事云原生相关工作从入门到精通不得不迈过的坎。通过本系列文章,你将从虚拟机准备开始,到使用二进制方式从零到一搭建起安全稳定的高可用kubernetes集…

ffmpeg调整音频音量踩坑

前一阵用Flutter结合ffmpeg做了一个音视频合并功能&#xff0c;记录一下遇到的问题。 合并方法 首先是音视频合并命令&#xff1a; ffmpeg -i input.mp4 -i input.mp3 -filter_complex "[1:a]adelay0s:all1[a1];[a1]amixinputs1[amixout]" -map 0:v:0 -map "…

Ts系列之条件类型

Ts系列之条件类型 Ts系列之条件类型前言一、初遇二、条件判断三、待补充 Ts系列之条件类型 前言 本片文章主要对ts条件类型的用法做一个讲解。 一、初遇 1、首先我们来看看一个小例子&#xff1a; interface Person {name: string;age: number; } interface Son extends P…

神经网络:梯度计算

在计算机视觉中&#xff0c;梯度计算是一项关键任务&#xff0c;它在优化算法中扮演着重要的角色。梯度表示函数在某一点上的变化率&#xff0c;可以指导模型参数的更新&#xff0c;使得模型逐步接近最优解。下面我将详细解释梯度计算的作用、原理和意义。 作用&#xff1a; 梯…

C++ 新的类型转换

文章目录 前言一、静态转换&#xff08;static_cast&#xff09;二、动态转换&#xff08;dynamic_cast&#xff09;&#xff1a;三、常量转换&#xff08;const_cast&#xff09;&#xff1a;四、重新解释转换&#xff08;reinterpret_cast&#xff09;&#xff1a;总结 前言 …

基于Java+Swing实现记事本-完美版

基于JavaSwing实现记事本-完美版 一、系统介绍二、功能展示1.主页2.文件功能3.编辑功能4.格式--功能5.查看功能 三、系统实现1. Fontv.java 四、其它1.其他系统实现2.获取源码 一、系统介绍 1.主页 2.文件功能 3.编辑功能 4.格式–功能 5.查看功能 二、功能展示 1.主页 2.文…

一文带你弄清Map集合及其实现类(适合小白秋招篇)

前言&#xff1a; 本篇文章主要讲解Java中的Map集合接口以及相关实现类的知识。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出&#xff0c;对大佬有帮助希望可以支持下哦~ 小威在此先感谢各位小伙伴儿了&#x1f601; 以…

【Linux】冯诺依曼体系结构 操作系统 进程概念

目录 一、冯诺依曼体系结构 二、操作系统 1、概念 2、设计OS的目的 三、进程 1、基本概念 2、描述进程-PCB 3、组织进程 4、查看进程和终止 5、通过系统调用获取进程标识符 6、通过系统调用创建进程-fork 7、进程状态 8、特殊进程 8.1 僵尸进程 8.2 孤儿进程 一、冯诺依曼体…

【从零开始学习JAVA | 第八篇】String类

目录 前言&#xff1a; String类&#xff1a; 常见的认识误区&#xff1a; 创建String类&#xff1a; 注意点&#xff1a; 总结&#xff1a; 前言&#xff1a; String类是Java中最常见的一个类&#xff0c;本篇将对Stirng类的各种功能进行详细的介绍&#xff0c;各位小伙伴…

js:使用vue-codemirror实现一个语法高亮的网页代码编辑器

codemirror code editor component for vuejs 译文&#xff1a;vuejs的codemirror代码编辑器组件 文档 https://github.com/surmon-china/vue-codemirror 安装 # 依赖 pnpm install codemirror vue-codemirror --save# 语言 pnpm install codemirror/lang-json --save pnpm …

【VulnHub系列】MyFileServer

因为是从PDF转换过来偶尔可能会出现内容缺少&#xff0c;可以看原版PDF&#xff1a;有道云笔记 实验环境 Kali&#xff1a;192.168.10.102 MyFileServer&#xff1a;192.168.10.106 实验过程 通过arp-scan来发现靶机的IP地址 sudo arp-scan --interface eth0 192.168.10.1…

mediapipe 谷歌高效ML框架-图像识别、人脸检测、关键点检测

参考&#xff1a; https://github.com/google/mediapipe https://developers.google.com/mediapipe/solutions/guide 框架也支持cv、nlp、audio等项目&#xff0c;速度很快&#xff1a; 1、图形识别 参考&#xff1a;https://developers.google.com/mediapipe/solutions/vi…