Javascript享元模式

news2024/12/23 10:09:31

Javascript享元模式

  • 1 什么是享元模式
  • 2 内部状态与外部状态
  • 3 享元模式的通用结构
  • 4 文件上传
    • 4.1 对象爆炸
    • 4.2 享元模式重构
  • 5 没有内部状态的享元模式
  • 6 对象池
  • 7 通用对象池实现

1 什么是享元模式

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。

假设服装店新到了50套男士衣服和50套女士衣服,为了推销出去,店里决定买一些模特来穿上衣服进行宣传。一般情况下,需要50个男模特和50个女模特,每个模特穿上衣服拍照,实现代码如下:

var Model = function (sex, underwear) {
  this.sex = sex;
  this.underwear = underwear;
};

Model.prototype.takePhote = function () {
  console.log("sex=" + this.sex + "underwear=" + this.underwear);
};

for (let i = 1; i <= 50; i++) {
  var maleModel = new Model("male", "underwear" + i);
  maleModel.takePhote();
}

for (let j = 1; j <= 50; j++) {
  var femaleModel = new Model("female", "underwear" + j);
  femaleModel.takePhote();
}

如果要得到一张照片,每次都需要传入sexunderwear参数,如上所示,现在一共有50套男款服装和50套女款服装,所以一共会产生100个对象。如果之后有10000套衣服,那这个程序可能会因为存在如此多的对象已经提前崩溃。

其实我们可以想到,虽然有100套衣服,但很显然并不需要50个男模特和50个女模特,男模特和女模特各自有一个就足够了,他们可以分别穿上不同的衣服来拍照。现在我们根据以上逻辑改写一下代码:

var Model = function (sex) {
  this.sex = sex;
};

Model.prototype.takePhote = function () {
  console.log("sex=" + this.sex + ", underwear=" + this.underwear);
};

// 首先分别创建一个男模特和一个女模特
var maleModel = new Model("male");
var femaleModel = new Model("female");

// 依次让男模特穿上所有的男装拍照
for (let i = 1; i <= 50; i++) {
  maleModel.underwear = "underwear" + i;
  maleModel.takePhote();
}

// 依次让女模特穿上所有的女装拍照
for (let j = 1; j <= 50; j++) {
  femaleModel.underwear = "underwear" + j;
  femaleModel.takePhote();
}

我们可以看到,改进之后的代码,只有两个对象,就可以完成同样的拍照的任务。

2 内部状态与外部状态

上面的这个例子便是享元模式的雏形,享元模式要求将对象的属性划分为内部状态外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量

那么如何区分内部状态和外部状态呢,我们可以根据下面这几条特征来区分:

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

这样一来,我们便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。

剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量。因此,享元模式是一种用时间换空间的优化模式。

在上面的例子中,性别是内部状态,衣服是外部状态,通过区分这两种状态,大大减少了系统中的对象数量。

3 享元模式的通用结构

上面展示的例子还不是一个完整的享元模式,在这个例子中还存在以下两个问题:

  • 我们通过构造函数显式new出了男女两个model对象,在其他系统中,也许并不是一开始就需要所有的共享对象
  • model对象手动设置了underwear外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,它们与共享对象的联系会变得困难。

我们通过一个对象工厂来解决第一个问题,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。对于第二个问题,可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。

4 文件上传

4.1 对象爆炸

什么是对象爆炸呢,比如说在文件上传功能中,可以选择依照队列一个一个地排队上传,也可以同时选择2000个文件。每一个文件都对应着一个JavaScript上传对象的创建,那么同时上传2000个文件,程序中就需要同时new2000个upload对象,这对浏览器会造成很大的冲击。

比如我们要实现使用插件或者Flash上传文件的功能,当用户选择了需要上传的文件之后,它们会去通知调用Window下的一个全局Javascript函数startUpload,用户选择的文件列表被组合成一个数组files塞进该函数的参数列表种,代码如下:

var id = 0;

window.startUpload = function (uploadType, files) {
  for (let i = 0, file; (file = files[i++]); ) {
    var uploadObj = new upload(uploadType, file.fileName, file.fileSize);
    uploadObj.init(id++); // 为upload对象设置一个唯一的id
  }
};

当用户选择完文件之后,startUpload函数会遍历files数组来创建对应的upload对象。接下来定义Upload构造函数,它接受3个参数,分别是插件类型、文件名和文件大小。这些信息都已经被插件组装在files数组里返回,代码如下:

var Upload = function (uploadType, fileName, fileSize) {
  this.uploadType = uploadType;
  this.fileName = fileName;
  this.fileSize = fileSize;
  this.dom = null;
};

Upload.prototype.init = function (id) {
  var that = this;
  this.id = id;
  this.dom = document.createElement("div");
  this.dom.innerHTML =
    "<span>文件名称:" +
    this.fileName +
    ", 文件大小: " +
    this.fileSize +
    "</span>" +
    '<button class="delFile">删除</button>';
  this.dom.querySelector(".delFile").onclick = function () {
    that.delFile();
  };
  document.body.appendChild(this.dom);
};

假设upload对象只有删除文件的功能,对应的方法是Upload.prototype.delFile。该方法中有一个逻辑:当被删除的文件小于3000KB时,该文件将被直接删除,否则页面中会弹出一个提示框,提示用户是否确认要删除该文件,代码如下:

Upload.prototype.delFile = function () {
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if (window.confirm("确定要删除该文件吗? " + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

接下来分别创建3个插件上传对象和3 个Flash上传对象:

startUpload("plugin", [
  { fileName: "1.txt", fileSize: 1000 },
  { fileName: "2.html", fileSize: 3000 },
  { fileName: "3.txt", fileSize: 5000 },
]);

startUpload("flash", [
  { fileName: "4.txt", fileSize: 1000 },
  { fileName: "5.html", fileSize: 3000 },
  { fileName: "6.txt", fileSize: 5000 },
]);

在这里插入图片描述

4.2 享元模式重构

首先确认内部状态和外部状态,在上面的例子中,只有上传类型uploadType是内部状态,在文件上传的例子里,upload对象必须依赖uploadType属性才能工作,这是因为插件上传、Flash上传、表单上传的实际工作原理有很大的区别,它们各自调用的接口也是完全不一样的,必须在对象创建之初就明确它是什么类型的插件,才可以在程序的运行过程中,让它们分别调用各自的方法。

无论我们使用什么方式上传,这个上传对象都是可以被任何文件共用的。而fileNamefileSize是根据场景而变化的,每个文件的fileNamefileSize都不一样, 它们只能被划分为外部状态。

明确了uploadType作为内部状态之后,我们再把其他的外部状态从构造函数中抽离出来,Upload构造函数中只保留uploadType参数:

var Upload = function (uploadType) {
  this.uploadType = uploadType;
};

Upload.prototype.init函数也不再需要,因为upload对象初始化的工作被放在了uploadManager.setExternalState函数里面,接下来只需要定义Upload.prototype.del函数即可:

// 剥离外部状态
Upload.prototype.delFile = function (id) {
  uploadManager.setExternalState(id, this); // 将id对应的对象的外部状态组装到共享对象中
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if (window.confirm("确定要删除该文件吗? " + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

接下来定义一个工厂来创建upload对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

// 使用工厂模式进行对象实例化
var UploadFactory = (function () {
  var createdFlyWeightObjs = {};
  return {
    create: function (uploadType) {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType];
      }
      return (createdFlyWeightObjs[uploadType] = new Upload(uploadType));
    },
  };
})();

现在我们来完善uploadManager对象,它负责向UploadFactory提交创建对象的请求,并用一个 uploadDatabase对象保存所有upload对象的外部状态,以便在程序运行过程中给upload共享对象设置外部状态,代码如下:

// 使用管理器封装外部状态
var uploadManager = (function () {
  var uploadDatabase = {};
  return {
    add: function (id, uploadType, fileName, fileSize) {
      var flyWeightObj = UploadFactory.create(uploadType);
      var dom = document.createElement("div");
      dom.innerHTML =
        "<span>文件名称:" +
        fileName +
        ", 文件大小: " +
        fileSize +
        "</span>" +
        '<button class="delFile">删除</button>';
      dom.querySelector(".delFile").onclick = function () {
        flyWeightObj.delFile(id);
      };
      document.body.appendChild(dom);
      uploadDatabase[id] = {
        fileName: fileName,
        fileSize: fileSize,
        dom: dom,
      };
      return flyWeightObj;
    },
    setExternalState: function (id, flyWeightObj) {
      var uploadData = uploadDatabase[id];
      for (var i in uploadData) {
        flyWeightObj[i] = uploadData[i];
      }
    },
  };
})();

触发上传动作:

var id = 0;
window.startUpload = function (uploadType, files) {
  for (var i = 0, file; (file = files[i++]); ) {
    uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
  }
};

测试一下:

startUpload("plugin", [
  { fileName: "1.txt", fileSize: 1000 },
  { fileName: "2.html", fileSize: 3000 },
  { fileName: "3.txt", fileSize: 5000 },
]);

startUpload("flash", [
  { fileName: "4.txt", fileSize: 1000 },
  { fileName: "5.html", fileSize: 3000 },
  { fileName: "6.txt", fileSize: 5000 },
]);

享元模式重构之前的代码里一共创建了6个upload对象,而通过享元模式重构之后,对象的数量减少为2,更幸运的是, 就算现在同时上传2000个文件,需要创建的upload对象数量依然是2。

5 没有内部状态的享元模式

在文件上传的例子中,我们分别进行过插件调用和Flash调用,导致程序中创建了内部状态不同的两个共享对象。但是在文件上传程序里,一般都会提前通过特性检测来选择一种上传方式,如果浏览器支持插件就用插件上传,如果不支持插件,就用Flash上传。

那么这种情况下,之前作为内部状态存在的uploadType属性是可以删掉的,在继续使用享元模式的前提下,构造函数Upload就变成了无参数的形式:

var Upload = function () {};

其他属性如依然可以作为外部状态保存在共享对象外部,改写创建享元对象的工厂,代码如下:

// 使用工厂模式进行对象实例化
var UploadFactory = (function () {
  var uploadObj;
  return {
    create: function () {
      if (uploadObj) {
        return uploadObj;
      }
      return (uploadObj = new Upload());
    },
  };
})();

管理器部分的代码不需要改动,还是负责剥离和组装外部状态。可以看到,当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂。虽然这时候的共享对象没有内部状态的区分,但还是有剥离外部状态的过程,我们依然倾向于称之为享元模式。

6 对象池

对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接new,而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后, 再进入
池子等待被下次获取。

对象池技术的应用非常广泛,HTTP连接池和数据库连接池都是其代表应用。在Web前端开发中,对象池使用最多的场景大概就是跟DOM有关的操作。很多空间和时间都消耗在了DOM节点上,如何避免频繁地创建和删除DOM节点就成了一个有意义的话题。

比如说,在地图软件中,经常会出现一些标志地名的小气泡,如下所示:
请添加图片描述
当我搜索故宫时,出现了3个小气泡,当我搜索王府井时,出现了5个气泡,按照对象池的思想,在第二次搜索开始之前,并不会把第一次创建的3个小气泡删除掉,而是把它们放进对象池。这样在第二次的搜索结果页面里,我们只需要再创建2个小气泡而不是5个。
请添加图片描述
先定义一个获取小气泡节点的工厂,作为对象池的数组成为私有属性被包含在工厂闭包里,这个工厂有两个暴露对外的方法,create表示获取一个div节点,recover表示回收一个div节点:

var toolTipFactory = (function () {
  var toolTipPool = []; // toolTip 对象池
  return {
    create: function () {
      // 如果对象池为空
      if (toolTipPool.length === 0) {
        var div = document.createElement("div"); // 创建一个 dom
        document.body.appendChild(div);
        return div;
      } else {
        // 如果对象池里不为空
        return toolTipPool.shift(); // 则从对象池中取出一个 dom
      }
    },
    recover: function (tooltipDom) {
      return toolTipPool.push(tooltipDom); // 对象池回收 dom
    },
  };
})();

第一次搜索时,需要创建3个小气泡节点,为了方便回收,用一个数组ary记录它们:

var ary = [];
for (var i = 0, str; (str = ["A", "B", "C"][i++]); ) {
  var toolTip = toolTipFactory.create();
  toolTip.innerHTML = str;
  ary.push(toolTip);
}

在这里插入图片描述
接下来假设地图需要开始重新绘制,在此之前要把这3个节点回收进对象池:

for (var i = 0, toolTip; (toolTip = ary[i++]); ) {
  toolTipFactory.recover(toolTip);
}

再创建5个小气泡:

for (var i = 0, str; (str = ["A", "B", "C", "D", "E"][i++]); ) {
  var toolTip = toolTipFactory.create();
  toolTip.innerHTML = str;
}

在这里插入图片描述
现在再测试一番,页面中出现了5个节点,上一次创建好的节点被共享给了下一次操作。对象池跟享元模式的思想有点相似,虽然innerHTML的值也可以看成节点的外部状态,但在这里我们并没有主动分离内部状态和外部状态的过程。

7 通用对象池实现

我们还可以在对象池工厂里,把创建对象的具体过程封装起来,实现一个通用的对象池:

var objectPoolFactory = function (createObjFn) {
  var objectPool = [];
  return {
    create: function () {
      var obj =
        objectPool.length === 0
          ? createObjFn.apply(this, arguments)
          : objectPool.shift();
      return obj;
    },
    recover: function (obj) {
      objectPool.push(obj);
    },
  };
};

现在利用objectPoolFactory来创建一个装载一些iframe的对象池:

var iframeFactory = objectPoolFactory(function () {
  var iframe = document.createElement("iframe");
  document.body.appendChild(iframe);
  iframe.onload = function () {
    iframe.onload = null; // 防止 iframe 重复加载
    iframeFactory.recover(iframe); // iframe 加载完成之后回收节点
  };
  return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = "http:// baidu.com";

var iframe2 = iframeFactory.create();
iframe2.src = "http:// QQ.com";

setTimeout(function () {
  var iframe3 = iframeFactory.create();
  iframe3.src = "http:// 163.com";
}, 3000);

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

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

相关文章

《许犁庭与柔性世界》第九章 桉嘴牛

“等爸爸冷静下来后&#xff0c;让我趁妹妹不在&#xff0c;抓紧创建几个自己喜欢的人物。我却不愿意。好说歹说&#xff0c;我勉强创造出了第七个人&#xff0c;那个像大姐姐一样照顾伙伴们的易姐姐&#xff0c;易分雪。为了避免妹妹使坏&#xff0c;我将易姐姐设计成跟她姑姑…

眼镜超声波清洗机值不值得买?是智商税?高性价比超声波清洗机

在考虑是否购买眼镜超声波清洗机时&#xff0c;许多人都感到困惑。一方面&#xff0c;超声波清洗机可以高效地清除眼镜上的污垢和细菌&#xff0c;为戴眼镜的人提供更健康的视觉体验。另一方面&#xff0c;有些人认为这可能是一种智商税&#xff0c;因为手动清洗眼镜可能同样有…

石英增强光声光谱气体传感技术中的高精密压力控制解决方案

摘要&#xff1a;光声池内气体压力的可调节控制以及稳定性是保证光声法高精度测量的关键&#xff0c;但在目前的光声和光谱研究中&#xff0c;对气体样品池内压力控制技术的报道极为简单&#xff0c;甚至很多都是错误的&#xff0c;根本无法实现高精度调节和控制&#xff0c;为…

Python爬虫从基础到入门:认识爬虫

Python爬虫从基础到入门:认识爬虫 1. 认识爬虫2. 开始简单的爬虫操作(使用requests)3. 辨别“数据”是静态加载还是动态生成的1. 认识爬虫 爬虫用自己的话说其实就是利用一定的编程语言,到网络上去抓取一些数据为自己所用。那为什么要用爬虫呢?自己直接到网页上去copy数据它…

SMART PLC 和S7-1200PLC MODBUSTCP通信速度测试

SMART PLC MODBUSTCP通信详细介绍请参看下面文章链接: S7-200SMART PLC ModbusTCP通信(多服务器多从站轮询)_matlab sumilink 多个modbustcp读写_RXXW_Dor的博客-CSDN博客文章浏览阅读6.4k次,点赞5次,收藏10次。MBUS_CLIENT作为MODBUS TCP客户端通过S7-200 SMART CPU上的…

频谱测量---测量信号的功率

频谱测量 通道功率、带宽、均值频率、中位数频率、谐波失真。 使用 obw 和 powerbw 查找信号的 90% 占用带宽和 3-dB 带宽。计算功率谱的均值或中位数频率。估计给定频带上的功率。测量谐波失真。估计瞬时带宽、瞬时频率、频谱熵和谱峭度。 函数 功率和带宽 bandpowerBand…

NSSCTF web刷题记录5

文章目录 [HZNUCTF 2023 preliminary]ezlogin[MoeCTF 2021]地狱通讯[NSSRound#7 Team]0o0[ISITDTU 2019]EasyPHP[极客大挑战 2020]greatphp[安洵杯 2020]Validator [HZNUCTF 2023 preliminary]ezlogin 考点&#xff1a;时间盲注 打开题目&#xff0c;在源码出得到hint 注入点很…

数字经济时代,农资企业如何拥抱数字化?

2月13日&#xff0c;《中共中央国务院关于做好2023年全面推进乡村振兴重点工作的意见》发布&#xff0c;这是21世纪以来第20个指导“三农”工作的中央一号文件。该意见中提出了中共中央国务院关于做好2023年全面推进乡村振兴重点工作的意见。 当前&#xff0c;世界百年未有之大…

LiveMedia视频监控汇聚管理平台功能方案之REST HTTP接口服务(六)

LiveMedia视频监控汇聚管理平台全面支持HTTP接口与其他系统对接&#xff0c;接口包含登陆、视频设备/组织结构添加、修改、删除、实时视频、录像回放、定位、设备控制、报警通知及报警联动等&#xff0c;第三方系统可以无缝的把视频中间件当作自身系统中的一个组件来调用和同步…

机器人替身不再只是电影!远程机器人VRoxy通过VR实时模仿你的动作

原创 | 文 BFT机器人 还记得从前看过的电影《铁甲钢拳》中&#xff0c;机器人模仿着狼叔的拳击动作给我们留下了深刻的印象。当人类拳击不再被允许&#xff0c;取而代之的是各种机器人走上了擂台&#xff0c;继续这项火爆又热血的运动&#xff0c;愉悦大众。曾经这只是电影中的…

Spring整合redis的key时出现\xac\xed\x00\x05t\前缀问题

AutowiredRedisTemplate redisTemplate;User usernew User(5,"tomhs","tttt");ValueOperations opsForValue redisTemplate.opsForValue();//存放key,opsForValue.set("user"user.getId(),user);//读取数据;System.out.println(opsForValue.get…

只有开源才能拯救AI

导语 | 随着 AI 技术的蓬勃发展&#xff0c;大模型的开源化正成为人工智能领域的新潮流&#xff0c;但同时引发的伦理和安全风险也饱受大家关注&#xff0c;如何把握平衡其中的尺度成为开源的一大难题。我们又应该如何有效进行开源治理&#xff1f;未来将走向何方&#xff1f;今…

振动异响振动案例 | 宝马 320D M 运动型 xDrive 旅行版(右舵) | 转向柱发出敲击声

顾客描述 客户报告说&#xff0c;来回摇动方向盘时&#xff0c;可以通过转向柱感觉到和听到敲击声。虽然症状已经存在了大约六个月&#xff0c;但它不会影响车辆的性能或操控性。我们的客户还报告了在低速驶过减速带时&#xff0c;悬架会发出间歇性的敲击声。技术描述 …

中小企业数字化转型进程加速,CRM系统前景如何?

自疫情不断反复之后&#xff0c;中小企业数字化转型进程开始加速。作为当下最热门的企业级应用&#xff0c;CRM客户管理系统的前景还是被看好的。相比于美国企业CRM系统7成的使用率&#xff0c;中国的CRM市场还有很大的发展空间。下面来详细说说&#xff0c;CRM系统的前景如何&…

研究生做实验找不到数据集咋办?

做实验找不到数据集咋办?这是很多研究者和开发者都会遇到的问题。数据集是实验的基础,没有合适的数据集,就无法验证模型的性能和效果。那么,有没有什么方法可以快速地找到我们需要的数据集呢?本文将介绍4个常用的数据集搜索平台,希望能够帮助大家解决这个难题。下面以室内…

[EFI]技嘉 Z490 VISION G i5-10500 电脑 Hackintosh 黑苹果引导文件

硬件配置 硬件型号驱动情况主板技嘉 Z490 VISION G CLPC controller Z490芯片组&#xff09;处理器英特尔 Core i5-10500 3.10GHz 六核已驱动内存16GB&#xff08; 威到DDR42655MHz8GBx 2〕已驱动硬盘SSDSC2BB150G7R (150 GB/ 国态硬盘&#xff09;已驱动显卡AMD Radeon RX 58…

【陈老板赠书活动 - 18期】- 计算机考研精炼1000题

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍&am…

【沐风老师】3dMax一键多边形窗插件PolyWindow使用教程

3dMax一键多边形窗插件使用教程 3dMax一键多边形窗插件PolyWindow&#xff0c;将选择的多边形面一键转化为窗模型。你可以通过编辑多边形的线框&#xff08;边&#xff09;来定义窗子的分格形状&#xff0c;这款插件可以大大提高艺术家建筑建模、室内建模制作窗子的速度。 可适…

flask框架报错解决方法

1、报错 jinja2.exceptions.TemplateNotFound 解决方法&#xff1a;报错jinja2.exceptions.TemplateNotFound&#xff0c;template没找到&#xff0c;由于我之前直接将.html文件和.py文件直接放在同一目录下&#xff0c;经了解&#xff0c;需要新增一个 templates目录&#xff…

继承和多态_Java零基础手把手保姆级教程(超详细)

文章目录 Java零基础手把手保姆级教程_继承和多态&#xff08;超详细&#xff09;1. 继承1.1 继承的实现&#xff08;掌握&#xff09;1.2 继承的好处和弊端&#xff08;理解&#xff09; 2. 继承中的成员访问特点2.1 继承中变量的访问特点&#xff08;掌握&#xff09;2.2 sup…