自研框架跻身全球 JS 框架榜单,排名紧随 React、Angular 之后!

前言

终于实现了一个重要目标!我独立研发的 JavaScript 框架 Strve,最近发布了重大版本 6.0.2。距离上次大版本发布已经接近两个月,期间进行了大量的优化,使得框架性能和稳定性都得到了大幅度的提升。在上次的大版本更新中,成功实现了对 JSX 语法的全面支持,使得 Strve 在代码智能提示和代码格式化方面更加友好,进一步提高了开发效率。

介绍

相信有些小伙伴没有听说过 Strve 到底是什么,那我这里就大体介绍一下。

Strve 是一个可以将字符串转换为视图(用户界面)的 JavaScript 库。Strve 不仅易于使用,而且可以灵活地拆解不同的代码块。使用模板字符串开发用户界面,主要是利用 JavaScript 的能力,只关注 JavaScript 文件。Strve 又是一个易用性的 JavaScript 框架,它提供了很多实用的功能与生态工具。

我们可以通过一些简单的示例来了解 Strve 的使用方法。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>Strve.js</title></head><body><script src="https://cdn.jsdelivr.net/npm/strve-js@6.0.2/dist/strve.full.prod.js"></script><script>const { html, setData, createApp } = Strve;const state = {count: 0,};function add() {setData(() => {state.count++;});}function App() {return html`<h1 onClick=${add}>${state.count}</h1>`;}const app = createApp(App);app.mount('#app');</script></body>
</html>

在上述代码中,我们通过引入 Strve 库,并使用 createApp 方法创建了一个 App 组件,然后通过 mount 方法挂载到页面上,这里的 App 组件就是通过模板字符串来定义的。这样就可以在 JS 代码中编写用户界面,是不是很方便呢?我们发现,在模板字符串中,我们使用 ${} 来引用数据,并且使用 onClick 方法来绑定事件。这样就可以实现一个计数器的功能。

除了这种简单的示例,Strve 还支持很多复杂的功能,我们可以使用 JSX 语法来编写组件,也可以使用函数式组件来编写组件,还可以使用组件来编写组件,甚至可以编写一些自定义的组件。

如果想了解更多关于 Strve 的信息,稍后可以到文章末尾处查阅官方文档。

性能评估

我们既然发布了 Strve,那么肯定需要对其性能进行评估,我们评估的工具就用js-framework-benchmark 。js-framework-benchmark 是什么?我们这里就简单介绍下 js-framework-benchmark,它是一个用于比较 JavaScript 框架性能的项目。它旨在通过执行一系列基准测试来评估不同框架在各种场景下的性能表现。这些基准测试包括渲染大量数据、更新数据、处理复杂的 UI 组件等。通过运行这些基准测试,可以比较不同框架在各种方面的性能优劣,并帮助开发人员选择最适合其需求的框架。js-framework-benchmark 项目提供了一个包含多个流行 JavaScript 框架的基准测试套件。这些框架包括 Angular、React、Vue 等。每个框架都会在相同的测试场景下运行,然后记录下执行时间和内存使用情况等性能指标。通过比较这些指标,可以得出不同框架的性能差异。这个项目的目标是帮助开发人员了解不同 JavaScript 框架的性能特点,以便在选择框架时能够做出更加明智的决策。同时,它也可以促进框架开发者之间的竞争,推动框架的不断改进和优化。

在评估之前,我们必须要了解 js-framework-benchmark 中有两种模式。一种是 keyed,另一种是 non-keyed。在 js-framework-benchmark 中,“keyed” 模式是指通过给数据项分配一个唯一标识符作为 “key” 属性,从而实现数据项与 DOM 节点之间的一对一关系。当数据发生变化时,与之相关联的 DOM 节点也会相应更新。而 non-keyed 模式是指当数据项发生变化时,可能会修改之前与其他数据项关联的 DOM 节点。

因为 Strve 支持keyed模式,所以我们将使用此模式来评估 Strve 的性能。

对以下操作进行了基准测试:

  • 创建行:页面加载后创建 1,000 行的持续时间(无预热)。
  • 替换所有行:替换表中所有 1,000 行的持续时间(5 次预热迭代)。
  • 部分更新:对于具有 10,000 行的表,每 10 行更新一次文本(进行 5 次预热迭代)。
  • 选择行:响应单击该行而突出显示该行的持续时间。 (5 次预热迭代)。
  • 交换行:在包含 1,000 行的表中交换 2 行的时间。 (5 次预热迭代)。
  • 删除行:删除具有 1,000 行的表的行的持续时间。 (5 次预热迭代)。
  • 创建多行:创建 10,000 行的持续时间(无预热)
  • 将行追加到大型表:在包含 10,000 行的表中添加 1,000 行的持续时间(无预热)。
  • 清除行:清除填充有 10,000 行的表的持续时间。 (无热身)
  • 就绪内存:页面加载后的内存使用情况。
  • 运行内存:添加 1,000 行后的内存使用情况。
  • 更新内存:1000 行的表点击 5 次更新后的内存使用情况。
  • 替换内存:点击 5 次创建 1000 行后的内存使用情况。
  • 重复清除内存:创建并清除 1,000 行 5 次后的内存使用情况。
  • 更新内存:1000 行的表点击 5 次更新后的内存使用情况。
  • 启动时间:加载和解析 javascript 代码以及渲染页面的持续时间。
  • 持续交互:灯塔指标 TimeToConstantlyInteractive:悲观 TTI - 当 CPU 和网络都非常空闲时。 (不再有超过 50 毫秒的 CPU 任务)
  • 脚本启动时间:灯塔指标 ScriptBootUpTtime:解析/编译/评估所有页面脚本所需的总毫秒数
  • 主线程工作成本:灯塔指标 MainThreadWorkCost:在主线程上工作所花费的总时间包括样式/布局等。
  • 总字节权重:灯塔指标 TotalByteWeight:加载到页面中的所有资源的网络传输成本(压缩后)。

对于所有基准测试,都会测量持续时间,包括渲染时间。

因为js-framework-benchmark是一个自动化测试的工具,只需要符合标准的代码就可以进行测试。Strve 支持 JSX 语法,所以我们将使用 JSX 语法来编写测试代码。

import { setData, createApp } from 'strve-js';
import { buildData } from './data.js';let selected;
let rows = [];function setRows(update = rows.slice()) {setData(() => {rows = update;},{name: TbodyComponent,});
}function add() {const data = rows.concat(buildData(1000));setData(() => {rows = data;},{name: TbodyComponent,});
}function remove(id) {rows.splice(rows.findIndex((d) => d.id === id),1);setRows();
}function select(id) {setData(() => {selected = id;},{name: TbodyComponent,});
}function run() {setRows(buildData());selected = undefined;
}function update() {for (let i = 0; i < rows.length; i += 10) {rows[i].label += ' !!!';}setRows();
}function runLots() {setRows(buildData(10000));selected = undefined;
}function clear() {setRows([]);selected = undefined;
}function swapRows() {if (rows.length > 998) {const d1 = rows[1];const d998 = rows[998];rows[1] = d998;rows[998] = d1;setRows();}
}function TbodyComponent() {return (<tbody>{rows.map((item) => (<tr class={item.id === selected ? 'danger' : ''} data-label={item.label} key={item.id}><td class='col-md-1'>{item.id}</td><td class='col-md-4'><a onClick={() => select(item.id)}>{item.label}</a></td><td class='col-md-1'><a onClick={() => remove(item.id)}><span class='glyphicon glyphicon-remove' aria-hidden='true'></span></a></td><td class='col-md-6'></td></tr>))}</tbody>);
}function MainBody() {return (<fragment><div class='jumbotron'><div class='row'><div class='col-md-6'><h1>Strve-keyed</h1></div><div class='col-md-6'><div class='row'><div class='col-sm-6 smallpad'><button type='button' class='btn btn-primary btn-block' id='run' onClick={run}>Create 1,000 rows</button></div><div class='col-sm-6 smallpad'><buttontype='button'class='btn btn-primary btn-block'id='runlots'onClick={runLots}>Create 10,000 rows</button></div><div class='col-sm-6 smallpad'><button type='button' class='btn btn-primary btn-block' id='add' onClick={add}>Append 1,000 rows</button></div><div class='col-sm-6 smallpad'><buttontype='button'class='btn btn-primary btn-block'id='update'onClick={update}>Update every 10th row</button></div><div class='col-sm-6 smallpad'><button type='button' class='btn btn-primary btn-block' id='clear' onClick={clear}>Clear</button></div><div class='col-sm-6 smallpad'><buttontype='button'class='btn btn-primary btn-block'id='swaprows'onClick={swapRows}>Swap Rows</button></div></div></div></div></div><table class='table table-hover table-striped test-data'><component $name={TbodyComponent.name}>{TbodyComponent()}</component></table><span class='preloadicon glyphicon glyphicon-remove' aria-hidden='true'></span></fragment>);
}createApp(() => MainBody()).mount('#main');

以下页面就是将进行基准测试的页面:

在这里插入图片描述
我们大体看下测试过程,我们将使用动图来展示页面效果,这样会觉得更加直观。

在这里插入图片描述

最终,Strve 通过了压力测试!

在这里插入图片描述

基准测试结果

既然我们通过测试,我们就需要提交到js-framework-benchmark官方项目中,进行综合评估,与全球其他框架进行比较。

我们提交的 PR 在 2023 年 9 月 18 号被作者合并了。

在这里插入图片描述

在接下来的时间里,作者进行了一系列的测试。最终,Chrome 118 版本于上周发布,并在 GitHub 上公布了官方的测试结果。

在这里插入图片描述

我们打开下面的网址,看下 Strve 的官方测试结果:

https://krausest.github.io/js-framework-benchmark/2023/table_chrome_118.0.5993.70.html

经过查询,全球 JavaScript 框架榜单中共有 142 个框架。

性能测试基准分为三类:

  • 持续时间
  • 启动指标
  • 内存分配

【持续时间】

在此测试基准中,Strve 平均值 1.42,排名第 90 位。

React、Angular 和 Vue,平均值分别为1.40、1.38 和 1.20,分别排名第 85 位、第 83 位和第 51 位。

平均值越小,排名则越靠前。颜色越绿代表越优。

在这里插入图片描述

【启动指标】

在此测试基准中,Strve 平均值 1.07。

React、Angular 和 Vue,平均值分别为 1.68、1.80 和 1.30。

平均值越小,排名则越靠前。颜色越绿代表越优。

在这里插入图片描述

【内存分配】

在此测试基准中,Strve 平均值 1.33。

React、Angular 和 Vue,平均值分别为 2.46、2.82 和 1.86。

平均值越小,排名则越靠前。颜色越绿代表越优。

在这里插入图片描述

新特性

我们在上面的测试中,可以看到 Strve 性能表现非常不错。

这次我们发布的大版本号为 6.0.2,我们将这个具有里程碑意义的大版本命名为 Strve6,而 “Strve6,从芯出发!” 这个口号正是 Strve6 的核心理念。这一版本象征着我们从底层技术出发,致力于为用户提供更优质、更高效的开发体验。

此次版本我们在性能与体验之间做了权衡。在源码层面,我们将普通 Diff 算法升级为 双端 Diff 算法,大大提升了性能。另外,我们在用户体验层面也做了很大的改进。

这里,我们提到了双端 Diff 算法,我们在面试中经常提到这个概念,但是很少用到实际项目中去。那么,为了更好地理解双端 Diff 算法如何提高性能,我们来看一个关于 Strve 简单的示例。

我们来遍历一个数组,并且每次点击按钮,往数组头部中添加一个元素。

【普通 Diff 算法】

<script type="module">import {html,setData,createApp,} from 'https://cdn.jsdelivr.net/npm/strve-js@6.0.2/dist/strve.full-esm.js';const state = {arr: [1, 2],count: 3,};function useUnshift() {setData(() => {state.count++;state.arr.unshift(state.count);});}function App() {return html`<fragment><button onClick=${useUnshift}>Unshift</button><ul>${state.arr.map((todo) => html`<li>${todo}</li>`)}</ul></fragment>`;}const app = createApp(App);app.mount('#app');
</script>

我们可以看到右侧 DOM 树,每次点击按钮,都会重新渲染整个列表。这样是肯定耗损浏览器性能的。

在这里插入图片描述

【双端 Diff 算法】

<script type="module">import {html,setData,createApp,} from 'https://cdn.jsdelivr.net/npm/strve-js@6.0.2/dist/strve.full-esm.js';const state = {arr: [1, 2],count: 3,};function useUnshift() {setData(() => {state.count++;state.arr.unshift(state.count);});}function App() {return html`<fragment><button onClick=${useUnshift}>Unshift</button><ul>${state.arr.map((todo) => html`<li key=${todo}>${todo}</li>`)}</ul></fragment>`;}const app = createApp(App);app.mount('#app');
</script>

我们可以看到右侧 DOM 树,每次点击按钮,仅添加必要的元素,而不是重新渲染整个列表。这是因为我们在每个列表项中添加了 key 属性,并且这个 key 是唯一的。key 这个特殊的 attribute 主要作为 Strve 的虚拟 DOM 算法提示,在比较新旧节点列表时用于识别 vnode。只要标签类型与 key 值都相等,就说明当前元素可以被复用。

在这里插入图片描述

热门话题

文章接近尾声,让我们来回顾一下最近社区的几个热门话题。

  1. 为什么要开发这个框架?初衷是什么?

答:其实,我的动机特别简单,完全受 JSX 语法的影响。刚接触 JSX 语法的时候,就被它那种魔法深深地吸引住了,可以在 JS 中写 HTML。所以,我就想我自己可不可以也搞一个类似 JSX 语法的库或者框架呢!一方面可以锻炼自己的代码能力,另一方面体验开发框架的整个流程,也方便我以后更全面的学习其他框架(Vue.js、React.js 等)。

做自己喜欢的事情是特别有意义的!

  1. 为什么选择 Strve 作为框架的名字?

答:Strve 最初定位是可以将字符串转换为视图(用户界面)的 JavaScript 库,所以是由 String 和 View 两个单词缩减组成的新单词。

  1. 跟前端热门框架比较,是想超过它们吗?

答:不是,我主要是想学习一下前端热门框架的实现原理,然后自己实现一个框架。有一句话说得好:“只有站在巨人的肩膀上才能望得更远!”。

  1. 记得之前也写过登上框架榜单的文章,这次为什么还要写?

答:之前,Strve 测评的模式是使用"non-keyed"。现在,Strve 新的版本支持"keyed"模式,所以,我重新写了一篇文章,来介绍 Strve 的新特性。

  1. Strve 6.0.2 版本发布,普通 Diff 算法升级为双端 Diff 算法,可以简单讲下双端 Diff 算法的概念吗?

答:双端 diff 算法就是头尾指针向中间移动,分别判断头尾节点是否可以复用,如果没有找到可复用的节点再去遍历查找对应节点的下标,然后移动。全部处理完之后要对剩下的节点进行批量的新增和删除。

  1. Strve 是个 JavaScript 库还是 JavaScript 框架?

答:首先,我们来看下框架与库有什么区别?库更多是一个封装好的特定的集合,提供给开发者使用,而且是特定于某一方面的集合(方法和函数),库没有控制权,控制权在使用者手中,在库中查询需要的功能在自己的应用中使用,我们可以从封装的角度理解库;框架顾名思义就是一套架构,会基于自身的特点向用户提供一套相当于叫完整的解决方案,而且控制权的在框架本身,使用者要找框架所规定的某种规范进行开发。Strve 可以是框架,因为 Strve 提供了路由、插件等生态工具;Strve 也可以是库, 因为 Strve 可以单独作为一个渲染库。

  1. Strve 你还要继续维护下去吗?

答:是的,我还会继续维护下去,因为我也想学习下去,也希望能帮助到更多前端开发者。

关于

Strve 我是从 2021 年下半年开始开发,到现在也快两年了。在这两年中,从一个之前只会 调用 API 的码农,到现在可以独立开发一个框架,让我收获了很多。学习了如何去分析一个框架的实现原理,也学习了如何去设计一个框架。

Strve 源码仓库:https://github.com/maomincoding/strve

Strve 中文文档:https://maomincoding.gitee.io/strve-doc-zh/

如果大家觉得 Strve 还不错,麻烦帮我点下 Star 吧,谢谢!

结语

感谢各位读者的阅读,希望本文能对你有所帮助,如果喜欢本文,欢迎点赞,欢迎关注!

最后,分享一段话给大家:

很多时候

不是有希望才去坚持

而是在坚持的过程中慢慢看到希望

我们都是在暗夜里赶路的人

纵使满身疲惫也不肯轻言放弃

愿你所坚持的东西

终有一天反过来拥抱你

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

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

相关文章

nodejs | js | ts | axios |下载远程链接图片

原理: 获取二进制流写入文件 import axios from axios; import fs from fs; export async function downloadImage(url: string, filename: string) {try {const response await axios.get(url, { responseType: stream });response.data.pipe(fs.createWriteStream(filename)…

css 两栏布局的实现

目录 前言 1. 浮动布局 用法 代码示例 理解 2. Flex布局 用法 代码示例 理解 3. Grid布局 用法 代码示例 理解 高质量的设计 前言 两栏布局是一种常见的网页设计模式&#xff0c;它将页面分为两个主要区域&#xff1a;主内容区域和侧边栏。这种布局方式不仅能够提…

天堂2游戏出错如何解决

运行游戏时出现以下提示&#xff1a;“the game may not be consistant because AGP is deactivated please activate AGP for consistancy” 这个问题的原因可能是由于您的显示卡的驱动或者主板的显示芯片组的驱动不是新开。或您虽然已经更新了您的显示卡的驱动程序&#xff0…

nodejs+vue城市轨道交通线路查询系统-计算机毕业设计

着社会的快速发展&#xff0c;计算机的影响是全面且深入的。社会生产水平的不断提高&#xff0c;日常生活中人们对备忘记账系统方面的要求也在不断提高&#xff0c;因特网的使用越来越广泛&#xff0c;而在众多的因特网中&#xff0c;万维网更是为人们带来了新鲜的体验。在这当…

在docker环境下从头搭建openvslam/orb_slam3的流程记录以及问题总结

文章目录 0. 前言1. MobaXterm软件2. docker操作2.1. 拉一个ubuntu镜像2.2. 修改名字&#xff08;可选&#xff09;2.3. 删除之前的docker镜像&#xff08;可选&#xff09; 3. openvslam搭建流程3.1. 起容器3.2. 前置包的安装3.3. 安装Eigen3.4. 安装opencv3.5. 安装DBoW23.6.…

MySQL——九、SQL编程

MySQL 一、触发器1、触发器简介2、创建触发器3、一些常见示例 二、存储过程1、什么是存储过程或者函数2、优点3、存储过程创建与调用 三、存储函数1、存储函数创建和调用2、修改存储函数3、删除存储函数 四、游标1、声明游标2、打开游标3、使用游标4、关闭游标游标案例 一、触发…

Flutter extended_image库设置内存缓存区大小与缓存图片数

ExtendedImage ExtendedImage 是一个Flutter库&#xff0c;用于提供高级图片加载和显示功能。这个库使用了 image 包来进行图片的加载和缓存。如果你想修改缓存大小&#xff0c;你可以通过修改ImageCache的配置来实现。 1. 获取ImageCache实例: 你可以通过PaintingBinding…

deeplearning4j训练推理案例2023——手写数字识别

文章目录 1.minist数据集2.依赖包3.手写数字训练与推理4. 扩展阅读deeplearning4j自带学习案例项目deeplearning4j-examples 1.minist数据集 下载链接 6W训练集&#xff0c;1W测试集 2.依赖包 主要是deeplearning4j、javacv的一些包&#xff0c;案例打出的jar包1.3G,pom来自…

超级强大!送你几款Linux 下终极SSH客户端

更多IT技术&#xff0c;请关注微信公众号:“运维之美” 超级强大&#xff01;送你几款Linux 下终极SSH客户端 1.MobaXterm2.Xshell3.SecureCRT4.PuTTY5.FinalShell6.Termius7.WindTerm 安全外壳协议&#xff08;Secure Shell&#xff0c;简称 SSH&#xff09;是一种网络连接协议…

【Gensim概念】02/3 NLP玩转 word2vec

第二部分 句法 六、句法模型&#xff08;类对象和参数&#xff09; 6.1 数据集的句子查看 classgensim.models.word2vec.BrownCorpus(dirname) Bases: object 迭代句子 Brown corpus (part of NLTK data). 6.2 数据集的句子和gram classgensim.models.word2vec.Heapitem(c…

【Docker】Docker数据的存储

默认情况下&#xff0c;在运行中的容器里创建的文件&#xff0c;被保存在一个可写的容器层里&#xff0c;如果容器被删除了&#xff0c;则对应的数据也随之删除了。 这个可写的容器层是和特定的容器绑定的&#xff0c;也就是这些数据无法方便的和其它容器共享。 Docker主要提…

智能井盖监测系统功能,万宾科技传感器效果

智能井盖传感器的出现是高科技产品的更新换代&#xff0c;同时也是智慧城市建设中的需求。在智慧城市建设过程之中&#xff0c;高科技产品的应用数不胜数&#xff0c;智能井盖传感器的出现&#xff0c;解决了城市道路安全保护着城市地下生命线&#xff0c;改善着传统井盖带来的…

责任链模式应用案例

前几天系统商品折扣功能优化&#xff0c;同事采用了责任链模式重构了代码&#xff0c;现整理如下。 一、概念 责任链模式是为请求创建一个处理者对象的链条&#xff0c;所有处理者&#xff08;除最末端&#xff09;都含有下一个对象的引用从而形成一条处理链&#xff0c;该模…

10月最新H5自适应樱花导航网站源码SEO增强版

10月最新H5自适应樱花导航网源码SEO增强版。非常强大的导航网站亮点就是对SEO优化比较好。 开发时PHP版本&#xff1a;7.3开发时MySQL版本&#xff1a;5.7.26 懂前端和PHP技术想更改前端页面的可以看&#xff1a;网站的前端页面不好看&#xff0c;你可以查看index目录&#x…

二、W5100S/W5500+RP2040树莓派Pico<DHCP>

文章目录 1 前言2 简介2 .1 什么是DHCP&#xff1f;2.2 为什么要使用DHCP&#xff1f;2.3 DHCP工作原理2.4 DHCP应用场景 3 WIZnet以太网芯片4 DHCP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 …

vue项目中将html转为pdf并下载

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人…

C/C++不及格学生 2020年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C不及格学生 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C不及格学生 2020年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给出一名学生的语文和数学成绩&#xff0c;判断他是…

web3之链上情报平台Arkham

文章目录 web3之链上情报平台Arkham什么是Arkham链上情报交易所 Arkham Intel Exchange相较于传统情报交易方式,Arkham Intel Exchange下优势 web3之链上情报平台Arkham 什么是Arkham 官网&#xff1a;https://zh.arkhamintelligence.com/ 官方&#xff1a;https://platform.…

如何在 Chrome 中设置HTTP服务器?

首先&#xff0c;定义问题&#xff1a;在 Chrome 浏览器中设置HTTP服务器主要涉及到修改网络设置&#xff0c;使用HTTP服务器可以帮助用户访问网络内容&#xff0c;提高网络速度或者保护隐私。 亲身经验&#xff1a;我曾在使用 Chrome 浏览器时&#xff0c;为了访问一些受限的网…

使用Docker快速搭建服务器环境

简介 这篇文章也是方便自己记录搭建流程&#xff0c;服务器的购买啥的就不说了&#xff0c;最终目标就是在一个空白的Linux系统上&#xff0c;使用docker运行MySQL、TomcatJava、Nginx、Redis 的单机环境&#xff0c;以后方便自己快速的部署服务器。 安装Docker 首先需要安装…