【原理】851- 从观察者模式到响应式的设计原理

发表于 3年以前  | 总阅读数:502 次

响应式对使用过 Vue 或 RxJS 的小伙伴来说,应该都不会陌生。响应式也是 Vue 的核心功能特性之一,因此如果要想掌握 Vue,我们就必须深刻理解响应式。接下来阿宝哥将从观察者模式说起,然后结合 observer-util 这个库,带大家一起深入学习响应式的原理。

一、观察者模式

观察者模式,它定义了一种 一对多 的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。在观察者模式中有两个主要角色:Subject(主题)和 Observer(观察者)。

由于观察者模式支持简单的广播通信,当消息更新时,会自动通知所有的观察者。下面我们来看一下如何使用 TypeScript 来实现观察者模式:

1.1 定义 ConcreteObserver

interface Observer {
  notify: Function;
}

class ConcreteObserver implements Observer{
  constructor(private name: string) {}
  notify() {
    console.log(`${this.name} has been notified.`);
  }
}

1.2 定义 Subject 类

class Subject { 
  private observers: Observer[] = [];

  public addObserver(observer: Observer): void {
    this.observers.push(observer);
  }

  public notifyObservers(): void {
    console.log("notify all the observers");
    this.observers.forEach(observer => observer.notify());
  }
}

1.3 使用示例

// ① 创建主题对象
const subject: Subject = new Subject();

// ② 添加观察者
const observerA = new ConcreteObserver("ObserverA");
const observerC = new ConcreteObserver("ObserverC");
subject.addObserver(observerA); 
subject.addObserver(observerC);

// ③ 通知所有观察者
subject.notifyObservers();

对于以上的示例来说,主要包含三个步骤:① 创建主题对象、② 添加观察者、③ 通知观察者。上述代码成功运行后,控制台会输出以下结果:

notify all the observers
ObserverA has been notified.
ObserverC has been notified.

在前端大多数场景中,我们所观察的目标是数据,当数据发生变化的时候,页面能实现自动的更新,对应的效果如下图所示:

要实现自动更新,我们需要满足两个条件:一个是能实现精准地更新,另一个是能检测到数据的异动。要能实现精准地更新就需要收集对该数据异动感兴趣的更新函数(观察者),在完成收集之后,当检测到数据异动,就可以通知对应的更新函数。

上面的描述看起来比较绕,其实要实现自动更新,我们就是要让 ① 创建主题对象、② 添加观察者、③ 通知观察者 这三个步骤实现自动化,这就是实现响应式的核心思路。接下来,我们来举一个具体的示例:

相信熟悉 Vue2 响应式原理的小伙伴,对上图中的代码都不会陌生,其中第二步骤也被称为收集依赖。通过使用 Object.defineProperty API,我们可以拦截对数据的读取和修改操作。

若在函数体中对某个数据进行读取,则表示此函数对该数据的异动感兴趣。当进行数据读取时,就会触发已定义的 getter 函数,这时就可以把数据的观察者存储起来。而当数据发生异动的时候,我们就可以通知观察者列表中的所有观察者,从而执行相应的更新操作。

Vue3 使用了 Proxy API 来实现响应式,Proxy API 相比 Object.defineProperty API 有哪些优点呢?这里阿宝哥不打算展开介绍了,后面打算写一篇专门的文章来介绍 Proxy API。下面阿宝哥将开始介绍本文的主角 —— observer-util:

Transparent reactivity with 100% language coverage. Made with ❤️ and ES6 Proxies.

https://github.com/nx-js/observer-util

该库内部也是利用了 ES6 的 Proxy API 来实现响应式,在介绍它的工作原理前,我们先来看一下如何使用它。

二、observer-util 简介

observer-util 这个库使用起来也很简单,利用该库提供的 observableobserve 函数,我们就可以方便地实现数据的响应式。下面我们先来举个简单的例子:

2.1 已知属性

import { observable, observe } from '@nx-js/observer-util';

const counter = observable({ num: 0 });
const countLogger = observe(() => console.log(counter.num)); // 输出 0

counter.num++; // 输出 1

在以上代码中,我们从 @nx-js/observer-util 模块中分别导入 observableobserve 函数。其中 observable 函数用于创建可观察的对象,而 observe 函数用于注册观察者函数。以上的代码成功执行后,控制台会依次输出 01。除了已知属性外,observer-util 也支持动态属性。

2.2 动态属性

import { observable, observe } from '@nx-js/observer-util';

const profile = observable();
observe(() => console.log(profile.name));

profile.name = 'abao'; // 输出 'abao'

以上的代码成功执行后,控制台会依次输出 undefinedabao。observer-util 除了支持普通对象之外,它还支持数组和 ES6 中的集合,比如 Map、Set 等。这里我们以常用的数组为例,来看一下如何让数组对象变成响应式对象。

2.3 数组

import { observable, observe } from '@nx-js/observer-util';

const users = observable([]);

observe(() => console.log(users.join(', ')));

users.push('abao'); // 输出 'abao'

users.push('kakuqo'); // 输出 'abao, kakuqo'

users.pop(); // 输出 'abao,'

这里阿宝哥只介绍了几个简单的示例,对 observer-util 其他使用示例感兴趣的小伙伴,可以阅读该项目的 README.md 文档。接下来,阿宝哥将以最简单的例子为例,来分析一下 observer-util 这个库响应式的实现原理。

如果你想在本地运行以上示例的话,可以先修改 debug/index.js 目录下的 index.js 文件,然后在根目录下执行 npm run debug 命令。

三、observer-util 原理解析

首先,我们再来回顾一下最早的那个例子:

import { observable, observe } from '@nx-js/observer-util';

const counter = observable({ num: 0 }); // A
const countLogger = observe(() => console.log(counter.num)); // B

counter.num++; // C

在第 A 行中,我们通过 observable 函数创建了可观察的 counter 对象,该对象的内部结构如下:

通过观察上图可知,counter 变量所指向的是一个 Proxy 对象,该对象含有 3 个 Internal slots。那么 observable 函数是如何将我们的 { num: 0 } 对象转换成 Proxy 对象呢?在项目的 src/observable.js 文件中,我们找到了该函数的定义:

// src/observable.js
export function observable (obj = {}) {
  // 如果obj已经是一个observable对象或者不应该被包装,则直接返回它
  if (proxyToRaw.has(obj) || !builtIns.shouldInstrument(obj)) {
    return obj
  }

  // 如果obj已经有一个对应的observable对象,则将其返回。否则创建一个新的observable对象
  return rawToProxy.get(obj) || createObservable(obj)
}

在以上代码中出现了 proxyToRawrawToProxy 两个对象,它们被定义在 src/internals.js 文件中:

// src/internals.js
export const proxyToRaw = new WeakMap()
export const rawToProxy = new WeakMap()

这两个对象分别存储了 proxy => rawraw => proxy 之间的映射关系,其中 raw 表示原始对象,proxy 表示包装后的 Proxy 对象。很明显首次执行时,proxyToRaw.has(obj)rawToProxy.get(obj) 分别会返回 falseundefined,所以会执行 || 运算符右侧的逻辑。

下面我们来分析一下 shouldInstrument 函数,该函数的定义如下:

// src/builtIns/index.js
export function shouldInstrument ({ constructor }) {
  const isBuiltIn =
    typeof constructor === 'function' &&
    constructor.name in globalObj &&
    globalObj[constructor.name] === constructor
  return !isBuiltIn || handlers.has(constructor)
}

shouldInstrument 函数内部,会使用参数 obj 的构造函数判断其是否为内置对象,对于 { num: 0 } 对象来说,它的构造函数是 ƒ Object() { [native code] },因此 isBuiltIn 的值为 true,所以会继续执行 || 运算符右侧的逻辑。其中 handlers 对象是一个 Map 对象:

// src/builtIns/index.js
const handlers = new Map([
  [Map, collectionHandlers],
  [Set, collectionHandlers],
  [WeakMap, collectionHandlers],
  [WeakSet, collectionHandlers],
  [Object, false],
  [Array, false],
  [Int8Array, false],
  [Uint8Array, false],
  // 省略部分代码
  [Float64Array, false]
])

看完 handlers 的结构,很明显 !builtIns.shouldInstrument(obj) 表达式的结果为 false。所以接下来,我们的焦点就是 createObservable 函数:

function createObservable (obj) {
  const handlers = builtIns.getHandlers(obj) || baseHandlers
  const observable = new Proxy(obj, handlers)
  // 保存raw => proxy,proxy => raw 之间的映射关系
  rawToProxy.set(obj, observable)
  proxyToRaw.set(observable, obj)
  storeObservable(obj)
  return observable
}

通过观察以上代码,我们就知道了为什么调用 observable({ num: 0 }) 函数之后,返回的是一个 Proxy 对象。对于 Proxy 的构造函数来说,它支持两个参数:

const p = new Proxy(target, handler)
  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

示例中的 target 指向的就是 { num: 0 } 对象,而 handlers 的值会根据 obj 的类型而返回不同的 handlers

// src/builtIns/index.js
export function getHandlers (obj) {
  return handlers.get(obj.constructor) // [Object, false],
}

baseHandlers 是一个包含了 get、has 和 set 等 “陷阱“ 的对象:

export default { get, has, ownKeys, set, deleteProperty }

在创建完 observable 对象之后,会保存 raw => proxy,proxy => raw 之间的映射关系,然后再调用 storeObservable 函数执行存储操作,storeObservable 函数被定义在 src/store.js 文件中:

// src/store.js
const connectionStore = new WeakMap()

export function storeObservable (obj) {
  // 用于后续保存obj.key -> reaction之间映射关系
  connectionStore.set(obj, new Map())
}

介绍了那么多,阿宝哥用一张图来总结一下前面的内容:

至于 proxyToRawrawToProxy 对象有什么用呢?相信看完以下代码,你就会知道答案。

// src/observable.js
export function observable (obj = {}) {
  // 如果obj已经是一个observable对象或者不应该被包装,则直接返回它
  if (proxyToRaw.has(obj) || !builtIns.shouldInstrument(obj)) {
    return obj
  }

  // 如果obj已经有一个对应的observable对象,则将其返回。否则创建一个新的observable对象
  return rawToProxy.get(obj) || createObservable(obj)
}

下面我们来开始分析第 B 行:

const countLogger = observe(() => console.log(counter.num)); // B

observe 函数被定义在 src/observer.js 文件中,其具体定义如下:

// src/observer.js
export function observe (fn, options = {}) {
  // const IS_REACTION = Symbol('is reaction')
  const reaction = fn[IS_REACTION]
    ? fn
    : function reaction () {
      return runAsReaction(reaction, fn, this, arguments)
    }
  // 省略部分代码
  reaction[IS_REACTION] = true
  // 如果非lazy,则直接运行
  if (!options.lazy) {
    reaction()
  }
  return reaction
}

在上面代码中,会先判断传入的 fn 是不是 reaction 函数,如果是的话,直接使用它。如果不是的话,会把传入的 fn 包装成 reaction 函数,然后再调用该函数。在 reaction 函数内部,会调用另一个函数 —— runAsReaction,顾名思义该函数用于运行 reaction 函数。

runAsReaction 函数被定义在 src/reactionRunner.js 文件中:

// src/reactionRunner.js
const reactionStack = []

export function runAsReaction (reaction, fn, context, args) {
  // 省略部分代码
  if (reactionStack.indexOf(reaction) === -1) {
    // 释放(obj -> key -> reactions) 链接并复位清理器链接
    releaseReaction(reaction)

    try {
      // 压入到reactionStack堆栈中,以便于在get陷阱中能建立(observable.prop -> reaction)之间的联系
      reactionStack.push(reaction)
      return Reflect.apply(fn, context, args)
    } finally {
      // 从reactionStack堆栈中,移除已执行的reaction函数
      reactionStack.pop()
    }
  }
}

runAsReaction 函数体中,会把当前正在执行的 reaction 函数压入 reactionStack 栈中,然后使用 Reflect.apply API 调用传入的 fn 函数。当 fn 函数执行时,就是执行 console.log(counter.num) 语句,在该语句内,会访问 counter 对象的 num 属性。counter 对象是一个 Proxy 对象,当访问该对象的属性时,会触发 baseHandlersget 陷阱:

// src/handlers.js
function get (target, key, receiver) {
  const result = Reflect.get(target, key, receiver)
  // 注册并保存(observable.prop -> runningReaction)
  registerRunningReactionForOperation({ target, key, receiver, type: 'get' })
  const observableResult = rawToProxy.get(result)
  if (hasRunningReaction() && typeof result === 'object' && result !== null) {
    // 省略部分代码
  }
  return observableResult || result
}

在以上的函数中,registerRunningReactionForOperation 函数用于保存 observable.prop -> runningReaction 之间的映射关系。其实就是为对象的指定属性,添加对应的观察者,这是很关键的一步。所以我们来重点分析 registerRunningReactionForOperation 函数:

// src/reactionRunner.js
export function registerRunningReactionForOperation (operation) {
  // 从栈顶获取当前正在执行的reaction
  const runningReaction = reactionStack[reactionStack.length - 1]
  if (runningReaction) {
    debugOperation(runningReaction, operation)
    registerReactionForOperation(runningReaction, operation)
  }
}

registerRunningReactionForOperation 函数中,首先会从 reactionStack 堆栈中获取正在运行的 reaction 函数,然后再次调用 registerReactionForOperation 函数为当前的操作注册 reaction 函数,具体的处理逻辑如下所示:

// src/store.js
export function registerReactionForOperation (reaction, { target, key, type }) {
  // 省略部分代码
  const reactionsForObj = connectionStore.get(target) // A
  let reactionsForKey = reactionsForObj.get(key) // B
  if (!reactionsForKey) { // C
    reactionsForKey = new Set()
    reactionsForObj.set(key, reactionsForKey)
  }
  if (!reactionsForKey.has(reaction)) { // D
    reactionsForKey.add(reaction)
    reaction.cleaners.push(reactionsForKey)
  }
}

在调用 observable(obj) 函数创建可观察对象时,会为以 obj 对象为 key,保存在 connectionStoreconnectionStore.set(obj, new Map()) )对象中。

阿宝哥把 registerReactionForOperation 函数内部的处理逻辑分为 4 个部分:

  • (A):从 connectionStore (WeakMap)对象中获取 target 对应的值,会返回一个 reactionsForObj(Map)对象;
  • (B):从 reactionsForKey (Map)对象中获取 key(对象属性)对应的值,如果不存在的话,会返回 undefined;
  • (C):如果 reactionsForKey 为 undefined,则会创建一个 Set 对象,并把该对象作为 value,保存在 reactionsForObj(Map)对象中;
  • (D):判断 reactionsForKey(Set)集合中是否含有当前的 reaction 函数,如果不存在的话,把当前的 reaction 函数添加到 reactionsForKey(Set)集合中。

为了让大家能够更好地理解该部分的内容,阿宝哥继续通过画图来总结上述的内容:

因为对象中的每个属性都可以关联多个 reaction 函数,为了避免出现重复,我们使用 Set 对象来存储每个属性所关联的 reaction 函数。而一个对象又可以包含多个属性,所以 observer-util 内部使用了 Map 对象来存储每个属性与 reaction 函数之间的关联关系。

此外,为了支持能把多个对象变成 observable 对象并在原始对象被销毁时能及时地回收内存, observer-util 定义了 WeakMap 类型的 connectionStore 对象来存储对象的链接关系。对于当前的示例,connectionStore 对象的内部结构如下所示:

最后,我们来分析 counter.num++; 这行代码。简单起见,阿宝哥只分析核心的处理逻辑,对完整代码感兴趣的小伙伴,可以阅读该项目的源码。当执行 counter.num++; 这行代码时,会触发已设置的 set 陷阱:

// src/handlers.js
function set (target, key, value, receiver) {
  // 省略部分代码
  const hadKey = hasOwnProperty.call(target, key)
  const oldValue = target[key]
  const result = Reflect.set(target, key, value, receiver)
  if (!hadKey) {
    queueReactionsForOperation({ target, key, value, receiver, type: 'add' })
  } else if (value !== oldValue) {
    queueReactionsForOperation({
      target,
      key,
      value,
      oldValue,
      receiver,
      type: 'set'
    })
  }
  return result
}

对于我们的示例,将会调用 queueReactionsForOperation 函数:

// src/reactionRunner.js
export function queueReactionsForOperation (operation) {
  // iterate and queue every reaction, which is triggered by obj.key mutation
  getReactionsForOperation(operation).forEach(queueReaction, operation)
}

queueReactionsForOperation 函数内部会继续调用 getReactionsForOperation 函数获取当前 key 对应的 reactions:

// src/store.js
export function getReactionsForOperation ({ target, key, type }) {
  const reactionsForTarget = connectionStore.get(target)
  const reactionsForKey = new Set()

  if (type === 'clear') {
    reactionsForTarget.forEach((_, key) => {
      addReactionsForKey(reactionsForKey, reactionsForTarget, key)
    })
  } else {
    addReactionsForKey(reactionsForKey, reactionsForTarget, key)
  }
 // 省略部分代码
  return reactionsForKey
}

在成功获取当前 key 对应的 reactions 对象之后,会遍历该对象执行每个 reaction,具体的处理逻辑被定义在 queueReaction 函数中:

// src/reactionRunner.js
function queueReaction (reaction) {
  debugOperation(reaction, this)
  // queue the reaction for later execution or run it immediately
  if (typeof reaction.scheduler === 'function') {
    reaction.scheduler(reaction)
  } else if (typeof reaction.scheduler === 'object') {
    reaction.scheduler.add(reaction)
  } else {
    reaction()
  }
}

因为我们的示例并没有配置 scheduler 参数,所以就会直接执行 else 分支的代码,即执行 reaction() 该语句。

好的,observer-util 这个库内部如何把普通对象转换为可观察对象的核心逻辑已经分析完了。对于普通对象来说,observer-util 内部通过 Proxy API 提供 get 和 set 陷阱,实现自动添加观察者(添加 reaction 函数)和通知观察者(执行 reaction 函数)的处理逻辑。

如果你看完本文所介绍的内容,应该就可以理解 Vue3 中 reactivity 模块内 targetMap 的相关定义:

// vue-next/packages/reactivity/src/effect.ts
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

除了普通对象和数组之外,observer-util 还支持 ES6 中的集合,比如 Map、Set 和 WeakMap 等。当处理这些对象时,在创建 Proxy 对象时,会使用 collectionHandlers 对象,而不是 baseHandlers 对象。这部分内容,阿宝哥就不再展开介绍,感兴趣的小伙伴可以自行阅读相关代码。如果想了解 WeakMap 的相关知识,可以阅读 你不知道的 WeakMap 这篇文章。

四、参考资源

  • what-is-an-internal-slot-of-an-object-in-javascript
  • MDN-Proxy
  • MDN-Reflect

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/qE-W3I_e2SYy9Y6uaq4w9w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录