本文详细介绍了 TypeScript 高级类型的使用场景,对日常 TypeScript 的使用可以提供一些帮助。
本文已收录在 Github
: github.com/beichensky/…[1] 中,走过路过点个 Star 呗
交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
语法:T & U
其返回类型既要符合
T
类型也要符合U
类型
用法:假设有两个接口:一个是 Ant
蚂蚁接口,一个是 Fly
飞翔接口,现在有一只会飞的蚂蚁:
interface Ant {
name: string;
weight: number;
}
interface Fly {
flyHeight: number;
speed: number;
}
// 少了任何一个属性都会报错
const flyAnt: Ant & Fly = {
name: '蚂蚁呀嘿',
weight: 0.2,
flyHeight: 20,
speed: 1,
};
复制代码
联合类型与交叉类型很有关联,但是使用上却完全不同。
语法:T | U
其返回类型为连接的多个类型中的任意一个
用法:假设声明一个数据,既可以是 string
类型,也可以是 number
类型
let stringOrNumber: string | number = 0
stringOrNumber = ''
复制代码
再看下面这个例子,start
函数的参数类型既是 Bird | Fish
,那么在 start
函数中,想要直接调用的话,只能调用 Bird
和 Fish
都具备的方法,否则编译会报错
class Bird {
fly() {
console.log('Bird flying');
}
layEggs() {
console.log('Bird layEggs');
}
}
class Fish {
swim() {
console.log('Fish swimming');
}
layEggs() {
console.log('Fish layEggs');
}
}
const bird = new Bird();
const fish = new Fish();
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
// 会报错:Property 'fly' does not exist on type 'Bird | Fish'
// pet.fly();
// 会报错:Property 'swim' does not exist on type 'Bird | Fish'
// pet.swim();
}
start(bird);
start(fish);
复制代码
语法:T extends K
这里的 extends 不是类、接口的继承,而是对于类型的判断和约束,意思是判断 T 能否赋值给 K
可以在泛型中对传入的类型进行约束
const copy = (value: string | number): string | number => value
// 只能传入 string 或者 number
copy(10)
// 会报错:Argument of type 'boolean' is not assignable to parameter of type 'string | number'
// copy(false)
复制代码
也可以判断 T 是否可以赋值给 U,可以的话返回 T,否则返回 never
type Exclude<T, U> = T extends U ? T : never;
复制代码
会遍历指定接口的 key 或者是遍历联合类型
interface Person {
name: string
age: number
gender: number
}
// 将 T 的所有属性转换为只读类型
type ReadOnlyType<T> = {
readonly [P in keyof T]: T
}
// type ReadOnlyPerson = {
// readonly name: Person;
// readonly age: Person;
// readonly gender: Person;
// }
type ReadOnlyPerson = ReadOnlyType<Person>
复制代码
语法:parameterName is Type
parameterName
必须是来自于当前函数签名里的一个参数名,判断 parameterName 是否是 Type 类型。
具体的应用场景可以跟着下面的代码思路进行使用:
看完联合类型的例子后,可能会考虑:如果想要在 start
函数中,根据情况去调用 Bird
的 fly
方法和 Fish
的 swim
方法,该如何操作呢?
首先想到的可能是直接检查成员是否存在,然后进行调用:
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if ((pet as Bird).fly) {
(pet as Bird).fly();
} else if ((pet as Fish).swim) {
(pet as Fish).swim();
}
}
复制代码
但是这样做,判断以及调用的时候都要进行类型转换,未免有些麻烦,可能会想到写个工具函数判断下:
function isBird(bird: Bird | Fish): boolean {
return !!(bird as Bird).fly;
}
function isFish(fish: Bird | Fish): boolean {
return !!(fish as Fish).swim;
}
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if (isBird(pet)) {
(pet as Bird).fly();
} else if (isFish(pet)) {
(pet as Fish).swim();
}
}
复制代码
看起来简洁了一点,但是调用方法的时候,还是要进行类型转换才可以,否则还是会报错,那有什么好的办法,能让我们判断完类型之后,就可以直接调用方法,不用再进行类型转换呢?
OK,肯定是有的,类型谓词 is
就派上用场了
function isBird(bird: Bird | Fish): bird is Bird {
return !!(bird as Bird).fly
}
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if (isBird(pet)) {
pet.fly();
} else {
pet.swim();
}
};
复制代码
每当使用一些变量调用 isFish
时,TypeScript
会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
TypeScript 不仅知道在 if 分支里 pet 是 Fish 类型;它还清楚在 else 分支里,一定不是 Fish 类型,一定是 Bird 类型
可以用 infer P
来标记一个泛型,表示这个泛型是一个待推断的类型,并且可以直接使用
比如下面这个获取函数参数类型的例子:
type ParamType<T> = T extends (param: infer P) => any ? P : T;
type FunctionType = (value: number) => boolean
type Param = ParamType<FunctionType>; // type Param = number
type OtherParam = ParamType<symbol>; // type Param = symbol
复制代码
判断 T 是否能赋值给 (param: infer P) => any
,并且将参数推断为泛型 P,如果可以赋值,则返回参数类型 P,否则返回传入的类型
再来一个获取函数返回类型的例子:
type ReturnValueType<T> = T extends (param: any) => infer U ? U : T;
type FunctionType = (value: number) => boolean
type Return = ReturnValueType<FunctionType>; // type Return = boolean
type OtherReturn = ReturnValueType<number>; // type OtherReturn = number
复制代码
判断 T 是否能赋值给 (param: any) => infer U
,并且将返回值类型推断为泛型 U,如果可以赋值,则返回返回值类型 P,否则返回传入的类型
typeof v === "typename"
或 typeof v !== "typename"
用来判断数据的类型是否是某个原始类型(
number
、string
、boolean
、symbol
)并进行类型保护
"typename"必须是 "number", "string", "boolean"或 "symbol"。但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
看下面这个例子, print
函数会根据参数类型打印不同的结果,那如何判断参数是 string
还是 number
呢?
function print(value: number | string) {
// 如果是 string 类型
// console.log(value.split('').join(', '))
// 如果是 number 类型
// console.log(value.toFixed(2))
}
复制代码
有两种常用的判断方式:
split
属性判断是 string
类型,是否包含 toFixed
方法判断是 number
类型弊端:不论是判断还是调用都要进行类型转换 2. 使用类型谓词
is
弊端:每次都要去写一个工具函数,太麻烦了
typeof
一展身手的时候了function print(value: number | string) {
if (typeof value === 'string') {
console.log(value.split('').join(', '))
} else {
console.log(value.toFixed(2))
}
}
复制代码
使用 typeof 进行类型判断后,TypeScript 会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
与 typeof
类似,不过作用方式不同,instanceof
类型保护是通过构造函数来细化类型的一种方式。
instanceof
的右侧要求是一个构造函数,TypeScript
将细化为:
prototype
属性的类型,如果它的类型不为 any
的话还是以 类型谓词 is 示例中的代码做演示:
最初代码:
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if ((pet as Bird).fly) {
(pet as Bird).fly();
} else if ((pet as Fish).swim) {
(pet as Fish).swim();
}
}
复制代码
使用 instanceof
后的代码:
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
复制代码
可以达到相同的效果
keyof T
对于任何类型
T
,keyof T
的结果为T
上已知的 公共属性名 的 联合
function start(pet: Bird | Fish) {
// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法
pet.layEggs();
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
复制代码
这里,keyof Person
返回的类型和 'name' | 'age' 联合类型是一样,完全可以互相替换
keyof
只能返回类型上已知的 公共属性名class Animal {
type: string;
weight: number;
private speed: number;
}
type AnimalProps = keyof Animal; // "type" | "weight"
复制代码
例如我们经常会获取对象的某个属性值,但是不确定是哪个属性,这个时候可以使用 extends
配合 typeof
对属性名进行限制,限制传入的参数只能是对象的属性名
const person = {
name: 'Jack',
age: 20
}
function getPersonValue<T extends keyof typeof person>(fieldName: keyof typeof person) {
return person[fieldName]
}
const nameValue = getPersonValue('name')
const ageValue = getPersonValue('age')
// 会报错:Argument of type '"gender"' is not assignable to parameter of type '"name" | "age"'
// getPersonValue('gender')
复制代码
T[K]
类似于
js
中使用对象索引的方式,只不过js
中是返回对象属性的值,而在ts
中返回的是T
对应属性 P 的类型
const person = {
name: 'Jack',
age: 20
}
function getPersonValue<T extends keyof typeof person>(fieldName: keyof typeof person) {
return person[fieldName]
}
const nameValue = getPersonValue('name')
const ageValue = getPersonValue('age')
// 会报错:Argument of type '"gender"' is not assignable to parameter of type '"name" | "age"'
// getPersonValue('gender')
复制代码
Readonly<T>
)interface Person {
name: string
age: number
weight: number | string
gender: 'man' | 'women'
}
type NameType = Person['name'] // string
type WeightType = Person['weight'] // string | number
type GenderType = Person['gender'] // "man" | "women"
复制代码
用于将
T
类型的所有属性设置为只读状态。
interface Person {
name: string
age: number
}
const person: Readonly<Person> = {
name: 'Lucy',
age: 22
}
// 会报错:Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'
复制代码
readonly
只读, 被readonly
标记的属性只能在声明时或类的构造函数中赋值,之后将不可改(即只读属性)
ReadonlyArray<T>
)interface Person {
name: string
age: number
}
const person: Readonly<Person> = {
name: 'Lucy',
age: 22
}
// 会报错:Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'
复制代码
只能在数组初始化时为变量赋值,之后数组无法修改
interface ReadonlyArray<T> {
/** Iterator of values in the array. */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, T]>;
/**
* Returns an iterable of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an iterable of values in the array
*/
values(): IterableIterator<T>;
}
复制代码
Partial<T>
)用于将 T
类型的所有属性设置为可选状态,首先通过 keyof T
,取出类型 T
的所有属性, 然后通过 in
操作符进行遍历,最后在属性后加上 ?
,将属性变为可选属性。
type Partial<T> = {
[P in keyof T]?: T[P];
}
复制代码
interface Person {
name: string
age: number
}
// 会报错:Type '{}' is missing the following properties from type 'Person': name, age
// let person: Person = {}
// 使用 Partial 映射后返回的新类型,name 和 age 都变成了可选属性
let person: Partial<Person> = {}
person = { name: 'pengzu', age: 800 }
person = { name: 'z' }
person = { age: 18 }
复制代码
Required<T>
)和 Partial
的作用相反
用于将 T
类型的所有属性设置为必选状态,首先通过 keyof T
,取出类型 T
的所有属性, 然后通过 in
操作符进行遍历,最后在属性后的 ?
前加上 -
,将属性变为必选属性。
type Required<T> = {
[P in keyof T]-?: T[P];
}
复制代码
interface Person {
name?: string
age?: number
}
// 使用 Required 映射后返回的新类型,name 和 age 都变成了必选属性
// 会报错:Type '{}' is missing the following properties from type 'Required<Person>': name, age
let person: Required<Person> = {}
复制代码
Pick<T>
)type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
复制代码
从
T
类型中提取部分属性,作为新的返回类型。
Pick
来实现。interface Goods {
type: string
goodsName: string
price: number
}
// 作为网络请求参数,只需要 goodsName 和 price 就可以
type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>
// 返回类型:
// type RequestGoodsParams = {
// goodsName: string;
// price: number;
// }
const params: RequestGoodsParams = {
goodsName: '',
price: 10
}
复制代码
Omit<T>
)定义:type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
和
Pick
作用相反,用于从T
类型中,排除部分属性
用法:比如长方体有长宽高,而正方体长宽高相等,所以只需要长就可以,那么此时就可以用 Omit
来生成正方体的类型
interface Rectangular {
length: number
height: number
width: number
}
type Square = Omit<Rectangular, 'height' | 'width'>
// 返回类型:
// type Square = {
// length: number;
// }
const temp: Square = { length: 5 }
复制代码
Extract<T, U>
)语法:Extract<T, U>
提取
T
中可以 赋值 给U
的类型
定义:type Extract<T, U> = T extends U ? T : never;
用法:
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T02 = Extract<string | number | (() => void), Function>; // () => void
复制代码
Exclude<T, U>
)语法:Exclude<T, U>
与
Extract
用法相反,从T
中剔除可以赋值给U
的类型
定义:type Exclude<T, U> = T extends U ? never : T
用法:
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T01 = Exclude<string | number | (() => void), Function>; // string | number
复制代码
Record<K, T>
)type Record<K extends string | number | symbol, T> = {
[P in K]: T;
}
复制代码
接收两个泛型,
K
必须可以是可以赋值给string | number | symbol
的类型,通过in
操作符对K
进行遍历,每一个属性的类型都必须是T
类型
Person
类型的数组转化成对象映射,可以使用 Record
来指定映射对象的类型interface Person {
name: string
age: number
}
const personList = [
{ name: 'Jack', age: 26 },
{ name: 'Lucy', age: 22 },
{ name: 'Rose', age: 18 },
]
const personMap: Record<string, Person> = {}
personList.map((person) => {
personMap[person.name] = person
})
复制代码
比如在传递参数时,希望参数是一个对象,但是不确定具体的类型,就可以使用 Record
作为参数类型
function doSomething(obj: Record<string, any>) {
}
复制代码
NonNullable<T>
)type NonNullable<T> = T extends null | undefined ? never : T
从 T 中剔除
null
、undefined
、never
类型,不会剔除void
、unknow
类型
type T01 = NonNullable<string | number | undefined>; // string | number
type T02 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]
type T03 = NonNullable<{name?: string, age: number} | string[] | null | undefined>; // {name?: string, age: number} | string[]
复制代码
ConstructorParameters<typeof T>
)返回 class 中构造函数参数类型组成的 元组类型
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
复制代码
class Person {
name: string
age: number
weight: number
gender: 'man' | 'women'
constructor(name: string, age: number, gender: 'man' | 'women') {
this.name = name
this.age = age;
this.gender = gender
}
}
type ConstructorType = ConstructorParameters<typeof Person> // [name: string, age: number, gender: "man" | "women"]
const params: ConstructorType = ['Jack', 20, 'man']
复制代码
InstanceType<T>
)获取 class 构造函数的返回类型
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
复制代码
class Person {
name: string
age: number
weight: number
gender: 'man' | 'women'
constructor(name: string, age: number, gender: 'man' | 'women') {
this.name = name
this.age = age;
this.gender = gender
}
}
type Instance = InstanceType<typeof Person> // Person
const params: Instance = {
name: 'Jack',
age: 20,
weight: 120,
gender: 'man'
}
复制代码
Parameters<T>
)获取函数的参数类型组成的 元组
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
复制代码
type FunctionType = (name: string, age: number) => boolean
type FunctionParamsType = Parameters<FunctionType> // [name: string, age: number]
const params: FunctionParamsType = ['Jack', 20]
复制代码
ReturnType<T>
)获取函数的返回值类型
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
复制代码
type FunctionType = (name: string, age: number) => boolean | string
type FunctionReturnType = ReturnType<FunctionType> // boolean | string
复制代码
用法 | 描述 |
---|---|
& | 交叉类型,将多个类型合并为一个类型,交集 |
\ | 联合类型,将多个类型组合成一个类型,可以是多个类型的任意一个,并集 |
用法 | 描述 |
---|---|
T extends U | 类型约束,判断 T 是否可以赋值给 U |
P in T | 类型映射,遍历 T 的所有类型 |
parameterName is Type | 类型谓词,判断函数参数 parameterName 是否是 Type 类型 |
infer P | 待推断类型,使用 infer 标记类型 P,就可以使用待推断的类型 P |
typeof v === "typename" | 原始类型保护,判断数据的类型是否是某个原始类型(number、string、boolean、symbol) |
instanceof v | 类型保护,判断数据的类型是否是构造函数的 prototype 属性类型 |
keyof | 索引类型查询操作符,返回类型上已知的 公共属性名 |
T[K] | 索引访问操作符,返回 T 对应属性 P 的类型 |
用法 | 描述 |
---|---|
Readonly | 将 T 中所有属性都变为只读 |
ReadonlyArray | 返回一个 T 类型的只读数组 |
ReadonlyMap<T, U> | 返回一个 T 和 U 类型组成的只读 Map |
Partial | 将 T 中所有的属性都变成可选类型 |
Required | 将 T 中所有的属性都变成必选类型 |
Pick<T, K extends keyof T> | 从 T 中摘取部分属性 |
Omit<T, K extends keyof T> | 从 T 中排除部分属性 |
Exclude<T, U> | 从 T 中剔除可以赋值给 U 的类型 |
Extract<T, U> | 提取 T 中可以赋值给 U 的类型 |
Record<K, T> | 返回属性名为 K,属性值为 T 的类型 |
NonNullable | 从 T 中剔除 null 和 undefined |
ConstructorParameters | 获取 T 的构造函数参数类型组成的元组 |
InstanceType | 获取 T 的实例类型 |
Parameters | 获取函数参数类型组成的元组 |
ReturnType | 获取函数返回值类型 |
如果有写的不对或不严谨的地方,欢迎大家能提出宝贵的意见,十分感谢。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/KyT3wXXTBVbxaM445ZHaeg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。