前端性能监控

大纲 我们会从以下三个方向来讲解埋点与监控的知识: 什么是埋点?什么是监控? JS 中实现监控的核心方案 写一个“相对”完整的监控实例 一、什么是埋点?什么是监控? 在日常沟通中,我们经常会把【埋点】和【监控】放到一起说,但是它们在本质上是有一定的区别的: 1. 埋点 埋点主要用于收集用户行为数据。在日常开发中,我们会通过 在前端代码中插入代码或脚本的方式 来实现埋点功能。 埋点的主要作用就是:捕获特定用户行为(如点击、浏览、提交表单、页面跳转等)以及关键业务数据(如下单金额、商品类别等) 在日常开发中,埋点的实现方案大致可以分为以下三大类: 手动埋点:在代码中手动加入记录代码来捕获特定事件。 自动埋点:利用 DOM 事件代理等技术来捕获页面上所有事件,从而减少手动配置。 可视化埋点:通过工具界面标记需要采集的元素和事件,可以不用手写代码。 2. 监控 而监控则主要关注 系统的性能和稳定性。在日常开发中,我们会通过 采集页面加载时间、资源请求、错误日志等数据 的方式来实现前端监控。 监控的主要作用就是:及时发现并定位页面性能瓶颈或代码异常,目的是为了保障系统不出 bug 在日常开发中,监控一般需要完成以下三大部分: 性能监控:如:首屏加载时间、页面交互耗时、资源加载耗时等。 错误监控:捕获 JavaScript 错误、网络请求失败、资源加载异常等。 用户体验监控:收集白屏、卡顿等影响用户体验的问题等。 区别总结 维度 前端埋点 前端监控 目标 捕获用户行为数据 监控系统性能、错误、稳定性 数据类型 用户点击、表单提交、页面跳转等 页面加载时间、错误日志、卡顿情况等 实现方式 手动埋点、自动埋点、可视化埋点 错误捕获、性能指标采集 核心关注点 用户行为、业务数据 系统Bug、性能优化 二、JS 中实现监控的核心方案 根据上面所说,我们知道埋点和监控的目的存在不同,但是它们的思路确是有很多一致性的,其核心都是:获取关键的数据,发送(上报)给服务端,依据数据来解决其不同的目的。 所以,无论是埋点也好,还是监控也罢,我们都需要 获取关键位置数据。 1. 跟踪用户事件(点击、滚动等) 定义通用跟踪函数(后续事件会通过该函数完成上报):trackEvent 函数接收事件类型和事件详情,并上报到服务端。 // 用于记录或发送跟踪数据到服务器的函数 function trackEvent(eventType, details) { console.log(`Event: ${eventType}`, details); // 在控制台打印事件类型和详情 // 上报到服务端。 fetch('/测试接口地址', { method: 'POST', body: JSON.stringify({ eventType, details }) }); } 捕获按钮点击事件:获取 id 为 myButton 的按钮,并在其 click 事件上添加监听器。在按钮被点击时调用 trackEvent 函数,记录点击事件的类型(button_click)、按钮 ID 和时间戳。 ...

August 7, 2025

浏览器的缓存机制解析

概述 浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,所以在分析浏览器缓存机制之前,我们先使用图文简单介绍一下HTTP报文,HTTP报文分为两种: HTTP请求(Request)报文,报文格式为:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体),如下图 HTTP响应(Response)报文,报文格式为:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体,如下图 注:通用信息头指的是请求和响应报文都支持的头域,分别为Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via;实体头则是实体信息的实体头域,分别为Allow、Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、extension-header。这里只是为了方便理解,将通用信息头,响应头/请求头,实体头都归为了HTTP头。 以上的概念在这里我们不做多讲解,只简单介绍,有兴趣的童鞋可以自行研究。 缓存过程分析 浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图: 由上图我们可以知道: 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中 以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。 强制缓存 强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下: 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图: 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存(暂不分析),如下图 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果,如下图 那么强制缓存的缓存规则是什么? 当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。 Expires Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。 Expires是HTTP/1.0的字段,但是现在浏览器默认使用的是HTTP/1.1,那么在HTTP/1.1中网页缓存还是否由Expires控制? 到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义,那么Cache-Control又是如何控制的呢? Cache-Control 在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为: public:所有内容都将被缓存(客户端和代理服务器都可缓存) private:所有内容只有客户端可以缓存,Cache-Control的默认取值 no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定 no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效 接下来,我们直接看一个例子,如下: 由上面的例子我们可以知道: HTTP响应报文中expires的时间值,是一个绝对值 HTTP响应报文中Cache-Control为max-age=600,是相对值 由于Cache-Control的优先级比expires,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。 注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。 了解强制缓存的过程后,我们拓展性的思考一下: 浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效? 这里我们以博客的请求为例,状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。 那么from memory cache 和 from disk cache又分别代表的是什么呢?什么时候会使用from disk cache,什么时候会使用from memory cache呢? ...

July 28, 2025

跨域资源共享(CORS)

跨域资源共享(CORS,Cross-Origin Resource Sharing)是浏览器用来放宽同源策略限制的一种机制,是前端跨域请求的主流解决方案。面试时考察点主要围绕它的原理、工作流程、配置细节及相关安全问题。 一、CORS 基础概念 作用:允许浏览器从不同源的服务器请求资源,突破同源策略限制。 原理:服务器通过设置特定的 HTTP 响应头,告诉浏览器允许跨域访问。 二、CORS 的关键响应头 头部 说明 Access-Control-Allow-Origin 指明允许访问的源,可以是具体域名(如 https://example.com)或 *(表示允许所有域访问) Access-Control-Allow-Methods 允许的请求方法(GET、POST、PUT、DELETE 等) Access-Control-Allow-Headers 允许请求携带的自定义头部字段 Access-Control-Allow-Credentials 是否允许携带 Cookie 或 HTTP 认证信息,值为 true 时允许 Access-Control-Max-Age 预检请求的结果缓存时间,单位秒 三、CORS 请求分类 1. 简单请求 满足以下条件,浏览器直接发请求: 请求方法是 GET、POST、HEAD 中之一。 请求头只包含简单头(如 Accept, Content-Type 仅限 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)。 不携带自定义 Cookie、认证信息。 2. 预检请求(Preflight) 当请求不满足简单请求条件时,浏览器先发送 OPTIONS 请求询问服务器是否允许该跨域请求。 服务器响应通过特定头部告诉浏览器是否放行。 四、CORS 工作流程 浏览器发送跨域请求。 如果是简单请求,直接带上请求头发送实际请求,服务器返回响应带有 CORS 相关头,浏览器决定是否允许。 如果是非简单请求,浏览器先发 OPTIONS 预检请求。 服务器响应预检,决定是否允许后,浏览器再发实际请求。 浏览器根据响应头决定是否允许前端访问响应内容。 五、携带 Cookie 的跨域请求 默认情况下,跨域请求不发送 Cookie。 前端请求时必须设置:xhr.withCredentials = true 或 fetch 的 credentials: 'include'。 服务器必须设置:Access-Control-Allow-Credentials: true。 Access-Control-Allow-Origin 不能使用 *,必须指定具体域名。 常见考点 CORS 是什么?为什么需要它? 同源策略和 CORS 的关系? 简单请求和预检请求的区别? 预检请求的触发条件有哪些? Access-Control-Allow-Origin 设置为 * 有什么限制? 如何支持带 Cookie 的跨域请求? CORS 配置中的常用响应头说明。 如何解决跨域请求失败问题? JSONP 和 CORS 的区别? 服务器如何配置支持 CORS?

December 17, 2024

高阶组件

一 前言 React高阶组件(HOC),对于很多react开发者来说并不陌生,它是灵活使用react组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也是一个组件的函数。 高阶作用用于强化组件,复用逻辑,提升渲染性能等作用。高阶组件也并不是很难理解,其实接触过后还是蛮简单的,接下来将按照,高阶组件理解?,高阶组件具体怎么使用?应用场景, 高阶组件实践(源码级别) 为突破口,带大家详细了解一下高阶组件。 我们带着问题去开始今天的讨论: 1 什么是高阶组件,它解决了什么问题? 2 有几种高阶组件,它们优缺点是什么? 3 如何写一个优秀高阶组件? 4 hoc怎么处理静态属性,跨层级ref等问题? 5 高阶组件怎么控制渲染,隔离渲染? 6 高阶组件怎么监控原始组件的状态? … 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 二 全方位看高阶组件 1 几种包装强化组件的方式 ① mixin模式 原型图 老版本的react-mixins 在react初期提供一种组合方法。通过React.createClass,加入mixins属性,具体用法和vue 中mixins相似。具体实现如下。 const customMixin = { componentDidMount(){ console.log( '------componentDidMount------' ) }, say(){ console.log(this.state.name) } } const APP = React.createClass({ mixins: [ customMixin ], getInitialState(){ return { name:'alien' } }, render(){ const { name } = this.state return <div> hello ,world , my name is { name } </div> } }) 这种mixins只能存在createClass中,后来React.createClass连同mixins这种模式被废弃了。mixins会带来一些负面的影响。 ...

December 17, 2024

Web Storage

一、 简介 浏览器本地存储是指浏览器提供的一种机制,允许 Web 应用程序在浏览器端存储数据,以便在用户下次访问时可以快速获取和使用这些数据。一共两种存储方式:localStorage 和 sessionStorage。下面介绍下两种缓存的特性和在内部平台的一些应用。 二、localStorage 和 sessionStorage 2.1、区别 localStorage 和 sessionStorage 的主要区别是生命周期,具体区别如下: localStorage sessionStorage 生命周期 持久化存储:除非自行删除或清除缓存,否则一直存在 会话级别的存储:浏览器标签页或窗口关闭 作用域 相同浏览器,同域名,不同标签,不同窗口 相同浏览器,同域名,同源窗口 获取方式 window.localStorage window.sessionStorage 存储容量 5M 5M 容量限制的目的是防止滥用本地存储空间,导致用户浏览器变慢。 2.2、浏览器兼容性 1)现在的浏览器基本上都是支持这两种 Storage 特性的。各浏览器支持版本如下: Chrome Firefox IE Opera Safari Android Opera Mobile Safari Mobile localStorage 4 3.5 8 10.5 4 2.1 11 iOS 3.2 sessionStorage 5 2 8 10.5 4 2.1 11 iOS 3.2 2)如果使用的是老式浏览器,比如Internet Explorer 6、7 或者其他,就需要在使用前检测浏览器是否支持本地存储或者是否被禁用。以 localStorage 为例: ...

October 9, 2024

Flexbox

一、Flexbox布局的概念 Flexbox布局也叫Flex布局,弹性盒子布局。它的目标是提供一个更有效地布局、对齐方式,并且能够使父元素在子元素的大小未知或动态变化情况下仍然能够分配好子元素之间的间隙。主要思想是使父元素能够调整子元素的宽度、高度、排列方式,从而更好的适应可用的布局空间。设定为flex布局的元素能够放大子元素使之尽可能填充可用空间,也可以收缩子元素使之不溢出。 ​ Flex布局更适合小规模的布局,可以简便、完整、响应式的实现各种页面布局。但是,设为Flex布局以后,其子元素的float、clear和vertical-align属性将失效。Flex弹性盒模型的优势在于只需声明布局应该具有的⾏为,⽽不需要给出具体的实现⽅式,浏览器负责完成实际布局,当布局涉及到不定宽度,分布对⻬的场景时,就要优先考虑弹性盒布局。 Flex布局是一个完整的模块,它包括了一套完整的属性。其中采用 Flex 布局的元素,称为 Flex 容器,简称"容器"。它的所有子元素就是容器成员,称为 Flex 项目,简称"项目"。 容器默认存在两个轴:水平轴(main axis)和垂直轴(cross axis),项目默认沿主轴排列(水平轴): 这里面涉及到了几个概念,下面来看一下: main axis: Flex 父元素的主轴是指子元素布局的主要方向轴,它由属性flex-direction来确定主轴是水平还是垂直的,默认为水平轴。 main-start & main-end: 分别表示主轴的开始和结束,子元素在父元素中会沿着主轴从main-start到main-end排布。 main size: 单个项目占据主轴的长度大小。 cross axis: 交叉轴,与主轴垂直。 cross-start & cross-end: 分别表示交叉轴的开始和结束。子元素在交叉轴的排布从cross-start开始到cross-end。 cross size: 子元素在交叉轴方向上的大小。 二、父元素属性 想要使用flex布局,首先需要给父元素指定为flex布局,这样容器内的元素才能实现flex布局: <div class="container"></div> .container { display: flex | inline-flex; } 这里有两种方式可以设置flex布局,使用display: flex;会生成一个块状的flex容器盒子,使用display: inline-flex;会生成一个行内的flex容器盒子。如果我们使用块状元素,比如div标签,就可以使用flex,如果使用行内元素,就可以使用inline-flex。多数情况下,我们会使用display: flex;。 父元素(容器)可以设置以下六个属性: flex-direction flex-wrap flex-flow justify-content align-items align-content 1. flex-direction flex-direction:主轴方向,它决定了容器内元素排列方向,它有四个属性值: .container { flex-direction: row | row-reverse | column | column-reverse; } (1)flex-direction: row:默认值,沿水平主轴从左到右排列,起点在左沿 (2)flex-direction: row-reverse:沿水平主轴从右到左排列,起点在右沿 (3)flex-direction: column:沿垂直主轴从上到下排列,起点在上沿 ...

October 8, 2024

v-for

在 Vue.js 开发中,v-for 是我们用来遍历数组、对象或其他数据结构的核心指令。然而,很多开发者可能忽略了其中的一个细微差别:in 和 of。这两个关键字看似相似,但却有各自不同的使用场景和适用数据类型。理解它们的区别,可以帮助我们编写更简洁、高效的代码。 本文将详细探讨 v-for 中的 in 和 of,并讲解它们各自的适用场景,帮助你在 Vue.js 项目中做出最佳选择。 什么是 v-for? v-for 是 Vue.js 提供的用于遍历数据的指令,它可以帮助你快速渲染列表、表格等动态生成的内容。在使用 v-for 时,我们通常需要遍历一个数据集合,并将每一项绑定到 DOM 元素上。 in 和 of 的概念 v-for 中的 in 和 of 是遍历数据时的两个常用关键字,它们的主要区别在于:in 更通用,可以遍历数组、对象和整数范围;而 of 专注于遍历可迭代对象(如数组、字符串、Set、Map 等) 。 接下来,我们深入探讨它们的具体用法及差异。 1. in:适用于数组、对象、整数范围 1.1 遍历数组 in 是 Vue.js 中默认推荐的方式,用于遍历数组中的元素。你可以轻松地用 in 处理数组中的每个值。 <ul> <li v-for="item in items">{{ item }}</li> </ul> 假设 items = ['apple', 'banana', 'cherry'],输出结果为: <li>apple</li> <li>banana</li> <li>cherry</li> 1.2 遍历对象 当你需要遍历对象的键和值时,in 也非常方便。通过 (value, key) 语法,你可以轻松获取对象的键和值。 ...

October 5, 2024

Class

Class 的基本语法 简介 类的由来 JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。 function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2); 上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。 ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。 基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } 上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数Point是一致的。 Point类除了构造方法,还定义了一个toString()方法。注意,定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。 ES6 的类,完全可以看作构造函数的另一种写法。 class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true 上面代码表明,类的数据类型就是函数,类本身就指向构造函数。 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。 class Bar { doStuff() { console.log('stuff'); } } const b = new Bar(); b.doStuff() // "stuff" 构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。 ...

October 2, 2024

原型链与继承

JavaScript 中没有类的概念的,主要通过原型链来实现继承。通常情况下,继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联(原型对象指针),这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。 原型 当我们 new 了一个新的对象实例,明明什么都没有做,就直接可以访问 toString 、valueOf 等原生方法。那么这些方法是从哪里来的呢?答案就是原型。 在控制台打印一个空对象时,我们可以看到,有很多方法,已经“初始化”挂载在内置的 __proto__ 对象上了。这个内置的 __proto__ 是一个指向原型对象的指针,它会在创建一个新的引用类型对象时(显示或者隐式)自动创建,并挂载到新实例上。当我们尝试访问实例对象上的某一属性 / 方法时,如果实例对象上有该属性 / 方法时,就返回实例属性 / 方法,如果没有,就去 __proto__ 指向的原型对象上查找对应的属性 / 方法。这就是为什么我们尝试访问空对象的 toString 和 valueOf 等方法依旧能访问到的原因,JavaScript 正式以这种方式为基础来实现继承的。 构造函数 如果说实例的 __proto__ 只是一个指向原型对象的指针,那就说明在此之前原型对象就已经创建了,那么原型对象是什么时候被创建的呢?这就要引入构造函数的概念。 其实构造函数也就只是一个普通的函数而已,如果这个函数可以使用 new 关键字来创建它的实例对象,那么我们就把这种函数称为 构造函数。 // 普通函数 function person () {} // 构造函数,函数首字母通常大写 function Person () {} const person = new Person(); 原型对象正是在构造函数被声明时一同创建的。构造函数被申明时,原型对象也一同完成创建,然后挂载到构造函数的 prototype 属性上: 原型对象被创建时,会自动生成一个 constructor 属性,指向创建它的构造函数。这样它俩的关系就被紧密地关联起来了。 细心的话,你可能会发现,原型对象也有自己的 __proto__ ,这也不奇怪,毕竟万物皆对象嘛。原型对象的 proto 指向的是 Object.prototype。那么 Object.prototype.__proto__ 存不存在呢?其实是不存在的,打印的话会发现是 null 。这也证明了 Object 是 JavaScript 中数据类型的起源。 ...

October 1, 2024

Swift二进制兼容性

Swift二进制兼容性是指Swift编译后的二进制代码能够跨版本、跨模块正确链接和运行的能力。它由三个核心机制组成:ABI稳定(Swift 5.0)、模块稳定性(Swift 5.1)和Library Evolution。这三者共同使得Swift二进制框架的分发成为可能。 graph TD A[Swift二进制兼容性] --> B[ABI稳定Swift 5.0] A --> C[模块稳定性Swift 5.1] A --> D[Library Evolution] B --> B1[统一运行时固定调用约定/内存布局] C --> C1[.swiftinterface跨编译器版本导入模块] D --> D1[运行时布局查询库可独立于客户端更新] B1 --> E[二进制框架分发] C1 --> E D1 --> E ABI稳定 什么是ABI ABI(Application Binary Interface,应用程序二进制接口)定义了二进制层面的接口规范,包括: 数据类型的大小和对齐方式:如Int在64位系统上是8字节 函数调用约定:参数如何传递、返回值如何获取 名称修饰(Name Mangling)规则:函数和类型在二进制中的命名方式 内存布局:对象在内存中的结构 异常处理机制:错误如何在二进制层面传播 运行时元数据格式:类型信息的存储方式 ABI vs API 特性 API(源码接口) ABI(二进制接口) 层面 源代码层面 编译后的二进制层面 兼容性检查 编译时 链接时/运行时 关注点 函数签名、类型定义 内存布局、调用约定 变化影响 需要重新编译 需要重新链接或替换二进制 一个简单的例子: // API层面:函数签名 func calculate(value: Int) -> Int // ABI层面关注的是: // - Int在内存中占多少字节 // - value参数通过哪个寄存器传递 // - 返回值通过哪个寄存器返回 // - 函数在二进制中的符号名是什么 ABI不稳定时期的问题 在Swift 5.0之前,Swift的ABI是不稳定的。这意味着: ...

May 2, 2026