ECMAScript 规范文本阅读导引 Part 2

发表于 4年以前  | 总阅读数:433 次

在前文 ECMAScript 规范文本阅读导引 - Part 1 中,我们了解了 ECMAScript 规范文本该如何入手查阅,这次我们将通过一个问题来深入 ECMAScript 规范文本,看看日常工作中我们所使用的 JavaScript 是如何按照规范文本的定义执行的。

基本类型的属性访问


JavaScript 对象上的成员属性广为人知是通过遍历原型链查找的,比如 ({}).hasOwnProperty 中虽然对象字面量上没有定义 hasOwnProperty 成员,但是因为对象字面量的原型默认就是 Object.prototype,所以 Object.prototype 上的 hasOwnProperty 成员也就可以从这个对象字面量上访问到了。那我们经常会在一些基本类型值上也会对其的属性进行访问,那这些属性又是在哪儿定义的呢?难道基本类型也有原型定义吗?

'foobar'.substring(3);
// -> 'bar'

注意:下文中会有许多对 ECMAScript 规范文本的直接引用,截止我们撰文的时间 2020 年 3 月 10 日,在此之后最新版本的 ECMAScript 规范文本可能会有更新,在阅读的时候可以参考最新规范文本阅读。

哪儿定义了成员属性访问的语法?


从成员表达式的文法生成式可以看到,成员表达式有 7 个可能的生成式。成员表达式可以是一个单独的 PrimaryExpression,也可以是一个成员表达式加上一个由方括号包裹的 Expression:MemberExpression [ Expression ],比如 obj['foo']

MemberExpression:
    PrimaryExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    MemberExpression TemplateLiteral
    SuperProperty
    MetaProperty
    new MemberExpression Arguments

'foobar'.substring 即是 MemberExpression . IdentifierName 所表达的文法。回到我们的问题,“基本类型的属性是如何访问的?”,属性访问是发生在运行时的,那么我们可以先来看看这段成员表达式的运行时语义。

了解更多上下文无关文法:https://en.wikipedia.org/wiki/Context-free_grammar

成员表达式的运行时语义

语法的运行时语义定义了这个通过这个语法的定义解析完成后,在运行时是如何表达他的含义的,比如成员表达式的运行时语义中定义了上文中 MemberExpression . IdentifierName 生成式如 foo.bar 这样的表达式在运行时是如何在 foo 上取出他的成员属性 bar 的值。

大多数 ECMAScript 规范中的运行时语义是由一系列算法步骤组成的,不过不像常规的伪代码,会使用更加精确的方式描述操作步骤。

MemberExpression : MemberExpression . IdentifierName
1. Let baseReference be the result of evaluating MemberExpression.
2. Let baseValue be ? GetValue ( baseReference ).
3. If the code matched by this MemberExpression is strict mode code , let strict be true; else let strict be false.
4. Return ? EvaluatePropertyAccessWithIdentifierKey ( baseValue , IdentifierName , strict ).

可以看到操作的第 4 步将更多具体的操作代理给了另外一个抽象操作 EvaluatePropertyAccessWithIdentifierKey

EvaluatePropertyAccessWithIdentifierKey ( baseValue, identifierName, strict )
1. Assert: identifierName is an IdentifierName.
2. Let bv be ? RequireObjectCoercible ( baseValue ).
3. Let propertyNameString be StringValue of identifierName.
4. Return a value of type Reference whose base value component is bv , whose referenced name component is propertyNameString, and whose strict reference flag is strict.

这段算法返回了一个引用类型,并且没有对对象执行任何具体的操作,那么这个属性引用类型是如何转换成具体的值的呢?我们回到我们的例子,可以发现,除了 'foobar'.startsWith 这段属性访问之外,代码中还有一次函数调用:

'foobar'.startsWith('foo');

引用类型常常被用在像 delete,typeof,赋值操作,super 关键字等等特性中。比如赋值操作的左操作 obj.foo = 'bar'obj.foo 就是一个引用类型,只有在最终的赋值操作中,引用才会被真正地具像化。

引用类型包含解析后的名字或者属性绑定。单个引用包含三个部分,引用基底,引用名,与是否是严格引用 flag。引用基底通常会是 undefined,一个对象,布尔值,字符串,Symbol,数字,BigInt,或者是 Environment Record。如果基底是 undefined 代表这个引用无法被解析。引用名会是字符串或者 Symbol,即我们能在 JavaScript 中使用的键类型。

所以我们继续查看以下调用表达式代表的运行时语义。

CallExpression : CoverCallExpressionAndAsyncArrowHead
1. Let expr be CoveredCallExpression of CoverCallExpressionAndAsyncArrowHead.
2. Let memberExpr be the MemberExpression of expr.
3. Let arguments be the Arguments of expr.
4. Let ref be the result of evaluating memberExpr.
5. Let func be ? GetValue(ref).
6. If Type(ref) is Reference, IsPropertyReference(ref) is false, and GetReferencedName(ref) is "eval", then
    a. If SameValue(func, %eval%) is true, then
        i. Let argList be ? ArgumentListEvaluation of arguments.
        ii. If argList has no elements, return undefined.
        iii. Let evalArg be the first element of argList.
        iv. If the source code matching this CallExpression is strict mode code, let strictCaller be true. Otherwise let strictCaller be false.
        v. Let evalRealm be the current Realm Record.
        vi. Return ? PerformEval(evalArg, evalRealm, strictCaller, true).
7. Let thisCall be this CallExpression.
8. Let tailCall be IsInTailPosition(thisCall).
9. Return ? EvaluateCall(func, ref, arguments, tailCall).

调用表达式的运行时语义抽象操作中,在第 5 步通过 GetValue 获取 MemberExpression 运行时语义抽象操作返回的引用类型表达的值。

上文中我们提到了“抽象操作”这个词,抽象操作的写法 OperationName(arg1, arg2) 与函数类似,也可以接受一个或多个参数,而他们与普通 JavaScript 函数不同的是,他们不能被在 JavaScript 中直接访问到,只是作为一个书写惯例,便于在 ECMAScript 规范文本中重复利用一系列操作与算法。

Records & Completion Records

在前文的抽象操作中我们会注意到,其中的部分操作前会有 ? 记号,这个记号代表了什么含义?

部分规范中定义的操作就与 ECMAScript 函数一样,需要处理各种控制流不同的表现行为,如通过 throws关键字中断的执行,并附带一个异常值 Error,或者通过 return 关键字中断函数的执行,并返回一个返回值一样,在 ECMAScript 规范中就是通过 Completion Record 类型来表达不同情况与他们附带的值的。

Records 类型是一个只在 ECMAScript 规范中使用的用来表达包含一系列数据的抽象类型,就如同抽象操作一样,不同的 JavaScript 引擎可以有不同的实现来代表 Record 类型。Record 值可以包含一个或多个键值对,这些键值对的值可以是普通 EMCAScript 值或者是其他 ECMAScript 中定义的抽象类型。在规范文本中,通常会使用双方括号的写法 [[Field]] 来代表对 Record 的字段访问。

Completion 类型作为一个具体的 Record 类型,下表就是 Completion Record 定义的键值对。

Field Name: [[Type]] Value:可以是 normal,break,continue,return,或者 throw 中的一个 Meaning:Completion 所代表的类型

Field Name:[[Value]] Value:任一 ECMAScript 值 ,或者为空 Meaning:代表过程中产出的值

Field Name:[[Target]] Value:任意 ECMAScript 字符串或者空 Meaning:在有目标 label 记号的控制流转移中的 label 记号,比如 break outer_loop,更多可以查阅 MDN break 语句了解更多相关信息

[[Type]] 是 normal 的 Completion Record 就可以叫做 Normal Completion,而除了 Normal Completion 之外的 Completion 类型都可以称为 Abrupt Completion。大部分时候,我们只会碰到 [[Type]] 为 throw 的 Abrupt Completion。其他三个 Abrupt Completion 只会在一些具体的文法元素被执行的时候才会出现。

在 ECMAScript 规范文本定义中,并不会出现类似 JavaScript 代码中的 try-catch 代码块,每一个可能的错误情况(或者是 Abrupt Completion)都需要被显示地处理。而如果没有一些便捷手段来处理这些情况,所有抽象操作中对错误的处理都需要写成以下四个步骤:先获取返回值;再在第二步中判断这个返回的 CompletionRecord 是不是一个 Abrupt Completion,如果是的话,就将这个 Abrupt Completion 作为这次操作的返回值返回;第三步从 CompletionRecord 中获取包裹的返回值;第四部才能开始我们真正的处理。就像下面这段描述一样:

1. Let resultCompletionRecord be AbstractOp().
2. If resultCompletionRecord is an abrupt completion, return resultCompletionRecord.
3. Let result be resultCompletionRecord.[[Value]].
4. result is the result we need. We can now do more things with it.

在 ES2016 以后,规范中就新增了几个简洁的写法,以上同样的文本可以写成下面 3 个步骤,其中第 2 步与第 3 步通过 ReturnIfAbrupt 处理所有的 Abrupt Completion,并自动将 result 的 [[Value]] 解包。

1. Let result be AbstractOp().
2. ReturnIfAbrupt(result).
3. result is the result we need. We can now do more things with it.

更进一步,通过引入 ? 记号,操作的描述就完全不再需要处理 CompletionRecord,而 result 已经是 [[Value]] 解包后的值了。

1. Let result be ? AbstractOp().
2. result is the result we need. We can now do more things with it.

? 记号类似,在 ECMAScript 规范文本中也会出现 ! 记号,这相当于对于这个操作返回值断言返回值必须是 Normal Completion。

1. Let val be ! OperationName().

// 相当于 ⬇️

1. Let val be OperationName().
2. Assert: val is never an abrupt completion.
3. If val is a Completion Record, set val to val.[[Value]].

可以在 ECMAScript 规范中的 ReturnIfAbrupt 简写符号 了解更多相关内容。

对象内部槽位

回到属性访问操作,CallExpression 在运行时需要对一个具体的函数进行函数调用操作,所以在第 5 步通过 GetValue 获取 MemberExpression 返回的引用类型对应的值:

GetValue ( V )
1. ReturnIfAbrupt(V).
2. If Type(V) is not Reference, return V.
3. Let base be GetBase(V).
4. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
5. If IsPropertyReference(V) is true, then
    a. If HasPrimitiveBase(V) is true, then
        i. Assert: In this case, base will never be undefined or null.
        ii. Set base to ! ToObject(base).
    b. Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)).
6. Else,
    a. Assert: base is an Environment Record.
    b. Return ? base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).

对比我们的例子代码可以看到,如果属性引用的基底是一个基本类型值,那么其中的步骤 5.a 就会对其执行 ToObject 抽象操作,因为基本类型实际上不像一个对象,有格子用于存储方法的内部存储,可以对各种操作进行重写,所以需要先将其转换成一个以如 String.prototype 为原型的对象,再对其取属性。ToObject 会根据参数的类型进行不同的操作,如对我们例子中的字符串基本类型,根据定义即是创建一个新的以参数中的字符串为数据源的 String 对象并返回。

ToObject ( argument )

Argument Type:Undefined Result:抛出一个 TypeError 错误。

Argument Type:Null
Result:抛出一个 TypeError 错误。

Argument Type:Boolean Result:返回一个新的 Boolean 对象,这个对象的 [[BooleanData]] 内部槽位设置为 argument 参数值。查阅 19.3 章节可以获取更多关于 Boolean 对象的描述。

Argument Type:Number Result:返回一个新的 Number 对象,这个对象的 [[NumberData]] 内部槽位设置为 argument 参数值。查阅 20.1 章节可以获取更多关于 Number 对象的描述。

Argument Type:String Result:返回一个新的 String 对象,这个对象的 [[StringData]] 内部槽位设置为 argument 参数值。查阅 21.1 章节可以获取更多关于 String 对象的描述。

Argument Type:Symbol Result:返回一个新的 Symbol 对象,这个对象的 [[SymbolData]] 内部槽位设置为 argument 参数值。查阅 19.4 章节可以获取更多关于 Symbol 对象的描述。

Argument Type:BigInt Result:返回一个新的 BigInt 对象,这个对象的 [[BigIntData]] 内部槽位设置为 argument 参数值。查阅 20.2 章节可以获取更多关于 BigInt 对象的描述。

Argument Type:Object Result:返回 argument 参数。

也就是说 GetValue 会对基本类型值转换成对象后,再对这个对象进行成员属性的访问,而对对象的成员属性访问即是 GetValue 步骤 5.b 中可以看到是通过 [[Get]] 这个操作,那么这是一个什么样的操作呢?[[这个记号]] 又代表了什么意思?

上文我们提到,ECMAScript 中访问 Record 类型的某个键值对就是使用 [[这个记号]] 的,除此之外,ECMAScript 中对对象的内部槽位与内部方法的访问也是通过类似的记号,到底是哪一种取决于使用的上下文中记号出现的位置,不过可以确定的是,通过 [[这个记号]] 访问属性是我们在 JavaScript 中都无法访问、观察到的属性。

在 ECMAScript 中,每一个 Object 都有一系列的内部方法,这些方法经常会在 ECMAScript 中定义的其他各种抽象操作中被调用。常见的有如:

  • [[Get]],用来获取对象上的一个成员属性(如 obj.prop);
  • [[Set]],用来给对象上的一个成员属性赋值(如 obj.prop = 42);
  • [[GetPrototypeOf]],用来获取对象的原型(如 Object.getPrototypeOf(obj));
  • [[GetOwnProperty]],用来获取对象的自有属性的属性描述符(如 getOwnPropertyDescriptor(obj, "prop"));
  • [[Delete]],用来删除对象上的一个属性(如 delete obj.prop)。

而函数就是一些有额外的 [[Call]] 内部方法的对象(还可以有 [[Construct]] 内部方法),因此函数也可以称为可调用的对象。

除了这些内部方法,JavaScript 对象还有很多内部槽位,这些槽位就是 ECMAScript 规范中用来存储对象的数据的地方。比如大多数对象都有的 [[Prototype]],值得注意的是我们刚提到了 [[GetPrototypeOf]]这个内部方法,那这两个有什么区别?大多数对象有 [[Prototype]] 内部槽位,但所有对象都会实现 [[GetPrototypeOf]] 内部方法。比如 Proxy 对象并没有他们自己的 [[Prototype]] 内部槽位,但是他们实现了 [[GetPrototypeOf]] 内部方法,这个内部方法会将调用代理给注册的 handler 或者代理对象的 [[GetPrototypeOf]]

可以在 9.1 章节 Ordinary Object Internal Methods and Internal Slots 了解更多详细的 Object 内部方法。

可以在 9.5 章节 Proxy Object Internal Methods and Internal Slots 了解更多关于 Proxy 外部对象的内部方法。

另外,ECMAScript 还将所有的对象分为两个类型,分别是普通对象和外部对象。大多数我们使用的对象都是普通对象,这意味着这些对象的内部方法都是在 9.1 章节 Ordinary Object Internal Methods and Internal Slots 中定义的默认方法。除此之外,我们还使用了非常多种类型的外部对象,这些对象会重新定义许多普通对象默认的内部方法,比如我们对 Array 类型使用下标赋值时 arr[1] = 123 或者 arr.length = 100,就会使用到 Array 外部对象类型重新定义的 [[DefineOwnProperty]] 对这个对象产生额外的操作,如数组扩缩容等。

可以在 9.4.2 章节 Array Exotic Objects 了解更多关于 Array 外部对象的内部方法。

我们可以通过下图更好地了解这些对象的关系。

图片来源 https://timothygu.me/es-howto图片来源 https://timothygu.me/es-howto

回到 GetValueGetValue 在将基础类型转换成普通对象后,在步骤 5.b 中通过调用对象的 [[Get]] 内部方法来获取对象的属性值:

`[[Get]]` ( P, Receiver )
1. Return ? OrdinaryGet(O, P, Receiver).

可以看到普通对象的 [[Get]] 操作将具体的内容代理给了 OrdinaryGet 抽象操作来处理。而通过 OrdinaryGet 抽象操作我们就可以不断地遍历对象与他的原型链上的所有原型对象的属性,直到找到期望的属性为止(步骤 3,如果没有找到属性,就继续调用原型的 [[Get]] 方法)。

OrdinaryGet ( O, P, Receiver )
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
    a. Let parent be ? O.[[GetPrototypeOf]]().
    b. If parent is null, return undefined.
    c. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).

其中,Property Descriptor 类型 也是一个 Record 类型,在 JavaScript 里我们通常使用对象字面量表示,如 Object.defineProperty(obj, 'foo', { enumerable: true, configurable: false, value: 'bar' })

const it = 'foobar';
'foobar'.substring(3);
// -> 'bar'

到此为止,我们就可以总结出,'foobar' 在属性 substring 的访问过程中被转换成了一个 String 对象,然后通过 String.prototype 获取到 String.prototype.substring,最后通过以过程中获得的 String 对象为 receiver,3 为参数调用这个函数我们就可以得到刚开始例子中的 "bar" 了。

String.prototype.substring

在获取到了 String.prototype.substring 这个函数后,如果我们对其调用并使用 undefined 作为 receiver(函数调用的 this 值)会发生什么?

String.prototype.substring.call(undefined, 2, 4)

我们根据以往 JavaScript 的使用经验,推测大概有两种可能:

  • String.prototype.substring()undefined 转换成字符串类型 "undefined",然后取这个字符串的索引为 2 的字符到索引为 4 的字符(即索引为 [2, 4) 的范围),即最后结果为 "de"。
  • String.prototype.substring() 抛出一个错误,拒绝以 undefined 作为 Receiver 输入。

遗憾的是在 MDN 上并没有对此有详细的说明,如果各位看官有兴趣可以翻阅一下 ECMAScript 21.1.3.22 小节中对此的定义,了解一下最后会是哪一个结果。

 相关推荐

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

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

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