响应式与观察者模式

news2024/11/16 2:35:53

什么是响应式?

响应式 是Vue 最独特的特性之一,是非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。我们也叫他双向绑定。

如果想要更改视图,只要直接更改对应属性的值即可,不需要调用 Vue 提供的 API 接口,而像 React 和小程序则需要调用相应的接口才能使视图进行更新。举个例子:

// Vue
this.a++;
// 小程序
this.setData({
  a: this.data.a++
})

响应式系统其实套用了观察者模式。

什么是观察者模式?

观察者模式(Observer):通常又被称作为发布-订阅者模式。它定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖于它的对象都会得到通知并自动更新,解决了主体对象与观察者之间功能的耦合。但是观察者模式不等于发布-订阅者模式。详见附录观察者模式与发布订阅者模式。

观察者模式的适用性

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。讲这两者封装在独立的对象中可以让它们可以各自独立的改变和复用。(高内聚)

  • 当一个对象必须通知其它对象,但是却不知道具体对象到底是谁。换句话说,你不希望这些对象是紧密耦合的。(低耦合)

  • 当一个对象的改变的时候,需要同时改变其它对象,被改变的对象不知道具体多少对象有待改变

数据初始化(VUE响应式系统初始化)的观察者模式流程示意图

流程解析:

1、 获取用户的 data 值

在 Vue 中,用户会传进来 options 参数,首先我们要获取用户的 data 对象的值,因为可能用户传的是对象,也可能是函数,所以要进行判断,然后将 data 挂载到 vm 实例上,这是为了后续方便获取 data 上的值。

vm.$data = data = (type === 'function' ? data.call(vm) : data);

看不懂call的用法?可以看之前这篇=>

2、代理 $data 数据

我们拿data 里的数据都是直接通过 this.xxx 来访问某个属性,并且能拿到相关数据,其实是 Vue 做了一层代理,实际上还是访问的 this.$data.xxx 来获取属性的值的。

这其实是通过 Object.defineProperty 来做代理,代理的对象是 this,如果用户想访问 this.xxx ,那么就通过 get 方法 return this.$data.xxx 来解决代理。

通过 Object.defineProperty 来做代理,代理的对象是 this,如果用户想访问 this.xxx ,那么就通过 get 方法 return this.$data.xxx 来解决代理。

// 实现的思路就是获取到 vm.$data 后遍历每个 key 后进行代理处理,代码如下:
function proxyData(vm) {
  // 代理$data,能通过this.xxx直接访问属性
  const $data = vm.$data;
  for (let key in $data) {
    Object.defineProperty(vm, key, {
      get() {
        return $data[key];
      },
      set(newVal) {
        $data[key] = newVal;
      }
    })
  }
}

这里不需要递归代理每一个数据,因为我们只要代理第一层数据,让代码能访问到第一层的数据即可,比如访问 this.a.b ,因为 this 对象下并没有 a 属性,所以要代理,代理后能访问到 this.a ,因为对象 a 中本来就有 b 属性,所以不进行代理还是能获取到的。

3、对data里面的数据进行数据劫持

源码:

walk (obj: Object) {
  const keys = Object.keys(obj)
  // 遍历将其变成 vue 的访问器属性
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}

从上面对象的遍历我们看到了 defineReactive ,那么劫持最关键的点也在于这个函数,该函数里面封装了 getter 和 setter 函数,使用观察者模式,互相监听。

// 设置为访问器属性,并在其 getter 和 setter 函数中,使用发布/订阅模式,互相监听。
export function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
  // 这里用到了观察者(发布/订阅)模式进行了劫持封装,它定义了一种一对多的关系,让多个订阅者监听一个发布对象,这个发布对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
 
 // 实例化一个发布对象,对象中有空的订阅者列表
  const dep = new Dep()
  
  // 获取属性描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {    
    return
  }

  const getter = property && property.get
  const setter = property && property.set
  
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 收集依赖,建立一对多的的关系,让多个观察者监听当前主题对象
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // 这里是对数组进行劫持
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // 劫持到数据变更,并发布消息进行通知
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {    
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

什么是observe实例?

大概工作内容如下:

function observe(value) {
  // 不对基础类型进行观察
  if (typeof value !== 'object' || value === null) return;
  new Observe(value);
}

// 观察者类,用于观测数据使其成为响应式数据
function Observe(value) {
  if (Array.isArray(value)) {
    // 如果是数组
    ......
  } else {
    // 观测对象
    this.walk(value);
  }
}

observe 函数来观测待劫持的对象,最开始的也就是用户传的 data 对象,注意一下 observe 函数只观测对象类型的数据,也就是 Object 或者 Array,如果不是这两个类型的直接返回,那么可能就有疑问,那普通数据怎么劫持呢,更改普通数据不也能实现响应式嘛?这是因为普通数据在对象里被观察了,因为用户传的 data 也是一个对象,所以如果基本数据类型的话,肯定是在 data 这个最大的对象下存在的,所以肯定被观测过了。

其次 Observe 类主要就是对对象和数组进行观测,并实施不同的策略,如果是 Object 的话,那就调用 walk 方法,遍历当前对象的每一个 key 值,然后利用 defineReactive 函数对其进行劫持。数组会在后续讨论

而 defineReactive 函数会一开始就调用 observe 函数,因为如果当传进来的 value 值还是 Object 就继续递归,直到为基本数据类型,就会被直接 return 回来,然后执行下面的 Object.defineProperty 方法,因为这样过后 Object 里的所有基本数据类型的值都被劫持了,深层的对象中的数据也被劫持了,目前数组里的基本类型数据先不讨论。

而我们可以看到defineReactive里也有observe函数,因为当data里的键值是对象或数组时,我们需要递归层层进行数据劫持

封装中转站Dep与Watcher

我们在劫持到数据变更的时候,并进行数据变更通知的时候,如果不做一个"中转站"的话,我们根本不知道到底谁订阅了消息,具体有多少对象订阅了消息。其中Dep是发布者,Watcher是订阅者。

Dep-发布者

Dep,全名 Dependency, Dep 类是用来做依赖收集的。收集订阅者Watcher清单。

let uid = 0

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    // 用来给每个订阅者 Watcher 做唯一标识符,防止重复收集
    this.id = uid++
    // 定义subs数组,用来做依赖收集(收集所有的订阅者 Watcher)
    this.subs = []
  }

  // 收集订阅者
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Dep.target = null

作用:

定义subs数组,用来收集订阅者Watcher

当劫持到数据变更的时候,通知订阅者Watcher进行update操作

绑定发布者的目标对象。(target -> Dep(中转站) ->Watcher)

Wacther -订阅者

它负责做的事情就是订阅 Dep ,当Dep 发出消息传递(notify)的时候,所以订阅着 Dep 的 Watchers 会进行自己的 update 操作。

精简源码:

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    this.cb = cb
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 解析表达式
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
      }
    }
    this.value = this.get()
  }

  get () {
    // 将目标收集到目标栈
    pushTarget(this)
    const vm = this.vm
    
    let value = this.getter.call(vm, vm)
    // 删除目标
    popTarget()
    
    return value
  }

  // 订阅 Dep,同时让 Dep 知道自己订阅着它
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 收集订阅者
        dep.addSub(this)
      }
    }
  }

  // 订阅者'消费'动作,当接收到变更时则会执行
  update () {
    this.run()
  }

  run () {
    const value = this.get()
    const oldValue = this.value
    this.value = value
    this.cb.call(this.vm, value, oldValue)
  }
}

Dep 负责收集所有的订阅者 Watcher ,具体谁不用管,具体有多少也不用管,只需要通过 target 指向的计算去收集订阅其消息的 Watcher 即可,然后只需要做好消息发布 notify 即可。

Watcher 负责订阅 Dep ,并在订阅的时候让 Dep 进行收集,接收到 Dep 发布的消息时,做好其 update 操作即可。

两者看似相互依赖,实则却保证了其独立性,保证了模块的单一性。

JS手写观察者模式

例子:

页面有两个输入框,当我们改变鞋子/猪肉的价格时,猪肉/鞋子的价格也将随着改变。

这里我们延伸出两对观察订阅关系。

鞋子(dep发布者)-猪肉(watcher观察者或订阅者)

猪肉(dep发布者)-鞋子(watcher观察者或订阅者)

发布者代码:
class Dep {         //发布者(商店)
   constructor(goodValue, goodName) {
     this.goodValue = goodValue;
     this.goodName = goodName;
     // 观察者数组
     this.observers = [];
   }
   setValue(state) {
     // 状态改变,通知订阅者
     this.goodValue = state;
     this.noticy();    
   }
   addObserver(ob) {      
     //添加观察
     this.observers.push({
       name: ob.name,
       constructor: ob
     });
   }
   noticy() {            
     //状态改变,发布
     this.observers.forEach(ob => {
       ob.constructor.update(`${ob.name}你好,${this.shopName}价格更新了,价格为${this.shopMoney}       `)
     })
   }
 }

这里是发布者构造函数,包含商品初价,商品名称,订阅者列表,之后的订阅者创造实例时会用到addObserver方法,noticy方法用于通知每一个订阅者(observers)

观察者代码:
class Observer {        //订阅者(顾客)
  constructor(name, dep) {
    this.name = name;
    this.dep = dep;
    // 一个订阅者可以订阅多个发布者
    this.dep.forEach(d => {
      d.addObserver(this);
    })
  }
  update(val) {       //改变通知
    console.log(val)
  }
}

这里订阅者可以同时订阅多个dep,并且在实例化时调用dep实例的addObserver方法。

这样就形成dep和observer的关联性。

完整源码:
<html lang="en">
<body>
  <span>鞋子价格</span><input type="text" oninput="changeMoney(event, 1)" value="500">
  <span>猪肉价格</span><input type="text" oninput="changeMoney(event, 2)" value="30">
</body>
<script>
  class Dep {         //发布者(商店)
    constructor(shopMoney, shopName) {
      this.shopMoney = shopMoney;
      this.shopName = shopName;
      this.observers = [];
    }
    setState(state) {       
      //状态改变,通知订阅
      this.shopMoney = state;
      this.noticy();    
    }
    addObserver(ob) {       
      //添加订阅者
      this.observers.push({
        name: ob.name,
        constructor: ob
      });
    }
    noticy() {            
      //状态改变,发布
      this.observers.forEach(ob => {
        ob.constructor.update(`${ob.name}你好,${this.shopName}价格更新了,价格为${this.shopMoney}`)
      })
    }
  }
class Observer {        //订阅者(顾客)
  constructor(name, dep) {
    this.name = name;
    this.dep = dep;
    this.dep.forEach(d => {
      d.addObserver(this);
    })
  }
  update(val) {       //改变通知
    console.log(val)
  }
}

const dep1 = new Dep(500, '鞋子');
const dep2 = new Dep(30, '猪肉');
const ob1 = new Observer('顾客1', [dep1, dep2]);
const ob2 = new Observer('顾客2', [dep1]);

function changeMoney(e, id) {
  // 通知发布者
  id === 1 
  ?
  dep1.setState(e.target.value)
  :
  dep2.setState(e.target.value)
}
</script>
</html>

其他应用:

vue的$on 以及 $emit 的设计也使用了观察者模式。

$emit 负责发布消息,$on 是订阅者 。

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      this.$on(event[i], fn)
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn)
  }
  return vm
}

Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  let cbs = vm._events[event]
  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs
    const args = toArray(arguments, 1)
    for (let i = 0, l = cbs.length; i < l; i++) {
      cbs[i].apply(vm, args)
    }
  }
  return vm
}

总结:

  • 目标和观察者间的抽象耦合:一个目标只知道他有一系列的观察者(目标进行依赖收集),却不知道其中任意一个观察者属于哪一个具体的类,这样目标与观察者之间的耦合是抽象的和最小的。

  • 支持广播通信:观察者里面的通信,不像其它通常的一些请求需要指定它的接受者。通知将会自动广播给所有已订阅该目标对象的相关对象,即上文中的 dep.notify() 。当然,目标对象并不关心到底有多少对象对自己感兴趣,它唯一的职责就是通知它的各位观察者,处理还是忽略一个通知取决于观察者本身。

  • 一些意外的更新:因为一个观察者它自己并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。如果观察者直接在目标上做操作的话,可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新,所以一般我们会把一些操作放在目标内部,防止出现上述的问题。

附录:观察者模式与发布订阅模式有什么区别

发布订阅模式:

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。

发布者只需告诉Broker,我要发的消息,topic是AAA;

订阅者只需告诉Broker,我要订阅topic是AAA的消息;

于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

从表面上看:

观察者模式里,只有两个角色 —— 观察者 + 被观察者

而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker

往更深层次讲:

观察者和被观察者,是松耦合的关系

发布者和订阅者,则完全不存在耦合

从使用层面上讲:

观察者模式,多用于单个应用内部

发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

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

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

相关文章

从隔壁老王开始的信号处理入门

诸神缄默不语-个人CSDN博文目录 我是从GNN被扔到NLP然后又做起了GNN现在又被喊去搞时间序列分类&#xff0c;所以现在才开始看信号处理&#xff08;因为我开始做GNN以来&#xff0c;GNN就以图域而非谱域为主了&#xff0c;所以那时我没怎么看过信号处理&#xff09;。 所以写个…

RabbitMQ消息队列(三):任务分发机制

在上篇文章中&#xff0c;我们解决了从发送端&#xff08;Producer&#xff09;向接收端&#xff08;Consumer&#xff09;发送“Hello World”的问题。在实际的应用场景中&#xff0c;这是远远不够的。从本篇文章开始&#xff0c;我们将结合更加实际的应用场景来讲解更多的高级…

jetson nano上编译与使用西门子PLC通讯库snap7

文章目录一.西门子snap7介绍二.西门子S7通讯介绍三.jetson nano编译snap7库四.Qt Cmake导入snap7库五.snap7主要函数说明1.与PLC建立连接2.读写PA区变量3.读写MK区变量六.通讯程序示例一.西门子snap7介绍 Snap7 是一个基于以太网与S7系列的西门子PLC通讯的开源库。支持包括S7系…

2023美赛数学建模ABCDEF题思路模型代码

占个位置吧&#xff0c;开始在本帖实时更新赛题思路代码&#xff0c;文章末尾获取&#xff01; 持续为更新参考思路 赛题思路 会持续进行思路模型分析&#xff0c;下自行获取。 A题思路&#xff1a; &#xff08;比赛开始后第一时间更新&#xff09; B题思路&#xff1a;…

《算法分析与设计》复习笔记

目录 一、算法的基本概念 1.1 算法的定义 1.2 算法的“好坏”如何衡量&#xff1f; 1.3 描述算法的时间复杂度 ⭐ 1.4 如何评价算法 二、 分治法 2.1 分治法的求解步骤 2.2 平衡的概念 2.3 递归式解法 2.3.1 主定理法 ⭐ 2.4 分治法的使用条件 2.5 分治法实例 2.5…

助力安全作业生产,基于轻量级YOLOv6s开发实践反光衣检测识别分析系统

在很多实际作业生产场景中&#xff0c;出于对安全的考虑&#xff0c;施工作业等操作都是要求穿戴反光衣的&#xff0c;这个主要是为了保护人身安全&#xff0c;但是很多时候工程作业场景下因为实际种种的原因工人实际作业操作的时候很多人并没有按照要求穿戴反光衣这就给安全生…

OPTEE安全存储

本文主要介绍OPTEE的安全存储技术&#xff0c;翻译自官方文档&#xff1a;Secure storage — OP-TEE documentation documentation (optee.readthedocs.io) 一、背景 OP-TEE中的安全存储是根据GlobalPlatform的TEE Internal Core API&#xff08;这里称为可信存储&#xff09;…

2023/1/13总结

今天学习了链式向前星和唯一分解定理&#xff08;数论&#xff09;。 链式向前星 链式向前星是一种存储图的方法&#xff0c;在此之前我们学到过存储图的方式&#xff1a;邻接表以及邻接矩阵&#xff0c;邻接矩阵浪费了很大的空间&#xff0c;而邻接表 写起来的代码有一点点…

微信小程序wxml的数据和事件的绑定,以及条件和列表的渲染

文章目录1.数据绑定的基本原则在data中定义页面的数据2.事件绑定bingtap的语法格式:在事件处理函数中为data中的数据赋值事件传参bindinput的语法格式实现文本框和data之间的数据同步1.定义数据2.渲染结构3.美化样式4.绑定input事件处理函数3.条件渲染hiddenwx:if与hidden的对比…

数据库 表设计 MySQL

表设计 约束 为了保证入库数据的合理性&#xff0c;添加的各种规则。 约束的分类 准备测试用的表格&#xff1a; CREATE TABLE emp ( id INT, -- 员工id&#xff0c;主键且自增长 ename VARCHAR(50), -- 员工姓名&#xff0c;非空且唯一 joindate DATE, -- 入职日期&…

【uniapp】渲染列表数据删除项导致每项数据重置的问题解决方案

开发uniapp项目&#xff0c;使用的是JavaScript Vue写法&#xff0c;操作wList数组列表更新的时候&#xff0c;如果每一项都带input 或 radio组件&#xff0c;要操作移除的话&#xff0c;那么组件的输入数据会被清除重置&#xff0c;若不希望这样&#xff0c;那应该怎么做才好呢…

设计模式相关内容介绍—软件设计原则(六个)

在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程员要尽量根据6条原则来开发程序&#xff0c;从而提高软件开发效率、节约软件开发成本和维护成本。 目录 1.开闭原则 2.里氏代替原则 3.依赖倒转原则 4.接…

dvwa中的文件包含攻击

环境&#xff1a;dvwa: 192.168.11.135 dvwa版本&#xff1a; Version 1.9 (Release date: 2015-09-19)kail机器&#xff1a;192.168.11.156一、什么是文件包含漏洞?为简化代码&#xff0c;会把重复的code内容单独写到一个页面文件&#xff0c;然后再需要调用重复内容的页面中…

C语言:初识C语言

目录前言1. 什么是c语言呢2. 第一个c语言程序2. 数据类型3. 变量和常量3.1 变量3.1.1 变量的定义3.1.2 变量的分类3.1.3 变量的使用3.1.4 变量的作用域和生命周期3.2 常量4. 字符串、转义字符、注释4.1 字符串4.2 转义字符4.3 注释5. 选择语句6. 循环语句7. 函数8. 数组9. 操作…

学习笔记——keep-alive缓存组件,再次返回组件data数据重置

前言&#xff1a;使用keep-alive缓存组件&#xff0c;当再次返回该组件后&#xff0c;希望其组件中的数据或状态&#xff0c;保持上次离开该组件时的情况。 一、当前组件树 希望缓存HomeMain组件的状态。 二、错误处理 我在HomeMain的祖先组件HomeLayout中&#xff0c;写了如下…

sqlplus 连接数据库

终端直连 Oracle 数据库 ORA-12162 错误 出于各种网络原因&#xff0c;无法直连数据库&#xff0c;但又必须查询数据库数据 我们只能选择直连数据库的服务器 然后通过 sqlplus 连接 Oracle 从配置文件里获取这样一段信息 urljdbc:oracle:thin:192.168.1.3:1521:testdb use…

【SpringCloud08】SpringCloud Consul服务注册与发现

1.Consul简介 1.1是什么 官网 Consul 是一套开源的分布式服务发现和配置管理系统&#xff0c;由 HashiCorp 公司用Go 语言开发 提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用&#xff0c;也可以一起使用以构建全方位…

基于MPLS-V**多分部互访的ensp企业网络规划与设计_ensp综合实验

作者&#xff1a;BSXY_19计科_陈永跃BSXY_信息学院注&#xff1a;未经允许禁止转发任何内容基于MPLS-V**多分部互访的ensp企业网络规划与设计_ensp综合实验前言及技术/资源下载说明&#xff08; **未经允许禁止转发任何内容** &#xff09;插曲&#xff1a;基于eNSP中大型校园/…

卡特加特数字中控主机,数字家庭控制中心!没它智能家居就是智障!

数字中控主机是数字家庭的核心&#xff0c;承担着“协调各方、总揽全局”的作用&#xff0c;是打造未来数字家庭空间必不可少的设备。区别于传统家居智能&#xff0c;它真正意义上告别了过去以设备为中心的架构&#xff0c;而是以人的个性化需求为中心&#xff0c;以数据作为资…

2020统考真题-距离最小三元组

2020年统考真题 定义三元组$ (a,b,c)$ &#xff08; a,b,c 均为正数&#xff09;的距离 D∣a−b∣∣b−c∣∣c−a∣D|a−b||b−c||c−a|D∣a−b∣∣b−c∣∣c−a∣ 。给定 3个非空整数集合 S1 、 S2 和 S3 &#xff0c;按升序分别存储在 3 个数组中。请设计一个尽可能高效的算…