前端埋点数据收集及上报方案

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

什么是埋点

埋点,它的学名是事件追踪(Event Tracking),主要是针对特定用户行为或业务过程进行捕获、处理和发送的相关技术及实施过程。埋点是数据领域的一个专业术语,也是互联网领域的一个俗称。

埋点是产品数据分析的基础,一般用于推荐系统的反馈、用户行为的监控和分析、新功能或者运营活动效果的统计分析等。

埋点包含两个重要概念:事件(event),属性(param)

  • 事件(event):应用中发生了什么,例如用户操作、系统事件或系统错误。以你拍一产品为例,包含以下事件:enter_page(进入页面)、leave_page(离开页面)。
  • 属性(param):为了描述用户群细分而定义的属性,例如语言偏好或地理位置。以“进入课后练习”事件为例,它包含如下事件属性:enter_from(从哪个页面来),class_id(课程id)等。
  • 属性值(value):属性的维度,即行为触发时的具体维度。例如:enter_from:home(主页)、system(系统)等。

主流方案

  • 无痕埋点(全埋点),利用浏览器或APP自带的监听方式,对用户的浏览页面、点击等行为进行收集,一般用于粗颗粒度的数据分析,例如公司的slardar

  • 数据噪声大,不管有用没有,数据都会被收集

  • 无法定制化埋点,无法采集到指定事件和业务属性

  • 可供DA使用的信息较少

  • 接入简单,几乎无侵入,不需要额外的开发成本

  • 用户操作行为收集非常完整,几乎不会遗漏

  • 优点:

  • 缺点:

  • 代码埋点,前端开发人员在代码中自定义监听和收集

  • 工作量大,而且对代码侵入性很大,后期维护也不是很方便

  • 可以精确埋点,具备明确的事件标识

  • 业务属性非常丰富

  • 埋点触发方式可以灵活定义

  • DA使用更方便和精确

  • 优点:

  • 缺点:

  • 埋点sdk,sdk向外暴露上报埋点的接口,监听和收集过程开发人员无感知。例如公司的tea

  • 暂时想不到

  • 业务开发只需关注事件标识、业务属性等

  • 兼顾无痕埋点优点和代码埋点的优势

  • 优点:

  • 缺点:

常见埋点属性

通常前端是按照页面维度统计埋点的,常见的事件属性如下:

属性 描述
uid 用户id,若用户未登陆,则返回特定标识id
url 当前事件触发页面的url
eventTime 触发埋点的时间戳
localTime 触发埋点时的用户本地时间,使用标准YYYY-MM-DD HH:mm:ss格式表示,方便后期直接使用字符串查询
deviceType 当前用户使用的设备类型,比如apple、三星、chrome等
deviceId 当前用户使用的设备id
osType 当前用户使用的系统类型,比如windows、macos、ios、android等
osVersion 当前用户使用的系统版本
appVersion 当前应用版本
appId 当前应用id
extra 自定义数据,一般是序列化的字符串,且数据结构应保持稳定

常见埋点事件

事件 上报时机 描述
页面停留 当前页面切换或者页面卸载时 记录前一页浏览时间
pv 进入页面时 页面访问次数,uv只需要根据deviceId过滤
交互事件 用户交互事件触发时 比如点击、长按等
逻辑事件 符合逻辑条件时 比如登陆、跳转页面等

性能数据采集方案

目前性能指标数据大部分来源于 window.performance API。

Performance.timing

参数名 描述
connectEnd HTTP(TCP) 返回浏览器与服务器之间的连接建立时的时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。
connectStart HTTP(TCP) 域名查询结束的时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和 fetchStart一致。
domComplete 当前文档解析完成,即Document.readyState 变为 'complete'且相对应的readystatechange 被触发时的时间戳
domContentLoadedEventEnd 当所有需要立即执行的脚本已经被执行(不论执行顺序)时的时间戳。
domContentLoadedEventStart 当解析器发送DOMContentLoaded 事件,即所有需要被执行的脚本已经被解析时的时间戳。
domInteractive 当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳。
domLoading 当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的 readystatechange事件触发时)的时间戳。
domainLookupEnd DNS 域名查询完成的时间。如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupStart DNS 域名查询开始的UNIX时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和fetchStart一致。
fetchStart 浏览器准备好使用HTTP请求来获取(fetch)文档的时间戳。这个时间点会在检查任何应用缓存之前。
loadEventEnd 当load事件结束,即加载事件完成时的时间戳。如果这个事件还未被发送,或者尚未完成,它的值将会是0.
loadEventStart load事件被发送时的时间戳。如果这个事件还未被发送,它的值将会是0。
navigationStart 同一个浏览器上一个页面卸载(unload)结束时的时间戳。如果没有上一个页面,这个值会和fetchStart相同。
redirectEnd 最后一个HTTP重定向完成时(也就是说是HTTP响应的最后一个比特直接被收到的时间)的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0.
redirectStart 第一个HTTP重定向开始时的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0。
requestStart 返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间戳。
responseEnd 返回浏览器从服务器收到(或从本地缓存读取,或从本地资源读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳。
responseStart 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳。如果传输层在开始请求之后失败并且连接被重开,该属性将会被数制成新的请求的相对应的发起时间
secureConnectionStart HTTPS 返回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接,则返回0。
unloadEventEnd 和 unloadEventStart 相对应,unload事件处理完成时的时间戳。如果没有上一个页面,这个值会返回0。
unloadEventStart 上一个页面unload事件抛出时的时间戳。如果没有上一个页面,这个值会返回0。

常见性能指标

指标名 描述
FP 页面首次绘制时间
FCP 页面首次有内容绘制的时间
FMP 页面首次有效绘制时间,FMP >= FCP
TTI 页面完全可交互时间
FID 页面加载阶段,用户首次交互操作的延时时间
MPFID 页面加载阶段,用户交互操作可能遇到的最大延时时间
LOAD 页面完全加载的时间(load 事件发生的时间)

FP

FP (First Paint)指标通常会反映页面的白屏时间,而白屏时间会反映当前 Web 页面的网络加载性能情况,当加载性能非常良好的情况下,白屏的时间就会越短,用户等待内容的时间就会越短,流失的概率就会降低。

该指标可以通过 performance.getEntriesByType('paint') 方法获取 PerformancePaintTiming API 提供的打点信息,找到 name 为 first-paint 的对象,描述的即为 FP 的指标数据,如下图所示:

FCP

FCP (First Contentful Paint) 为首次有内容渲染的时间点,在性能统计指标中,从用户开始访问 Web 页面的时间点到 FCP 的时间点这段时间可以被视为无内容时间,一般 FCP >= FP。

该指标可以通过 performance.getEntriesByType('paint') 方法获取 PerformancePaintTiming API 提供的打点信息,找到 name 为 first-contentful-paint 的对象,描述的即为 FCP 的指标数据,如下图所示:

FMP

FMP(First Meaningful Paint),即首次绘制有意义内容的时间,当整体页面的布局和文字内容全部渲染完成后,即可认为是完成了首次有意义内容的绘制。所以 FMP 衡量了用户看到网页的主要内容的时间,是用户体验角度的一种重要的衡量指标。

前端业界现在比较认可的一个计算 FMP 的方式就是「页面在加载和渲染过程中最大布局变动之后的那个绘制时间 」。可通过 MutationObserver 监听每一次页面整体的 DOM 变化,触发 MutationObserver 的回调,在回调计算出当前 DOM 树的变动分数,分数变化最剧烈的时刻,即为 FMP 的时间点。

TTI

TTI(Time To Interactive),即从页面加载开始到页面处于完全可交互状态所花费的时间。页面处于完全可交互状态时,满足以下 3 个条件:

  1. 页面已经显示有用内容。
  2. 页面上的可见元素关联的事件响应函数已经完成注册。
  3. 事件响应函数可以在事件发生后的 50ms 内开始执行。

资源加载指标

window.performance.getEntriesByType('resource')会返回当前页面加载的所有资源(js、css、img...)的各类性能指标,可用于静态资源性能数据采集。

主要类型有:script、link、img、css、xmlhttprequest、beacon、fetch、other。PerformanceResourceTiming - Web APIs | MDN

参数名 描述
connectEnd 一个 DOMHighResTimeStamp,表示浏览器完成建立与服务器的连接以检索资源之后的时间。
connectStart 一个 DOMHighResTimeStamp,表示浏览器开始建立与服务器的连接以检索资源之前的时间。
decodedBodySize 一个 number,表示在删除任何应用的内容编码之后,从消息主体的请求(HTTP 或缓存)中接收到的大小(以八位字节为单位)。
domainLookupEnd 一个 DOMHighResTimeStamp,表示浏览器完成资源的域名查找之后的时间。
domainLookupStart 一个 DOMHighResTimeStamp,表示在浏览器立即开始资源的域名查找之前的时间
duration 返回一个 timestamp,即 responseEnd 和 startTime 属性的差值。
encodedBodySize 一个 number,表示在删除任何应用的内容编码之前,从有效内容主体的请求(HTTP 或缓存)中接收到的大小(以八位字节为单位)。
entryType 返回 "resource"。
fetchStart 一个 DOMHighResTimeStamp,表示浏览器即将开始获取资源之前的时间。
initiatorType 一个 string,代表启动性能条目的资源的类型
name 返回资源 URL。
nextHopProtocol 一个 string,代表用于获取资源的网络协议,由 ALPN 协议 ID(RFC7301) 定义。
redirectEnd 一个 DOMHighResTimeStamp,表示收到上一次重定向响应的发送最后一个字节时的时间。
redirectStart 一个 DOMHighResTimeStamp 代表启动重定向的请求开始之前的时间。
requestStart 一个 DOMHighResTimeStamp,表示浏览器开始向服务器请求资源之前的时间。
responseEnd 一个 DOMHighResTimeStamp,表示在浏览器接收到资源的最后一个字节之后或在传输连接关闭之前(以先到者为准)的时间。
responseStart 一个 DOMHighResTimeStamp,表示浏览器从服务器接收到响应的第一个字节后的时间。
secureConnectionStart 一个 DOMHighResTimeStamp,表示浏览器即将开始握手过程以保护当前连接之前的时间。
serverTiming 一个 PerformanceServerTiming 数组,包含服务器计时指标的 PerformanceServerTiming 条目。
startTime 返回一个 timestamp,表示资源获取开始的时间。该值等效于 fetchStart。
transferSize 一个 number 代表所获取资源的大小(以八位字节为单位)。该大小包括响应标头字段以及响应有效内容主体。
workerStart 一个 DOMHighResTimeStamp, 如果服务 Worker 线程已经在运行,则返回在分派 FetchEvent 之前的时间戳,如果尚未运行,则返回在启动 Service Worker 线程之前的时间戳。如果服务 Worker 未拦截该资源,则该属性将始终返回 0。

其他指标计算方式

指标名 描述 计算方式
DNS查询 DNS 阶段耗时 domainLookupEnd - domainLookupStart
TCP连接 TCP 阶段耗时 connectEnd - connectStart
SSL建连 SSL 连接时间 connectEnd - secureConnectionStart
首字节网络请求 首字节响应时间(ttfb) responseStart - requestStart
内容传输 内容传输,Response阶段耗时 responseEnd - responseStart
DOM解析 Dom解析时间 domInteractive - responseEnd
资源加载 资源加载 loadEventStart - domContentLoadedEventEnd
首字节 首字节 responseStart - fetchStart
DOM Ready dom ready domContentLoadedEventEnd - fetchStart
redirect时间 重定向时间 redirectEnd - redirectStart
DOM render dom渲染耗时 domComplete - domLoading
load 页面加载耗时 loadEventEnd - navigationStart
unload 页面卸载耗时 unloadEventEnd - unloadEventStart
请求耗时 请求耗时 responseEnd - requestStart
白屏时间 白屏时间 domLoading - navigationStart

错误数据采集方案

目前所能捕捉的错误有三种:

  • 资源加载错误,通过 addEventListener('error', callback, true) 在捕获阶段捕捉资源加载失败错误。

  • js 执行错误,通过 window.onerror 捕捉 js 错误。

  • 跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。此时需要在script标签增加crossorigin="anonymous"属性,同时资源服务器需要增加CORS相关配置,比如Access-Control-Allow-Origin: *

  • promise 错误,通过 addEventListener('unhandledrejection', callback)捕捉 promise 错误,但是没有发生错误的行数,列数等信息,只能手动抛出相关错误信息。

// 在捕获阶段,捕获资源加载失败错误
addEventListener('error', e => {
    const target = e.target
    if (target != window) {
        monitor.errors.push({
            type: target.localName,
            url: target.src || target.href,
            msg: (target.src || target.href) + ' is load error',
            time: Date.now()
        })
    }
}, true)

// 监听 js 错误
window.onerror = function(msg, url, row, col, error) {
    monitor.errors.push({
        type: 'javascript',
        row: row,
        col: col,
        msg: error && error.stack? error.stack : msg,
        url: url,
        time: Date.now()
    })
}

// 监听 promise 错误 缺点是获取不到行数数据
addEventListener('unhandledrejection', e => {
    monitor.errors.push({
        type: 'promise',
        msg: (e.reason && e.reason.msg) || e.reason || '',
        time: Date.now()
    })
})
复制代码

数据上报方案

在这个场景中,需要考虑两个问题:

  • 如果数据上报接口与业务系统使用同一域名,浏览器对请求并发量有限制,所以存在网络资源竞争的可能性。
  • 浏览器通常在页面卸载时会忽略异步ajax请求,如果需要必须进行数据请求,一般在unload或者beforeunload事件中创建同步ajax请求,以此延迟页面卸载。从用户侧角度,就是页面跳转变慢。

Beacon

可以看到,除开ie浏览器,目前主流现代浏览器对beacon的支持率非常高。Beacon - MDN文档

Beacon 接口用来调度向 Web 服务器发送的异步非阻塞请求。

  • Beacon 请求使用 HTTP POST方法,并且不需要有响应。
  • Beacon 请求能确保在页面触发 unload 之前完成初始化。

通俗的讲就是,Beacon可将数据异步发送至服务端,且能够保证在页面卸载完成前发送请求(解决ajax页面卸载会终止请求的问题)。使用方法如下:

navigator.sendBeacon(url, data);
复制代码

其中 data 参数是可选的,它的类型可以为 ArrayBufferView, Blob, DOMString 或者 FormData。如果浏览器成功地将 beacon 请求加入到待发送的队列里,这个方法将会返回 true ,否则将会返回 false

使用Beacon时需要后台需要使用post方法接收参数,考虑到跨域问题,后台还需要改造接口配置CORS。同时请求头必须满足CORS-safelisted request-header,其中content-type的类型必须为application/x-www-form-urlencoded, multipart/form-data, 或者text/plain

type ContentType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';

const serilizeParams = (params: object) => {
    return window.btoa(JSON.stringify(params))
}

function sendBeacon(url: string, params: object) {
  const formData = new FormData()
  formData.append('params', serilizeParams(params))
  navigator.sendBeacon(url, formData)
}
复制代码

Image

sendBeacon的兼容性问题是不可避免的,不过可以充分利用大部分浏览器会在页面卸载前完成图片的加载的特性,通过在页面添加img的方式上报数据。

function sendImage(url: string, params: object) {
  const img = new Image()

  img.style.display = 'none'

  const removeImage = function() {
    img.parentNode.removeChild(img)
  }

  img.onload = removeImage
  img.onerror = removeImage

  img.src = `${url}?params=${serilizeParams(params)}`

  document.body.appendChild(img)
}
复制代码

由于img图片为get请求方式,不同服务器针对uri的长度有限制,长度超过限制时会出现HTTP 414错误,所以还要注意上报频率,减少一次性上传的属性过多。

HTTP 1.1 defines Status Code 414 Request-URI Too Long for the cases where a server-defined limit is reached. You can see further details on RFC 2616. For the case of client-defined limits, there is no sense on the server returning something, because the server won't receive the request at all.

兼容方案

优先使用sendBeacon的方式,Image方式作为fallback。

function sendLog(url: string, params: object) {
    if(navigator.sendBeacon) {
        sendBeacon(url, params)
    } else {
        sendImage(url, params)
    }
}
复制代码

相关文档

  • 深入了解前端监控原理
  • 前端埋点数据收集及上报方案

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

 相关推荐

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

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

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