你不知道的 Proxy

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

从观察者模式到响应式的设计原理 这篇文章中,阿宝哥介绍了 observer-util 这个库如何使用 Proxy 来实现响应式。而对于 vue-next 项目中的 @vue/reactivity 模块,也是利用 Proxy 来实现响应式。因此,如果你要学习 @vue/reactivity 模块的话,就需要先掌握 Proxy。

接下来,阿宝哥将从 6 个方面入手,带你一步一步揭开 Proxy 对象的神秘面纱。阅读完本文,你将了解以下内容:

  • 代理的作用;
  • Proxy 对象与 Reflect 对象的相关知识;
  • Proxy 对象的 6 个使用场景;
  • 使用 Proxy 对象时的一些注意事项;
  • Proxy 在开源项目中的应用。

一、聊一聊代理

在日常工作中,相信挺多小伙伴都用过 Web 调试代理工具,比如 Fiddler 或 Charles。通过使用 Web 调试代理工具,我们可以抓取 HTTP/HTTPS 协议请求,还可以手动修改请求参数和响应结果。不仅如此,在调试线上问题时,利用 Web 调试代理工具,你还可以把线上 压缩混淆过 的 JS 文件映射成本地 未压缩混淆过 的 JS 文件。

在简单介绍了 Web 调试代理工具的基本功能之后,我们来看一下使用 Web 调试代理工具的 HTTP 请求流程:

通过上图可知,在引入 Web 调试代理工具之后,我们发起的 HTTP 请求都会通过 Web Proxy 进行转发和处理。增加了 Web Proxy 代理层,让我们能够更好地控制 HTTP 请求的流程。对于单页应用程序来说,当从服务器获取数据之后,我们就会读取相应的数据在页面上显示出来:

以上流程与浏览器直接从服务器获取数据类似:

为了能够灵活控制 HTTP 请求的流程,我们增加了的 Web Proxy 层。那么我们能否控制数据对象的读取流程呢?答案是可以的,我们可以利用 Web API,比如 Object.defineProperty 或 Proxy API。在引入 Web API 之后,数据的访问流程如下图所示:

接下来,阿宝哥将重点介绍 Proxy API,它可是 Vue3 实现数据响应式幕后的 “功臣” 哟。对它感兴趣的小伙伴,跟阿宝哥一起学起来吧。

二、Proxy 对象简介

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。Proxy 的构造函数语法为:

const p = new Proxy(target, handler)

相关的参数说明如下:

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

在介绍 Proxy 对象的使用示例前,我们先来了解一下它的兼容性:

(图片来源:https://caniuse.com/?search=Proxy)

由上图可知,Proxy API 的兼容性并不是很好,所以大家在使用的时候要注意其兼容性问题。

2.1 Proxy 对象使用示例

了解完 Proxy 构造函数,我们来看一个简单的例子:

const man = {
  name: "阿宝哥",
};

const proxy = new Proxy(man, {
  get(target, property, receiver) {
    console.log(`正在访问${property}属性`);
    return target[property];
  },
});

console.log(proxy.name);
console.log(proxy.age);

在以上示例中,我们使用了 Proxy 构造函数为 man 对象,创建了一个代理对象。在创建代理对象时,我们定义了一个 get 捕获器,用于捕获属性读取的操作。 捕获器的作用就是用于拦截用户对目标对象的相关操作,在这些操作传播到目标对象之前,会先调用对应的捕获器函数,从而拦截并修改相应的行为。

在设置了 get 捕获器之后,当成功运行以上的示例代码,控制台会输出以下结果:

正在访问name属性
阿宝哥
正在访问age属性
undefined

通过观察以上输出结果,我们可以发现 get 捕获器 不仅可以拦截已知属性的读取操作,也可以拦截未知属性的读取操作。在创建 Proxy 对象时,除了定义 get 捕获器 之外,我们还可以定义其他的捕获器,比如 has、set、delete、apply 或 ownKeys 等。

2.2 handler 对象支持的捕获器

handler 对象支持 13 种捕获器,这里阿宝哥只列举以下 5 种常用的捕获器:

  • handler.get():属性读取操作的捕获器。
  • handler.set():属性设置操作的捕获器。
  • handler.deleteProperty():delete 操作符的捕获器。
  • handler.ownKeys():Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕获器。
  • handler.has():in 操作符的捕获器。

需要注意的是,所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。 看完上面的捕获器介绍,是不是觉得 Proxy 对象很强大。

三、Reflect 对象简介

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

在介绍 Reflect 对象的使用示例前,我们先来了解一下它的兼容性:

(图片来源:https://caniuse.com/?search=Reflect)

3.1 Reflect 对象使用示例

const man = {
  name: "阿宝哥",
  city: "Xiamen",
};

console.log(Reflect.set(man, "sex", 1)); // true
console.log(Reflect.has(man, "name")); // true
console.log(Reflect.has(man, "age")); // false
console.log(Reflect.ownKeys(man)); // [ 'name', 'city', 'sex' ]

除了示例中介绍的 sethasownKeys 方法之外,Reflect 对象还支持 getdefinePropertydeleteProperty 等方法。下面阿宝哥将简单介绍 Reflect 对象所支持的一些静态方法。

3.2 Reflect 对象支持的静态方法

Reflect 的所有属性和方法都是静态的,该对象提供了与 Proxy handler 对象相关的 13 个方法。同样,这里阿宝哥只列举以下 5 个常用的方法:

  • Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于 target[name]。
  • Reflect.set(target, propertyKey, value[, receiver]):将值赋值给属性的函数。返回一个布尔值,如果更新成功,则返回 true。
  • Reflect.deleteProperty(target, propertyKey):删除 target 对象的指定属性,相当于执行 delete target[name]。
  • Reflect.has(target, propertyKey):判断一个对象是否存在某个属性,和 in 运算符的功能完全相同。
  • Reflect.ownKeys(target):返回一个包含所有自身属性(不包含继承属性)的数组。

在实际的 Proxy 使用场景中,我们往往会结合 Reflect 对象提供的静态方法来实现某些特定的功能。为了让大家能够更好地理解并掌握 Proxy 对象,接下来的环节,阿宝哥将列举 Proxy 对象的 6 个使用场景。

四、Proxy 使用场景

这里我们先来介绍 Proxy 对象的第一个使用场景 —— 增强型数组

4.1 增强型数组

定义 enhancedArray 函数
function enhancedArray(arr) {
  return new Proxy(arr, {
    get(target, property, receiver) {
      const range = getRange(property);
      const indices = range ? range : getIndices(property);
      const values = indices.map(function (index) {
        const key = index < 0 ? String(target.length + index) : index;
        return Reflect.get(target, key, receiver);
      });
      return values.length === 1 ? values[0] : values;
    },
  });

  function getRange(str) {
    var [start, end] = str.split(":").map(Number);
    if (typeof end === "undefined") return false;

    let range = [];
    for (let i = start; i < end; i++) {
      range = range.concat(i);
    }
    return range;
  }

  function getIndices(str) {
    return str.split(",").map(Number);
  }
}
使用 enhancedArray 函数
const arr = enhancedArray([1, 2, 3, 4, 5]);

console.log(arr[-1]); //=> 5
console.log(arr[[2, 4]]); //=> [ 3, 5 ]
console.log(arr[[2, -2, 1]]); //=> [ 3, 4, 2 ]
console.log(arr["2:4"]); //=> [ 3, 4]
console.log(arr["-2:3"]); //=> [ 4, 5, 1, 2, 3 ]

由以上的输出结果可知,增强后的数组对象,就可以支持负数索引、分片索引等功能。除了可以增强数组之外,我们也可以使用 Proxy API 来增强普通对象。

4.2 增强型对象

创建 enhancedObject 函数
const enhancedObject = (target) =>
  new Proxy(target, {
    get(target, property) {
      if (property in target) {
        return target[property];
      } else {
        return searchFor(property, target); //实际使用时要对value值进行复位
      }
    },
  });

let value = null;
function searchFor(property, target) {
  for (const key of Object.keys(target)) {
    if (typeof target[key] === "object") {
      searchFor(property, target[key]);
    } else if (typeof target[property] !== "undefined") {
      value = target[property];
      break;
    }
  }
  return value;
}
使用 enhancedObject 函数
const data = enhancedObject({
  user: {
    name: "阿宝哥",
    settings: {
      theme: "dark",
    },
  },
});

console.log(data.user.settings.theme); // dark
console.log(data.theme); // dark

以上代码运行后,控制台会输出以下代码:

dark
dark

通过观察以上的输出结果可知,使用 enhancedObject 函数处理过的对象,我们就可以方便地访问普通对象内部的深层属性。

4.3 创建只读的对象

创建 Proxy 对象
const man = {
  name: "semlinker",
};

const handler = {
  set: "Read-Only",
  defineProperty: "Read-Only",
  deleteProperty: "Read-Only",
  preventExtensions: "Read-Only",
  setPrototypeOf: "Read-Only",
};

const proxy = new Proxy(man, handler);
使用 proxy 对象
console.log(proxy.name);
proxy.name = "kakuqo";

以上代码运行后,控制台会输出以下代码:

semlinker
proxy.name = "kakuqo";
           ^
TypeError: 'Read-Only' returned for property 'set' of object '#<Object>' is not a function

观察以上的异常信息可知,导致异常的原因是因为 handler 对象的 set 属性值不是一个函数。如果不希望抛出运行时异常,我们可以定义一个 freeze 函数:

function freeze (obj) {
  return new Proxy(obj, {
    set () { return true; },
    deleteProperty () { return false; },
    defineProperty () { return true; },
    setPrototypeOf () { return true; }
  });
}

定义好 freeze 函数,我们使用数组对象来测试一下它的功能:

let frozen = freeze([1, 2, 3]);
frozen[0] = 6;
delete frozen[0];
frozen = Object.defineProperty(frozen, 0, { value: 66 });
console.log(frozen); // [ 1, 2, 3 ]

上述代码成功执行后,控制台会输出 [ 1, 2, 3 ],很明显经过 freeze 函数处理过的数组对象,已经被 “冻结” 了。

4.4 拦截方法调用

定义 traceMethodCalls 函数
function traceMethodCalls(obj) {
  const handler = {
    get(target, propKey, receiver) {
      const origMethod = target[propKey]; // 获取原始方法
      return function (...args) {
        const result = origMethod.apply(this, args);
        console.log(
          propKey + JSON.stringify(args) + " -> " + JSON.stringify(result)
        );
        return result;
      };
    },
  };
  return new Proxy(obj, handler);
}
使用 traceMethodCalls 函数
const obj = {
  multiply(x, y) {
    return x * y;
  },
};

const tracedObj = traceMethodCalls(obj);
tracedObj.multiply(2, 5); // multiply[2,5] -> 10

上述代码成功执行后,控制台会输出 multiply[2,5] -> 10,即我们能够成功跟踪 obj 对象中方法的调用过程。其实,除了能够跟踪方法的调用,我们也可以跟踪对象中属性的访问,具体示例如下:

function tracePropAccess(obj, propKeys) {
  const propKeySet = new Set(propKeys);
  return new Proxy(obj, {
    get(target, propKey, receiver) {
      if (propKeySet.has(propKey)) {
        console.log("GET " + propKey);
      }
      return Reflect.get(target, propKey, receiver);
    },
    set(target, propKey, value, receiver) {
      if (propKeySet.has(propKey)) {
        console.log("SET " + propKey + "=" + value);
      }
      return Reflect.set(target, propKey, value, receiver);
    },
  });
}

const man = {
  name: "semlinker",
};
const tracedMan = tracePropAccess(man, ["name"]);

console.log(tracedMan.name); // GET name; semlinker
console.log(tracedMan.age); // undefined
tracedMan.name = "kakuqo"; // SET name=kakuqo

在以上示例中,我们定义了一个 tracePropAccess 函数,该函数接收两个参数:obj 和 propKeys,它们分别表示需跟踪的目标和需跟踪的属性列表。调用 tracePropAccess 函数后,会返回一个代理对象,当我们访问被跟踪的属性时,控制台就会输出相应的访问日志。

4.5 隐藏属性

创建 hideProperty 函数
const hideProperty = (target, prefix = "_") =>
  new Proxy(target, {
    has: (obj, prop) => !prop.startsWith(prefix) && prop in obj,
    ownKeys: (obj) =>
      Reflect.ownKeys(obj).filter(
        (prop) => typeof prop !== "string" || !prop.startsWith(prefix)
      ),
    get: (obj, prop, rec) => (prop in rec ? obj[prop] : undefined),
  });
使用 hideProperty 函数
const man = hideProperty({
  name: "阿宝哥",
  _pwd: "www.semlinker.com",
});

console.log(man._pwd); // undefined
console.log('_pwd' in man); // false
console.log(Object.keys(man)); // [ 'name' ]

通过观察以上的输出结果,我们可以知道,利用 Proxy API,我们实现了指定前缀属性的隐藏。除了能实现隐藏属性之外,利用 Proxy API,我们还可以实现验证属性值的功能。

4.6 验证属性值

创建 validatedUser 函数
const validatedUser = (target) =>
  new Proxy(target, {
    set(target, property, value) {
      switch (property) {
        case "email":
          const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
          if (!regex.test(value)) {
            console.error("The user must have a valid email");
            return false;
          }
          break;
        case "age":
          if (value < 20 || value > 80) {
            console.error("A user's age must be between 20 and 80");
            return false;
          }
          break;
      }

      return Reflect.set(...arguments);
    },
  });
使用 validatedUser 函数
let user = {
  email: "",
  age: 0,
};

user = validatedUser(user);
user.email = "semlinker.com"; // The user must have a valid email
user.age = 100; // A user's age must be between 20 and 80

上述代码成功执行后,控制台会输出以下结果:

The user must have a valid email
A user's age must be between 20 and 80

介绍完 Proxy 对象的使用场景之后,我们来继续介绍与 Proxy 对象相关的一些问题。

五、Proxy 相关问题

5.1 this 的指向问题

const target = {
  foo() {
    return {
      thisIsTarget: this === target,
      thisIsProxy: this === proxy,
    };
  },
};

const handler = {};
const proxy = new Proxy(target, handler);
console.log(target.foo()); // { thisIsTarget: true, thisIsProxy: false }
console.log(proxy.foo()); // { thisIsTarget: false, thisIsProxy: true }

上述代码成功执行后,控制台会输出以下结果:

{ thisIsTarget: true, thisIsProxy: false }
{ thisIsTarget: false, thisIsProxy: true }

通过以上输出的结果,foo 方法中的 this 指向与当前的调用者有关。看起来挺简单的,但在一些场景下如果稍不注意的话,就会出现问题,比如以下这个示例:

const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }

  get name() {
    return _name.get(this);
  }
}

在以上示例中,我们使用 WeakMap 对象来存储 Person 对象的私有信息。定义完 Person 类,我们就可以通过以下方式来使用它:

const man = new Person("阿宝哥");
console.log(man.name); // 阿宝哥

const proxy = new Proxy(man, {});
console.log(proxy.name); // undefined

对于以上的代码,当我们通过 proxy 对象来访问 name 属性时,你会发现输出的结果是 undefined。这是因为当使用 proxy.name 的方式访问 name 属性时,this 指向的是 proxy 对象,而 _name WeakMap 对象中存储的是 man 对象,所以输出的结果是 undefined

然而,对于以上的问题,如果我们按照以下方式定义 Person 类,就不会出现以上问题:

class Person {
  constructor(name) {
    this._name = name;
  }
  get name() {
    return this._name;
  }
}

const man = new Person("阿宝哥");
console.log(man.name); // 阿宝哥

const proxy = new Proxy(man, {});
console.log(proxy.name); // 阿宝哥

另外,如果你对 WeakMap 感兴趣的话,可以阅读 你不知道的 WeakMap 这篇文章。

5.2 get 捕获器 receiver 参数是什么

const p = new Proxy(target, {
  get: function(target, property, receiver) {
    // receiver
  }
});

get 捕获器用于拦截对象的读取属性操作,该捕获器含有三个参数:

  • target:目标对象。
  • property:被读取的属性名。
  • receiver:指向当前的 Proxy 对象或者继承于当前 Proxy 的对象。

为了更好地了解 receiver 参数的描述信息,我们来举个具体的示例:

const proxy = new Proxy({},
  {
    get: function (target, property, receiver) {
      return receiver;
    },
  }
);

console.dir(proxy.getReceiver === proxy); // true
var inherits = Object.create(proxy);
console.dir(inherits.getReceiver === inherits); // true

那么我们能否改变 receiver 指向的对象呢?答案是可以的,通过 Reflect 对象提供的 get 方法,我们可以动态设置 receiver 对象的值,具体使用方式如下所示:

console.dir(Reflect.get(proxy, "getReceiver", "阿宝哥"));

其实 receiver 的名称是来源于 ECMAScript 规范:

  • [[Get]] (propertyKey, Receiver) → any Return the value of the property whose key is propertyKey from this object. If any ECMAScript code must be executed to retrieve the property value, Receiver is used as the this value when evaluating the code.
  • [[Set]] (propertyKey, value, Receiver) → Boolean Set the value of the property whose key is propertyKey to value. If any ECMAScript code must be executed to set the property value, Receiver is used as the this value when evaluating the code. Returns true if the property value was set or false if it could not be set.

以上的 [[Get]][[Set]] 被称为内部方法,ECMAScript 引擎中的每个对象都与一组内部方法相关联,这些内部方法定义了其运行时行为。

需要注意的是,这些内部方法不是 ECMAScript 语言的一部分。对于对象的访问器属性来说,在执行内部代码时,Receiver 将被作为 this 的值,同样使用 Reflect 对象提供的 API,我们也可以通过设置 receiver 参数的值来改变 this 的值:

const obj = {
  get foo() {
    return this.bar;
  },
};

console.log(Reflect.get(obj, "foo")); // undefined
console.log(Reflect.get(obj, "foo", { bar: 2021 })); // 2021

5.3 包装内置构造函数的实例

当使用 Proxy 包装内置构造函数实例的时候,可能会出现一些问题。比如使用 Proxy 代理 Date 构造函数的实例:

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate(); // Error

当以上代码运行后,控制台会输出以下异常信息:

proxy.getDate();
      ^
TypeError: this is not a Date object.

出现以上问题的原因是因为有些原生对象的内部属性,只有通过正确的 this 才能拿到,所以 Proxy 无法代理这些原生对象的属性。那么如何解决这个问题呢?要解决这个问题,我们可以为 getDate 方法绑定正确的 this

const target = new Date();
const handler = {
  get(target, property, receiver) {
    if (property === "getDate") {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, property, receiver);
  },
};

const proxy = new Proxy(target, handler);
console.log(proxy.getDate());

5.4 创建可撤销的代理对象

通过 Proxy.revocable() 方法可以用来创建一个可撤销的代理对象,该方法的签名为:

Proxy.revocable(target, handler);

相关的参数说明如下:

  • target:将用 Proxy 封装的目标对象。可以是任何类型的对象,包括原生数组,函数,甚至可以是另外一个代理对象。
  • handler:一个对象,其属性是一批可选的函数,这些函数定义了对应的操作被执行时代理的行为。

调用 Proxy.revocable 方法之后,其返回值是一个对象,其结构为:{"proxy": proxy, "revoke": revoke},其中:

  • proxy:表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

了解完 revocable 方法之后,我们来举一个具体的示例:

const target = {}; 
const handler = {};
const { proxy, revoke } = Proxy.revocable(target, handler);

proxy.name = "阿宝哥";
console.log(proxy.name); // 阿宝哥

revoke();
console.log(proxy.name); // TypeError: Revoked

当以上代码成功运行之后,控制台会输出以下内容:

阿宝哥
Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
  at <anonymous>

通过观察以上的结果,我们可知当 proxy 对象被撤销之后,我们就没有办法对已撤销的 proxy 对象执行任何操作。

六、Proxy 在开源项目中的应用

因为 Proxy 对象能够提供强大的拦截能力,所以它被应用在一些成熟的开源项目中,用于实现响应式的功能,比如 vue-next 和 observer-util 项目。对于 observer-util 这个项目,阿宝哥已经写了一篇 从观察者模式到响应式的设计原理 的文章来介绍该项目,感兴趣的小伙伴可以自行阅读。

而对于 vue-next 项目来说,响应式的功能被封装在 @vue/reactivity 模块中,该模块为我们提供了一个 reactive 函数来创建响应式对象。下面我们来简单了解一下 reactive 函数的实现:

// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

reactive 函数内部,会继续调用 createReactiveObject 函数来创建响应式对象,该函数也是被定义在 reactive.ts 文件中,该函数的的具体实现如下:

// packages/reactivity/src/reactive.ts
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 省略部分代码  
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

createReactiveObject 函数内部,我们终于见到了期待已久的 Proxy 对象。当 target 对象不是集合类型的对象,比如 Map、Set、WeakMap 和 WeakSet 时,在创建 Proxy 对象时,使用的是 baseHandlers,该 handler 对象定义了以下 5 种捕获器:

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

其中 getset 捕获器是分别用于收集 effect 函数和触发 effect 函数的执行。好了,这里阿宝哥只是介绍一下 @vue/reactivity 中的 reactive 函数,关于该模块是如何实现响应式的细节,这里就不展开介绍了,阿宝哥后续会单独写一篇文章来详细分析该模块的功能。

七、参考资源

  • MDN - Proxy
  • MDN - Reflect
  • exploringjs - proxies
  • stackoverflow - what-is-a-receiver-in-javascript

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

 相关推荐

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

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

发布于: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次阅读
 目录