Web Workers

在浏览器和服务器上,JavaScript程序在单个处理线程上运行。这意味着程序一次只能做一件事。简单来说,你的新电脑可能有32核的CPU,但当JavaScript应用程序运行时,其中31个核心都处于空闲状态。 JavaScript的单线程避免了复杂的并发情况。如果两个线程同时尝试进行不兼容的更改会发生什么?例如,一个浏览器可能在另一个线程重定向到新的URL并从内存中清除该文档时正在更新DOM。Node.js、Deno和Bun都继承了浏览器的单线程引擎。 这不是JavaScript特定的限制。大多数语言都是单线程的,但像PHP和Python这样的Web选项通常在Web服务器上运行,为每个用户请求启动解释器的新线程的不同实例。这会消耗资源,因此Node.js应用程序通常定义自己的Web服务器,该服务器在单个线程上运行,并异步处理每个传入的请求。 Node.js的方法在处理高流量负载时可能更高效,但长时间运行的JavaScript函数会消耗效率收益。 在我们演示如何使用Web Workers解决执行速度问题之前,我们首先要了解JavaScript的运行方式以及为什么长时间运行的函数会引发问题。 JavaScript非阻塞I/O事件循环 你可能会认为一次只做一件事会导致性能瓶颈,但JavaScript是异步的,这就避免了大部分单线程处理问题,因为: 不需要等待用户在网页上点击按钮。 浏览器触发一个事件,当点击发生时调用JavaScript函数。 不需要等待Ajax请求的响应。 浏览器触发一个事件,在服务器返回数据时调用JavaScript函数。 Node.js应用程序不需要等待数据库查询的结果。 运行时在数据可用时调用JavaScript函数。 JavaScript引擎运行一个事件循环。一旦代码的最后一条语句执行完毕,运行时会循环回来,检查是否有未完成的定时器、待处理的回调函数和数据连接,然后根据需要执行回调函数。 其他操作系统处理线程负责调用诸如HTTP请求、文件处理和数据库连接等输入/输出系统。它们不会阻塞事件循环。事件循环可以继续执行下一个在队列中等待的JavaScript函数。 实质上,JavaScript引擎的主要责任是运行JavaScript代码。操作系统处理所有其他可能导致引擎调用JavaScript函数的I/O操作。 长时间运行的JavaScript函数 JavaScript函数通常由事件触发。它们会进行一些处理,输出一些数据,并且大部分情况下会在毫秒级别内完成,以便事件循环可以继续运行。 不幸的是,一些长时间运行的函数可能会阻塞事件循环。想象一下,你正在开发自己的图像处理函数(如锐化、模糊、灰度化等)。异步代码可以从文件中读取(或写入)数百万字节的像素数据,这对JavaScript引擎影响很小。然而,处理图像的JavaScript代码可能需要几秒钟来计算每个像素。该函数会阻塞事件循环,直到它完成,其他JavaScript代码无法运行。 在浏览器中,用户将无法与页面进行交互。他们无法点击、滚动或输入,并且可能会看到一个“脚本无响应”的错误,其中有停止处理的选项。 对于Node.js服务器应用程序而言,情况更糟糕。在函数执行时,它无法响应其他请求。如果函数需要花费10秒钟来完成,那么在那个时间点访问的每个用户都必须等待最多10秒钟,即使他们没有处理图像。 你可以通过将计算拆分为较小的子任务来解决这个问题。下面的代码使用传递的imageFn函数,最多处理1,000个像素(从一个数组中)。然后,它使用1毫秒的setTimeout延迟调用自身。事件循环的阻塞时间较短,这样JavaScript引擎可以在迭代之间处理其他传入的事件: // pass callback function, image processing function, and input image // return an output image to a callback function function processImage( callback, imageFn = i => {}, imageIn = [] ) { const chunkSize = 1000; // pixels to process per iteration // process first chunk let imageOut = [], pointer = 0; processChunk(); // process a chunk of data function processChunk() { const pointerEnd = pointer + chunkSize; // pass chunk to image processing function imageOut = imageOut.concat( imageFn( imageIn.slice( pointer, pointerEnd ) ) ); if (pointerEnd < imageIn.length) { // process next chunk after a short delay pointer = pointerEnd; setTimeout(processChunk, 1); } else if (callback) { // complete - return output image to callback function callback( null, imageOut ); } } } 这种方法可以防止脚本无响应,但不总是切实可行的。单个执行线程仍然会执行所有的工作,即使CPU可能有更多的处理能力。为了解决这个问题,我们可以使用Web Workers。 ...

October 16, 2024

动画

前言 CSS中有几个特别相近的几个属性。他们是transition,transform,animation。可能还有人对translate是什么也不清楚,那我们先给个简单的介绍。 属性 含义 transition 过渡 用于设置元素的样式过度,和animation有着类似的效果,但细节上有很大的不同 transform 变形 用于元素进行旋转、缩放、移动或倾斜,和设置样式的动画并没有什么关系,就相当于color一样用来设置元素的“外表” animation 动画 css动画属性 translate 移动 只是transform的一个属性值,即移动。 transition 过渡 字面意思上来讲,就是元素从这个属性(color)的某个值(red)过渡到这个属性(color)的另外一个值(green),这是一个状态的转变,需要一种条件来触发这种转变,比如我们平时用到的:hoever、:focus、:checked、媒体查询或者JavaScript。 一条transition规则,是可以定义多个属性的变化的,中间用逗号隔开,如果是全部属性变化则用“all”即可。 语法:transition: property duration timing-function delay; 值 描述 transition-property 规定设置过渡效果的 CSS 属性的名称 transition-duration 规定完成过渡效果需要多少秒或毫秒 transition-timing-function 规定速度效果的速度曲线 transition-delay 定义过渡效果何时开始 我发现简写时候的顺序并不一定就是写死的,经过测试在chrome上只要delay在duration后面即可。当然按照标准写是最好的。 Demo <!DOCTYPE html> <html lang="en"> <head> <title>transition</title> <style> #box { height: 100px; width: 100px; background: green; transition: transform 1s ease-in 1s; } #box:hover { transform: rotate(180deg) scale(.5, .5); } </style> </head> <body> <div id="box"></div> </body> </html> 效果 ...

October 8, 2024

$nextTick

nextTick 是 Vue 的一个核心实现,$nextTick方法将回调延迟到下次DOM更新循环之后执行。Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。 nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列 前置知识 - js事件循环 JS 运行机制 JS 执行是单线程的,基于事件循环。事件循环大致分为以下步骤: 所有同步任务都在主线程上执行,形成一个执行栈。 异步任务放进任务队列,异步任务分为宏任务和微任务 执行栈所有同步任务执行完成,就会执行任务队列。对应的异步任务,结束等待状态,进入执行栈,开始执行。 主线程不断重复上面的第三步。 主线程的执行过程就是一个 tick,而所有的异步结果都是通过 “任务队列” 来调度。 消息队列中存放的是一个个的任务(task)。 task 分为两大类,分别是 macro task 和 micro task,并且每个 macro task(宏任务) 结束后,都要清空所有的 micro task(微任务)。 如图所示 for (macroTask of macroTaskQueue) { // 执行宏任务 handleMacroTask(); // 执行所有微任务 for (microTask of microTaskQueue) { handleMicroTask(microTask); } } 宏任务: script、setTimeout、setInterval、Node中的setImmediate 等 微任务: Promise.then、MutationObserver、Node 中的 Process.nextTick等 ...

October 5, 2024

数组扩展

ES6(ECMAScript 2015)为JavaScript数组引入了多项扩展功能,这些功能不仅简化了数组操作,还提高了代码的可读性和效率。本文将详细介绍ES6数组扩展的主要功能和用法。 一、扩展运算符(…) 扩展运算符可以用于数组的赋值、合并、解构赋值以及将字符串转为数组。 赋值数组: let arr1 = [100, 200, 300, [500]]; let arr2 = [...arr1]; // 浅拷贝 console.log(arr2 == arr1); // false arr1[0] = 400; console.log(arr2); // 不受影响 arr1[3][0] = 600; console.log(arr2); // 受影响(只拷贝了一层) 合并数组: console.log([...[1, 2, 3], ...[4, 5, 6]]); // [1, 2, 3, 4, 5, 6] 解构赋值: let [a, ...list] = [1, ...["a", "b"]]; console.log(a); // 1 console.log(list); // ["a", "b"] 将字符串转为数组: console.log([..."str"]); // ["s", "t", "r"] 二、Array.from() Array.from()方法用于将类数组对象或可迭代对象转换为真正的数组。 let arrLike = { "0": "内容", "1": "内容2", "2": "内容3", length: 3 }; console.log(Array.from(arrLike)); // ["内容", "内容2", "内容3"] 此外,Array.from()还可以接受一个回调函数,用于对数组中的每个元素进行处理。 let arr = Array.from({ length: 2 }, () => "jack"); console.log(arr); // ["jack", "jack"] 三、Array.of() Array.of()方法用于将一组值转换为数组,与Array构造函数的行为不同,Array.of()不会把一个参数作为数组的长度。 ...

October 2, 2024

垃圾回收

写在前面 我们知道垃圾回收机制是引擎来做的,JS引擎有很多种(各个浏览器都不同),其垃圾回收机制在一些细节及优化上略有不同,本文我们以一些通用的回收算法作为切入,再由 V8 引擎发展至今对该机制的优化为例(为什么以 V8 为例?因为它市场占有率大 😄 ),一步一步深入来助我们了解垃圾回收机制,因为只有真正了解垃圾回收机制,后面才能理解内存泄漏的问题以及手动预防和优化 JavaScript 是门魅力无限的语言,关于它的 GC(垃圾回收)方面,你了解多少呢?想来大部分人是因为面试才去看一些面试题从而了解的垃圾回收,那在正式开始之前,给大家列几个小问题,大家可以先想一下答案,带着问题及答案再去看文章,最后读完此文如果你的答案可以优化,即有收获 什么是垃圾回收机制? 垃圾是怎样产生的? 为什么要进行垃圾回收? 垃圾回收是怎样进行的? V8 引擎对垃圾回收进行了哪些优化? 当然,我们可不仅仅是为了面试,其目的是一次性彻底搞懂 GC! GC是什么 GC 即 Garbage Collection ,程序工作过程中会产生很多 垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而 GC 就是负责回收垃圾的,因为他工作在引擎内部,所以对于我们前端来说,GC 过程是相对比较无感的,这一套引擎执行而对我们又相对无感的操作也就是常说的 垃圾回收机制 了 当然也不是所有语言都有 GC,一般的高级语言里面会自带 GC,比如 Java、Python、JavaScript 等,也有无 GC 的语言,比如 C、C++ 等,那这种就需要我们程序员手动管理内存了,相对比较麻烦 垃圾产生&为何回收 我们知道写代码时创建一个基本类型、对象、函数……都是需要占用内存的,但是我们并不关注这些,因为这是引擎为我们分配的,我们不需要显式手动的去分配内存 但是,你有没有想过,当我们不再需要某个东西时会发生什么?JavaScript 引擎又是如何发现并清理它的呢? 我们举个简单的例子 let test = { name: "isboyjc" }; test = [1,2,3,4,5] 如上所示,我们假设它是一个完整的程序代码 我们知道 JavaScript 的引用数据类型是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用,所以,JavaScript 中对引用数据类型的操作都是操作对象的引用而不是实际的对象。可以简单理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的 那上面代码首先我们声明了一个变量 test,它引用了对象 {name: 'isboyjc'},接着我们把这个变量重新赋值了一个数组对象,也就变成了该变量引用了一个数组,那么之前的对象引用关系就没有了,如下图 没有了引用关系,也就是无用的对象,这个时候假如任由它搁置,一个两个还好,多了的话内存也会受不了,所以就需要被清理(回收) 用官方一点的话说,程序的运行需要内存,只要程序提出要求,操作系统或者运行时就必须提供内存,那么对于持续运行的服务进程,必须要及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则就会导致进程崩溃 垃圾回收策略 在 JavaScript 内存管理中有一个概念叫做 可达性,就是那些以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需回收 至于如何回收,其实就是怎样发现这些不可达的对象(垃圾)它并给予清理的问题, JavaScript 垃圾回收机制的原理说白了也就是定期找出那些不再用到的内存(变量),然后释放其内存 ...

October 1, 2024

iOS中的数据库

数据持久化是移动端开发的核心能力之一。iOS提供了从轻量级KV存储到完整关系数据库的多层次方案。本文将从底层存储原理出发,系统性地梳理iOS中各类数据库与持久化技术的设计思想、实现机制和适用场景。 一、iOS数据持久化方案全景 iOS中常见的数据持久化方案,按数据复杂度和使用场景可分为以下几层: graph TD subgraph KV["轻量级KV存储"] A["UserDefaultsplist序列化"] B["Keychain加密存储"] C["MMKVmmap + protobuf"] end subgraph FILE["文件存储"] D["plist / JSON"] E["NSCoding / Codable归档"] end subgraph SQL["关系数据库"] F["SQLiteC语言API"] G["FMDBOC封装"] H["WCDBORM + 加密"] end subgraph ORM["ORM框架"] I["Core DataApple官方"] J["Realm零拷贝"] K["SwiftDataSwift原生"] end KV --> |"数据结构更复杂"| FILE FILE --> |"需要结构化查询"| SQL SQL --> |"需要对象映射"| ORM 方案 数据模型 查询能力 线程安全 加密 适用场景 UserDefaults KV (plist类型) Key查找 线程安全 无 用户偏好、简单配置 Keychain KV (Data) Key查找 线程安全 系统加密 密码、Token、证书 MMKV KV (protobuf) Key查找 线程安全 可选AES 高频读写的KV数据 plist/JSON文件 字典/数组 无 不安全 无 静态配置、缓存 NSCoding/Codable 对象图 无 不安全 无 对象序列化 SQLite 关系表 SQL 需手动处理 可选(SQLCipher) 结构化数据、复杂查询 FMDB 关系表 SQL FMDatabaseQueue 可选 SQLite的OC封装 WCDB 关系表+ORM SQL+ORM 自动管理 内置 高性能结构化存储 Core Data 对象图 NSPredicate NSManagedObjectContext 无 复杂对象关系 Realm 对象 类型安全查询 对象冻结 可选AES 高性能对象存储 SwiftData Swift对象 #Predicate宏 ModelContext 无 Swift原生数据持久化 二、轻量级KV存储 2.1 UserDefaults UserDefaults是iOS最常用的轻量级存储方案,底层将数据以plist格式序列化到磁盘。 ...

May 2, 2026

OpenClaw 源码导读(一):架构总览 — 为"单个主人"而设计的 AI Gateway

2025 年 11 月,Peter Steinberger(PSPDFKit 创始人、前著名 iOS 社区人物)把自己折腾出来的个人 AI 助手 Molty 开源为 OpenClaw。短短几个月,仓库吃到 35 万+ star、93 个 release、360 多个 contributor,核心代码量在 TypeScript/Swift/Kotlin/Go/Python 之间横跨 40 多万行。它和 Claude Code、Codex CLI 乍看都是"跑在本地的 CLI Agent",但设计哲学完全不同——Claude Code 是一个编程助手,OpenClaw 是一个长在你设备上的私人秘书:它挂在 WhatsApp/Telegram/iMessage/微信 的 IM 客户端后面,能自己发消息、自己开浏览器、自己调用 iOS/Android 上的摄像头和麦克风。 本文是 OpenClaw 源码导读系列的第一篇,目标是把整个项目的"地图"摊开。先讲清楚它要解决什么问题、在怎样的信任模型下运行,再把仓库 100 多个顶层模块拎出来分类,最后给出后续系列文章的导航。 一、它到底是什么 1. 一句话定义 OpenClaw 官方给自己的定位是 “Personal AI Assistant. Any OS. Any Platform. The lobster way.” 翻译过来就是:一个在你自己设备上运行、从你自己现有的聊天工具里和你对话的个人 AI 助手。 这个定位里藏着三个关键差异: Personal:它不是多租户 SaaS,也不是团队协作工具,而是单一主人的私人助手。整个信任模型就是为"只有一个 operator"优化的。 Any OS / Any Platform:Gateway 是 Node 进程,可以跑在 macOS/Linux/Windows(WSL2)、甚至 Fly.io/Docker/NAS 上;客户端包含 iOS/Android/macOS 原生 App,还有 Web Dashboard。 The lobster way:作者把它拟人化成一只太空龙虾 Molty,这是一个品牌/吉祥物层面的设计,但也反映了项目的"玩心"。 2. 和 Claude Code 对比:两个相似却完全不同的 CLI Agent 由于作者 Steipete 本身是 Claude Code 的重度用户和 Anthropic 的合作者,社区最常问的问题就是"和 Claude Code 有啥区别"。这里先画一张对比表: ...

May 2, 2026

http2.0 服务端推送

HTTP/2.0 的 服务端推送(Server Push) 是它相较于 HTTP/1.x 引入的一项重要功能,旨在优化网页加载性能,尤其是首次加载时资源依赖的获取效率。 一、什么是服务端推送? HTTP/2 Server Push 允许服务器主动将资源“推送”给客户端,而不是等待客户端明确请求。这在某些场景下可以预加载依赖资源、减少请求延迟,提升页面的首屏加载速度。 例如: 客户端请求了 HTML 页面,服务器可以在返回 HTML 的同时,主动推送页面所需的 CSS/JS 资源。 二、服务端推送的工作原理 流程概览: 浏览器发送主资源请求(如 HTML); 服务器识别该请求需要哪些依赖资源(如 CSS、JS); 服务器将这些资源 打包为 PUSH_PROMISE 帧 发送给客户端,声明它即将发送的资源; 客户端接收到 PUSH_PROMISE 后,会缓存该资源的响应; 当浏览器稍后真正需要这个资源时,不会再发起真实请求,而是从缓存中获取推送的内容; 避免了请求的 RTT 往返延迟。 技术细节: PUSH_PROMISE 是 HTTP/2 中新增的帧类型,用于声明服务器准备推送的资源; 所有推送的资源都绑定到一个主请求流(主页面),不能独立存在; 客户端可选择 拒绝(RST_STREAM) 不想要的推送资源; 推送资源会根据缓存策略存储在浏览器的缓存中,不一定立即使用。 三、实际示例(Nginx + Link Header) 在使用 Nginx 部署 HTTP/2 服务时,可以通过 Link 响应头启用 Server Push: location = /index.html { http2_push /style.css; http2_push /main.js; } 或者使用 Link 响应头方式: ...

July 28, 2025

错误边界

人无完人,代码总会出错,出错并不可怕,关键是怎么处理。 我就想问问大家react的应用的错误怎么捕捉呢? 这个时候: 小白+++:怎么处理? 小白++: ErrorBoundary 小白+: ErrorBoundary, try catch 小黑#: ErrorBoundary, try catch, window.onerror 小黑##: 这个是个严肃的问题,我知道N种处理方式,你有什么更好的方案? ErrorBoundary EerrorBoundary是16版本出来的,有人问那我的15版本呢,我不听我不听,反正我用16,当然15有unstable_handleError。 关于ErrorBoundary官网介绍比较详细,这个不是重点,重点是他能捕捉哪些异常。 子组件的渲染 生命周期函数 构造函数 class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } } <ErrorBoundary> <MyWidget /> </ErrorBoundary> 开源世界就是好,早有大神封装了react-error-boundary 这种优秀的库。 你只需要关心出现错误后需要关心什么,还以来个 Reset, 完美。 ...

December 17, 2024

地理定位

HTML5 提供了地理定位(Geolocation)API,用于获取用户的当前位置。通过这一功能,网页可以访问用户的地理位置信息,进而实现如地图服务、定位推荐、基于位置的广告等功能。该 API 主要依赖设备的 GPS、Wi-Fi、IP 地址、蓝牙等信息来确定位置。 Geolocation API 的基本用法 主要通过 navigator.geolocation 对象来访问地理位置信息,提供了以下三个方法: getCurrentPosition(success, error, options) 获取用户的当前位置,一次性返回位置信息。 watchPosition(success, error, options) 持续监听用户的位置变化,并且实时返回新的位置信息。 clearWatch(id) 取消 watchPosition 监听的位置变化。 方法的参数说明 success(position):成功获取位置时的回调函数。position 对象包含位置信息,如经度、纬度、速度等。 error(error):可选参数,获取位置失败时的回调函数。error 对象包含错误信息,如用户拒绝提供位置或获取超时等。 options:可选参数,包含配置选项,影响获取位置的精度、超时时间等。 使用示例 if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const latitude = position.coords.latitude; const longitude = position.coords.longitude; console.log(`Latitude: ${latitude}, Longitude: ${longitude}`); }, (error) => { console.error(`Error Code = ${error.code}: ${error.message}`); }, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); } else { console.log("Geolocation is not supported by this browser."); } 获取到的 position 对象 position 对象包含用户的位置信息,主要属性包括: ...

October 16, 2024