元素、组件、实例和节点

React 中的元素、组件、实例和节点,是React中关系密切的4个概念,也是很容易让React 初学者迷惑的4个概念。现在,老干部就来详细地介绍这4个概念,以及它们之间的联系和区别,满足喜欢咬文嚼字、刨根问底的同学(老干部就是其中一员)的好奇心。 元素 (Element) React 元素其实就是一个简单JavaScript对象,一个React 元素和界面上的一部分DOM对应,描述了这部分DOM的结构及渲染效果。一般我们通过JSX语法创建React 元素,例如: const element = <h1 className='greeting'>Hello, world</h1>; element是一个React 元素。在编译环节,JSX 语法会被编译成对React.createElement()的调用,从这个函数名上也可以看出,JSX语法返回的是一个React 元素。上面的例子编译后的结果为: const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); 最终,element的值是类似下面的一个简单JavaScript对象: const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } } React 元素可以分为两类:DOM类型的元素和组件类型的元素。DOM类型的元素使用像h1、div、p等DOM节点创建React 元素,前面的例子就是一个DOM类型的元素;组件类型的元素使用React 组件创建React 元素,例如: const buttonElement = <Button color='red'>OK</Button>; buttonElement就是一个组件类型的元素,它的值是: const buttonElement = { type: 'Button', props: { color: 'red', children: 'OK' } } 对于DOM类型的元素,因为和页面的DOM节点直接对应,所以React知道如何进行渲染。但是对于组件类型的元素,如buttonElement,React是无法直接知道应该把buttonElement渲染成哪种结构的页面DOM,这时就需要组件自身提供React能够识别的DOM节点信息,具体实现方式在介绍组件时会详细介绍。 有了React 元素,我们应该如何使用它呢?其实,绝大多数情况下,我们都不会直接使用React 元素,React 内部会自动根据React 元素,渲染出最终的页面DOM。更确切地说,React元素描述的是React虚拟DOM的结构,React会根据虚拟DOM渲染出页面的真实DOM。 组件 (Component) React 组件,应该是大家最熟悉的React中的概念。React通过组件的思想,将界面拆分成一个个可以复用的模块,每一个模块就是一个React 组件。一个React 应用由若干组件组合而成,一个复杂组件也可以由若干简单组件组合而成。 React组件和React元素关系密切,React组件最核心的作用是返回React元素。这里你也许会有疑问:React元素不应该是由React.createElement() 返回的吗?但React.createElement()的调用本身也是需要有“人”负责的,React组件正是这个“责任人”。React组件负责调用React.createElement(),返回React元素,供React内部将其渲染成最终的页面DOM。 既然组件的核心作用是返回React元素,那么最简单的组件就是一个返回React元素的函数: function Welcome(props) { return <h1>Hello, {props.name}</h1>; } Welcome是一个用函数定义的组件。如果使用类(class)定义组件,返回React元素的工作具体就由组件的render方法承担,例如: ...

December 17, 2024

浏览器的缓存机制

一、前言 缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。 对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。 接下来的内容中我们将通过缓存位置、缓存策略以及实际场景应用缓存策略来探讨浏览器缓存机制。 如需获取思维导图或想阅读更多优质文章请猛戳GitHub博客 二、缓存位置 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。 Service Worker Memory Cache Disk Cache Push Cache 1.Service Worker Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。 Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。 2.Memory Cache Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存 内存缓存中有一块重要的缓存资源是preloader相关指令(例如<link rel="prefetch">)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。 3.Disk Cache Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。 ...

December 17, 2024

类型判断

JS中的类型判断也是咱的老朋友了,如果要你说两句,那便是啥玩意typeof、instanceof的,张口就来,但你真的掌握了每一种类型判断,并熟知其原理吗?就比如说Object.prototype.toString.call(),当面试官问其中的 call有什么作用时,咱们又该如何游说呢。 typeof原理 所有的类型判断里当属typeof最为经典,其最为出名之处就是这玩意不顶用,typeof居然会将null判定为object,但这到底是什么原因?实际上,这就是JS这门编程语言的官方团队造出来的一个bug。 let s = '123' let n = 123 let f = true let u = undefined let nu = null let sy = Symbol(123) let big = 1234n let obj = {} let arr = [] let fn = function(){} let date = new Date(); console.log(typeof(s)); // string console.log(typeof(n)); // number console.log(typeof(f)) // boolean console.log(typeof(u)) // undifined console.log(typeof(sy)) // symbol console.log(typeof(big)) // bigint console.log(typeof(nu)); //将null判断为object //以下为引用类型,除了function,一律被判断为object console.log(typeof(obj)) //object console.log(typeof(arr)) //object console.log(typeof(date)) //object // 所有引用类型只能判断function console.log(typeof(fn)) //function // 原因就是:typeof会把所有传进去的值都转成二进制,而当年JS制定的规则便是:原始类型的被转为二进制的前面三个值绝对不为零,而typeof会把前三位为零的类型全部认定为对象,null这个类型又是JS语言后来引入的。遵循其他语言的原则,JS语言将null的二进制值定为一长串的0,因此typeof在判断时会将其认定为object。 因此,面试官一般不会询问判断方法 typeof 的原理。 手写一个instanceof 相较于typeof,instanceof有其优越之处,也有其不足之处,比如,instanceof只能判断引用类型,并且是通过原型链查找来判断类型。 let s = '123' let n = 123 let f = true let u = undefined let nu = null let sy = Symbol(123) let big = 1234n let obj = {} let arr = [] let fn = function(){} let date = new Date(); console.log(s instanceof String); // false instanceof 不能判断原始类型 // 以下为引用类型 console.log(obj instanceof Object) ; // true console.log(arr instanceof Object); // true console.log(fn instanceof Function); // true console.log(date instanceof Date); // true 首先要知道每个类型的原型都不相同。 ...

November 4, 2024

伪类和伪元素

什么是伪类和伪元素? 伪类:以冒号(:)开头,用于选择处于特定状态的元素。 伪元素:以双冒号(::)开头,用于在文档中插入虚构的元素。 这么说有点抽象,我们来看具体案例。例如下面的伪类语法表达的意思是:文档里的那些已经被用户访问过的 a 标签,当鼠标悬浮在它上面的时候,颜色为红色。 a:visited:hover { color: red; } 效果如下: 可以看到,无论是 visited还是 hover,都表示了 a 标签的某种状态,选择器始终选择的是伪类前面的元素,只不过当出现伪类描述的状态时,样式才生效而已。 注意,上面使用的是无痕浏览器打开的页面,是不可能出现 visited 状态的,因为无痕不记录访问历史,我们可以点击控制台的 :hov 图标手动勾选 :visited 和 :hover 来模拟 a 标签「已访问」和「悬浮」两种状态。 我们再来看一下伪元素的案例,下面的语法表达的意思是: div 标签里面首字的颜色为红色。 div::first-letter { color: red; } 效果如下: 与伪类不同的是,首字并不是 div 元素的某种状态,而是浏览器创建出来的一个虚拟的元素,我们可以在上图的右下角看到 Pseudo ::first-letter element提示,相当于浏览器自动选中了「彻」字。 注意:::first-letter 伪元素只作用于块状元素上面,如果把 div 换成 span,就无法选中首字了,除非手动设置 span 的 display 属性为 block 或 inline-block 等块状值。 从上面的案例当中,我们可以得出两个结论: 伪类用于向某些已经存在的选择器添加特殊效果(当状态改变时) 伪元素用于将特殊效果添加到不存在的虚拟元素中(浏览器自动创建) 也就是说伪类的本质还是类(class),作用于标签本身,只不过限定了状态条件;而伪元素的本质是元素(element),作用于该虚拟元素的内容本身。 伪类有哪些? 按照功能,可划分为以下几类: 动态伪类::visited、:focus、:hover等 状态伪类::disabled、:empty、:required 等 结构伪类::first-child、:nth-of-type等 其他伪类::target、:lang、:not()等 下面的表格详细记录了各种伪类及其描述: 伪类 描述 兼容性 :active 元素处于活动状态时 ✅ :focus 元素已获取焦点时 ✅ :hover 元素处于悬浮状态时 ✅ :link 链接未访问时 ✅ :visited 链接已访问时 ✅ :first-child 元素是首个子元素时 ✅ :last-child 元素是最后一个子元素时 ✅ :nth-child() 元素是第 n 个子元素时 ✅ :nth-last-child() 元素是倒数第 n 个子元素时 ✅ :only-child 元素是唯一子元素时 ✅ :first-of-type 元素是首个特定类型的子元素时 ✅ :last-of-type 元素是最后一个特定类型的子元素时 ✅ :nth-of-type() 元素是第 n 个特定类型的子元素时 ✅ :nth-last-of-type() 元素是倒数第 n 个特定类型的子元素时 ✅ :only-of-type 元素是唯一的特定类型的子元素时 ✅ :not 不满足指定条件时 ✅ :target 元素 id 匹配到哈希值时 ✅ :root 元素是文档树的根元素时 ✅ :lang() 匹配到指定语言时 ✅ :empty 元素处于没有子元素状态时 ✅ :invalid 和 :valid 表单项是否有效 ✅ :required 和 :optional 表单项是否必填 ✅ :in-range和 :out-of-range 表单项是否超出范围 ✅ :read-only和 :read-write 表单项是否只读 ✅ :enabled和 :disabled 表单项是否禁用 ✅ :fullscreen 当前处于全屏显示模式时 ⚠️ :blank 输入框处于输入为空状态时 ❌ :dir() 匹配到特定文字书写方向时 ❌ 上表中的兼容性表示: ...

October 8, 2024

Webpack与Vite

前言 vite比webpack快? vite比webpack简单? 今天我们就来分析一下,到底有什么不一样的地方! 定位 对比之前,我们先要搞懂,vite与webpack的定位以及关系才可以。 那前端社区中常谈到的这些工具webpack、rollup、parcel、esbuild、vite、vue-cli、create-react-app、umi他们之间的关系是怎样的。 webpack、rollup、parcel、esbuild都是打包工具,代码写好之后,我们需要对代码进行压缩、合并、转换、分割、打包等操作,这些工作需要打包工具去完成。 vue-cli、create-react-app、umi 是基于webpack的上层封装,通过简单的配置就可以快速创建出一个项目,把更多的时间放在业务开发上。 vite开发环境依赖esbuild进行预构建,生产环境则依赖rollup进行打包,并且充分利用了现代浏览器的特性,比如http2、ES module,vite是站在众多巨人肩膀上的一个产物, 类似webpack + webpack-dev-server的结合体,是一个非常棒的前端项目的构建工具。 运行原理 首先,我们从运行原理上分析一下,vite为什么比webpack快。 webpack运行原理 当我们使用webpack启动项目时,webpack会根据我们配置文件(webpack.config.js) 中的入口文件(entry),分析出项目项目所有依赖关系,然后打包成一个文件(bundle.js),交给浏览器去加载渲染。 这样就会带来一个问题,项目越大,需要打包的东西越多,启动时间越长。 关于ES module 在讲vite运行原理之前,我们先说一下ES module 目前,绝大多数现代浏览器都已经支持ES module了, 我们只需要在<script>标签中添加type="module",就可以使用ES module了。 下面这段代码是可以直接在浏览器中运行的。 // test.js export default function hello() { console.log('hello world'); } // index.html <script type="module"> import hello from './test.js'; hello(); // hello world </scirpt> vite运行原理 在<script type="module">中,浏览器遇到内部的import引用时,会自动发起http请求,去加载对应的模块。 vite也正是利用了ES module这个特性,使用vite运行项目时,首先会用esbuild进行预构建,将所有模块转换为es module,不需要对我们整个项目进行编译打包,而是在浏览器需要加载某个模块时,拦截浏览器发出的请求,根据请求进行按需编译,然后返回给浏览器。 这样一来,首次启动项目(冷启动)时,自然也就比webpack快很多了,并且项目大小对vite启动速度的影响也很小。 构建方式 我们再来看一下,vite与webpack在项目构建上有哪些区别。 webpack webpack是基于nodejs运行的,但js只能单线程运行,无法利用多核CPU的优势,当项目越来越大时,构建速度也就越来越慢了。 vite vite预构建与按需编译的过程,都是使用esbuild完成的。 esbuild是用go语言编写的,可以充分利用多核CPU的优势,所以vite开发环境下的预构建与按需编译速度,都是非常快的。 http2 vite充分利用了http2可以并发请求的优势,这也是速度快的一个主要原因。 接下来,我们了解一下http2的来龙去脉。 在之前http1的时候,浏览器对同一个域名的请求,是有并发限制的,一般为6个,如果并发请求6个以上,就会造成阻塞问题,所以在http1的时代,我们要减少打包产物的文件数量,减少并发请求,来提高项目的加载速度。 2015年以后,http2出现了,他可以并发发送多个请求,不会出现http1的并发限制。这时候,将打包产物分成多个小模块,并行去加载,反而会更快。 vite也充分利用了这一优势,对项目资源进行了合理的拆分,访问项目时,同时加载多个模块,来提升项目访问速度。 热更新 vite速度快的另一个原因是与webpack不同的热更新机制。 ...

October 5, 2024

数据绑定

1. 定义 1.1 双向绑定 & 单向绑定 vue的双向绑定,即数据与视图的响应式设计。具体表现为:View的改变能实时让Model发生变化,而Model的变化也能实时更新View。 什么情况下用户可以更新View呢?如:填写表单。当用户填写表单时,View的状态就被更新了,如果此时MVVM框架可以自动更新Model的状态,那就相当于把Model和View做了双向绑定。 注意区别:单向数据绑定,所有数据只有一份,一旦数据变化,就去更新页面(只有data–>DOM,没有DOM–>data)。 用户在页面作出更新,需要用户手动收集(双向绑定是自动收集),在合并到原有的数据中。 Vue.js中的v-model主要用在表单的input输入框,完成视图和数据的双向绑定: <!DOCTYPE html> <html> <head></head> <body> <div id="app"> <input type="text" v-model="message"> <p>{{message}}</p> </div> <script> var app = new Vue({ el: '#app', data: { message: '' } }); </script> </body> </html> 1.2 v-model来添加双向绑定 <input v-model="xxx"> <!-- 上面的代码等价于 --> <input :value="xxx" @input="xxx = $event.target.value"> <!-- 双向绑定 = 单向绑定 + UI事件监听 --> 1.3 双向绑定、单向绑定的优缺点 单向绑定:数据流也是单向的,对于复杂应用来说是实施统一状态管理(如redux)的前提。 双向绑定:在一些需要实时反应用户输入的场合会非常方便(如多级联动菜单)。但常认为复杂应用中这种便利比不上引入状态管理带来的优势。因为不知道状态什么时候发生改变,是谁造成的改变,数据变更也不会通知。 响应式的思路:mvvm Model,View,View-Model 2. 原理概述 Vue 数据双向绑定原理是通过 数据劫持 + 发布者-订阅者模式 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。详见 vue源码。 ...

October 4, 2024

CSRF攻击

CSRF是什么 CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求,利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。 一个典型的CSRF攻击有着如下的流程: 受害者登录a.com,并保留了登录凭证(Cookie)。 攻击者引诱受害者访问了b.com。 b.com 向 a.com 发送请求。 a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。 a.com以受害者的名义执行了这个请求。 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。 CSRF的特点 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用” 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。 如何进行预防 CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。 防止csrf常用方案如下: 阻止不明外域的访问 同源检测 Samesite Cookie 提交时要求附加本域才能获取的信息 CSRF Token 双重Cookie验证 同源检测 Cookie的同源和浏览器的同源策略有所区别: 浏览器同源策略:协议、域名和端口都相同即同源; Cookie同源策略:域名相同即同源; 在HTTP协议中,每个异步请求都会携带两个header,用来标记来源域名: Origin Header Referer Header 这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端修改,服务器接收到后可以根据这两个Header确定来源的域名; 综上所述: 同源验证是一个相对简单的防范方法,能够防范绝大多数的CSRF攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。 Samesite Cookie属性 在Chrome 51版本后,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击。 Cookie的Samesite属性用来限制第三方Cookie, 从而减少安全风险,它有三个值: Set-Cookie: SameSite = Strict; Set-Cookie: SameSite = Lax; Set-Cookie: SameSite = None; Strict: 最为严格,完全禁止第三方Cookie, 跨站点时,任何情况都不发送Cookie; Lax: 限制稍微宽松,大多数情况下时不发送第三方Cookie的,除了a链接、预加载请求和GET表单; None: 关闭SameSite属性,但必须同时设置Secure属性, ...

October 2, 2024

模版字符串

未见过的函数用法: function fun(strings,params1){ console.log(strings); console.log(params1) } fun`params1 = ${333}` // 输出分别为 // ['params1 = ', ''] // 333 不知道各位在学习或开发中有没有遇到过这样的函数调用方式呢?秉承着我没遇到的东西就要发文章来分享分享的原则,经过我查阅资料并加以学习,就让本文来带大家了解一下JS中的标签函数吧。 模板字符串 标签函数的主要作用就是处理模板字符串,所以在了解标签函数之前先了解一下模板字符串(模板字面量)吧。 基本用法 模板字符串是ES6新增的使用模板字面量定义字符串的能力,用反引号```````` 进行分隔。与使用''或""的定义的字符串字面量不同是模板字面量保留换行字符,可以跨行定义字符串。 例如: let str1 = 'xxhh\n2233' let str2 = `xxhh 2233` console.log(str1 === str2) // 结果为true 要注意的是模板字符串中的所有字符都会包含在字符串中,特别在使用缩进的时候,缩进的空格都会包含在字符串中。 let str = ` <div> <h1>xxhh</h1> </div> ` console.log(str.length) // 结果为44,包含了缩进所产出的所有空格 字符串插值 在没有模板字符串时,我们通常会使用+加法运算符来进行字符串的拼接,但有了模板字符串后我们并可通过插值占位符${}来将各字面量、变量或表达式结果来进行拼接,这样可以更容易地创建复杂和动态的字符串,同时保持代码的可读性和简洁性。这是模板字符串最常用的一个特性。 let name = 'xxhh'; let str1 = "name:"+name; let str2 = `name:${name}`; console.log(str1,str2) // name:xxhh name:xxhh console.log(str1 === str2) // true 要注意的是使用字符串插值的所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript 表达式都可以用于插值。 ...

October 2, 2024

DOCTYPE

DOCTYPE 是一个声明,位于 HTML 文档的最顶部,指示浏览器使用哪种 HTML 或 XHTML 版本来渲染页面。它的主要作用是确保浏览器以标准模式解析文档,从而避免不同浏览器间的渲染差异。正确使用 DOCTYPE 可以提升页面的兼容性和可访问性。 <!DOCTYPE>声明应该位于HTML文档的第一行,紧接着是<html>标签。 例如,对于 HTML5 文档,你只需写: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document Title</title> </head> <body> <!-- 页面内容 --> </body> </html> 这个声明告诉浏览器该文档是一个HTML5文档。HTML5是最新的HTML标准,它包括了许多现代化的功能,如语义元素、表单控件、音频和视频等。 DOCTYPE 的作用有以下几个方面: 指定 HTML 版本:浏览器根据 DOCTYPE 的声明来确定使用哪个 HTML 版本来渲染页面,从而保证页面在不同的浏览器上显示一致性。 触发标准模式:在 HTML 中,如果省略了 DOCTYPE 声明,浏览器会进入混杂模式(Quirks mode),这种模式下浏览器的渲染方式与早期的浏览器相同,可能导致页面的显示出现不可预测的错误。而指定了 DOCTYPE 声明,则会触发标准模式(Standards mode),使得浏览器按照 HTML 规范的要求进行页面渲染,从而保证页面的稳定性和可靠性。 供浏览器和开发人员参考:DOCTYPE 声明还包含了有关 HTML 文档的元信息,例如所使用的 DTD(文档类型定义),以及其他的元数据信息,这些信息可以供浏览器和开发人员参考,帮助开发人员更好地了解和掌握 HTML 语言的特点和规范。 提高网页加载速度:指定 DOCTYPE 声明可以帮助浏览器更快地加载网页,因为浏览器知道要使用哪种渲染模式,从而更快地解析 HTML 文档。 避免代码错误:指定 DOCTYPE 声明可以帮助开发人员在编写 HTML 代码时遵循标准,避免一些常见的代码错误,例如忘记关闭标签或者使用非法的属性和元素等等。 除了HTML5的<!DOCTYPE>声明之外,还有一些其他版本的HTML和XHTML文档类型声明。这里是一些例子: 1. HTML 4.01 Strict: ...

September 24, 2024

Claude Code 源码导读:Agent 设计详解

Claude Code 的 agent 到底是怎么被设计成一个能长时间工作、能分派子 agent、能记忆、能压缩上下文、也能可靠停下来的系统的? Claude Code 最值得研究的地方,不是它“能调用工具”,而是它把模型、工具、记忆、上下文、后台任务、hook 和权限系统编成了一个稳定的 agent harness。 一句话概括:Claude Code 的 agent 不是一个模型循环,而是三层循环叠在一起:主 query 循环负责推理和工具回灌;工具循环负责受控执行和并发;任务循环负责子 agent、后台 agent、记忆整理和长任务生命周期。 flowchart TD User["用户输入 / 队列消息"] --> Query["query.ts 主循环"] Query --> Model["流式模型调用"] Model -->|没有 tool_use| StopPath["停止路径stop hooks / token budget / completed"] Model -->|产生 tool_use| ToolLoop["工具执行循环"] ToolLoop --> Attach["附件注入记忆 / 变更文件 / 任务通知 / 技能"] Attach --> Query Query --> Compact["上下文治理tool budget / microcompact / autocompact / reactive compact"] Query --> Memory["记忆系统CLAUDE.md / AutoMem / Session Memory"] Query --> AgentTool["AgentTool"] AgentTool --> SubAgent["runAgent 子循环"] SubAgent --> LocalTask["LocalAgentTask前台 / 后台 / 可恢复"] LocalTask --> Attach 一、核心源码地图 先把关键文件放在桌面上: ...

June 21, 2026