《WebKit 技术内幕》学习之九(4): JavaScript引擎
4 实践——高效的JavaScript代码
4.1 编程方式
关于如何使用JavaScript语言来编写高效的代码,有很多铺天盖地的经验分享,以及很多特别好的建议,读者可以搜索相关的词条,就能获得一些你可能需要的结果。同时,本节希望结合前面介绍的各种引擎内部的技术,按照特定的类别为读者归纳一些方式和方法,让我们从以下几个方面来解读它们。
- 类型 。因为JavaScript的类型是在动态时候确定的,这给引擎带来很大的问题。同时对于某个函数来说,V8和JavaScriptCore都使用了隐藏类和内嵌缓存技术来加速对象和属性的访问,所以对于该函数只是使用某个类型的对象或者较少类型,以此减少缓存失误的机率从而提高性能。同时,对于数组,尽量使用存放相同类型的数据,这样可以通过偏移位置来访问它们。在目前的众多技术中,比较突出的就是asm.js,它主要是在JavaScript中显示标记一些类型,这样可以让JavaScript引擎能够准确判断对象的类型,从而生成优化的代码。目前Firefox项目中的JavaScript引擎SpiderMonkey已经(正在做)内置支持该JavaScript文件,详情请查找asm.js。
- 数据表示 。因为一些简单类型的数据直接保存在句柄中,这能够有效地减少寻址时间和内存的使用。但是,因为使用了一部分位(特别对于V8引擎)来表示,所以整数表示范围缩小,如果使用较大的整数,那么就需要使用堆来保存。同时,对于数值来说,只要能够使用整数的,尽量不要使用浮点类型。
- 内存 。有效使用内存能够显著地提高代码的性能。对于使用垃圾回收的语言来说,并不是意味着没有内存泄露的问题,这就需要即时回收不需要使用的内存。简单的做法就是对引用不再使用的对象的变量设置为空(a = null)。另外一个方法跟类型有关,通过引入delete关键字,代码可以使用“delete a.x”来删除一个对象,这虽然可以减少内存的使用,但是因为使用了隐藏类,这种情况下可能需要新建隐藏类,所以这会带来一些复杂的额外操作。
- 优化回滚 。如前面介绍的,不要书写出触发出现优化回滚的代码,否则会大幅降低代码的性能。在执行多次之后,不要出现修改对象类型的语句。这说起来可能有些难,但实际上,如示例代码9-5之类的用法即可。
- 新机制 。使用JavaScript引擎或者是渲染引擎提供的新机制和新接口,如前面介绍的requestAnimationFrame等接口,这样可以有效减少JavaScript引擎的额外负担。另外,可以使用WebWorker等JavaScript并发技术来提升引擎并发处理能力。
4.2 例子
在浏览器中,JavaScript引擎和渲染引擎WebKit需要协同工作才能达到一个好的效果,结合这二者,这一小节来介绍一个简单的例子,就是后来被引入的新的JavaScript接口requestAnimationFrame,以此来解释它是如何解决两者之间一些比较难以处理的问题的,以及它给Web前端开发者带来的思考。
接触过JavaScript的读者应该有过了解或者使用setTimeout或setInterval的经历,其功能是在每个时间间隔之后一次性或者重复多次执行一段JavaScript代码(称为回调函数),以完成特定的动画要求。但是,这里面多少还有些疑问。
- 时间间隔应该设置为多少才合适呢?跟屏幕的分辨率有关系吗?
- 设置的时间间隔会按照预想的执行吗?动画会被平滑地显示出效果吗?
- 回调函数是复杂的好还是简单的好呢?应该如何编写才能效率高呢?
- 与平台和浏览器相关吗?如何适应不同操作系统和浏览器呢?
这些问题对setTimeout和setInterval来说很重要。对主循环机制和渲染机制有一定了解的读者来说,上面这几条其实是非常难做到的,哪怕是较为接近理想的结果也很难达到。
幸运的是,总是有聪明的人来帮助大家解决难题。对问题提出一个漂亮解决方案的是Mozilla的Robert O'Callahan。他的灵感和依据来源于CSS。CSS能够知道动画什么时候发生,所以能够较为准确地知道什么时候该刷新用户界面。对于JavaScript来说,是不是也可以应用类似的机制呢?答案是肯定的。其做法是增加一个新的函数requestAnimationFrame,该函数告诉浏览器JavaScript想发起一个动画帧,然后在动画帧绘制之前,需要做一些动作,这样浏览器可以根据需要来优化自己的消息循环机制和调用时间点,以达到较好的平衡效果。
WebKit中setTimeout和setInterval的实现机制是类似的,区别在于后者是重复性的,如图9-28所示的类关系。

图9-28 WebKit中的计时器等相关类
WebKit会为DOM树中的每个setTimeout和setInterval调用创建一个DOMTimer,而后该对象会由存储TLS(Thread Local Storage)中的ThreadTimers负责管理,其内部其实是一个最小堆,每次将超时时间设置为最小的。同时,时间相同的计时器可以合并。当计时器超时后,Chromium将清除该计时器对象,同时调用相应的回调函数,回调函数通常会更新页面的样式和布局,这会触发重新计算布局,从而触发立即重新绘制一个新帧。结合上面的描述,这里大致总结一下setTimeout和setInterval的不足。
- setTimeout和setInterval从不考虑浏览器内部发生了其他什么事,它们只要求浏览器在某个时间之后来调用回调函数,无论浏览器很繁忙或者页面被隐藏(虽然某些浏览器做了这方面的优化,如Chromium)。
- setTimeout和setInterval只是要求浏览器做什么,而不管浏览器能不能做到(如主循环有很多事件需要处理),这有点强人所难,而且会带来极大的资源浪费。例如屏幕的刷新率是60Hz,但是设置的时间间隔是5毫秒,其实对用户来说,他们根本看不到这些变化,但却额外需要消耗更多的CPU资源,太不环保了。
- setTimeout和setInterval可能是出于编程风格方面的考虑。如果每一帧在不同的代码处需要设置回调函数,一个方法是将这些代码统一到一个地方,但是这有点勉为其难,另一个方法是分别用setInterval设置它们,这个方法的问题是,浏览器可能需要计算更多次,刷新更多次的屏幕。
现在再来看看requestAnimationFrame是如何解决这些不足之处的呢?其原理就是其会申请绘制下一帧,至于什么时候还不知道,都是由浏览器决定,浏览器只需要在绘制下一帧前执行其设置的回调函数,完成JavaScript代码对动画所做的设置和逻辑即可。基本过程如下。
- JavaScript调用requestAnimationFrame,因而相应地,Webkit和Chromium会调度一个需要绘制下一帧的事件,该事件会将requestAnimationFrame的调用上下文和回调函数记录下来。
- 上面的请求会触发Chromium更新页面内容的事件,该事件被mainloop调度处理后,会检查是否需要调用动画的相关处理,因为有动画需要处理,所以会依次调用那些回调函数,JavaScript引擎会更新相应的CSS属性或者DOM树修改。
- Chromium触发重新计算布局(参看布局章节),更新自己的Renderer树,而后绘制,完成一帧的渲染。
上面这些描述会给Web前端开发者们在编写JavaScript代码时带来哪些思考和便利呢?
- 回调函数不能太大,不能占用太长时间,否则会影响页面的响应和绘制的频率。
- requestAnimationFrame不需要设置间隔时间,不同刷新率的间隔时间可能不一样,这完全由浏览器来控制,而不需要JavaScript代码的开发者们操心。
- 回调函数无需合并,开发者们可以在任意位置设置回调函数,它们可以被浏览器集中处理,而无需有统一的入口。
一个新的JavaScript接口可以带来很不错的处理方式,以此来平衡JavaScript引擎和渲染引擎之间的关系,并且能够有效帮助那些利用JavaScript和HTML5技术来实现动画的开发者们,这一点值得我们思考。
4.3 未来
因为历史的局限性,JavaScript最初的时候并不合适用来开发大工程和性能要求非常高的场景,所以开发者编写的代码对性能要求也不是很高。但是,目前的发展趋势是需要很高的性能,为此,仅仅依靠任何一方是没有办法来达到此目标的。笔者认为,今后为了高效的JavaScript代码性能,至少需要以下三个方面的努力,而且,就目前而言,它们也都在不停地向前发展。
首先是JavaScript语言和规范的发展。目前虽然规范定义的WebWorker在一定程度上能够并发,但是能力非常有限,而且两者之间只能通过有限的方式来通信(这个技术是由W3C组织引入的)。如果能够在ECMAScript标准中推动并行JavaScript能力,这绝对是一个大胆而又令人神往的想法。目前,一些大公司或者组织已经在推动并行JavaScript,希望未来有快速的发展,能够带领JavaScript真正进入并行时代。
其次是JavaScript引擎技术的发展和创新。一个简单的例子就是,V8不停地将之前用在其他编译器的技术带入到JavaScript引擎中来,同时自身也创造一些新的方法。据笔者目前观察得知,基本每个V8版本的升级都会带来性能上的提高,大家有理由相信,在这场JavaScript引擎大战中,各个引擎都会不停地提升技术以提升性能。
最后是同Web前端开发者相关的,那就是关于编写高效的JavaScript代码。结合语言的新能力和引擎技术的不断发展,要根据它们的特点,使用新技术和回避一些会对引擎带来重大性能伤害的用法。目前还没有这方面的系统介绍,希望未来能够有更多帮助开发者提高代码效率的使用方法被共享出来。
相关文章:
《WebKit 技术内幕》学习之九(4): JavaScript引擎
4 实践——高效的JavaScript代码 4.1 编程方式 关于如何使用JavaScript语言来编写高效的代码,有很多铺天盖地的经验分享,以及很多特别好的建议,读者可以搜索相关的词条,就能获得一些你可能需要的结果。同时,本节希望…...
[SpringBoot2.6.13]FastJsonHttpMessageConverter不生效
文章目录 错误描述问题分析打印目前所有的消息处理器寻找适配版本消息解释器加载顺序 错误原因正确写法使用最新版本fastjson(2024-1-22)配置fastjson2消息转换器(保留系统原消息转换器)替换消息转换器配置fastjson2 错误描述 采用Bean的方式配置FastJsonHttpMessageConverter…...
(delphi11最新学习资料) Object Pascal 学习笔记---第3章第一节(简单语句与复合语句)
Object Pascal 学习笔记,Delphi 11 编程语言的完整介绍 作者: Marco Cantu 笔记:豆豆爸 3.1 简单语句与复合语句 编程指令通常称为语句。一个程序块可以由多个语句组成。有两种类型的语句,简单语句和复合语句。当语句不包含任何其他子语…...
Unity - 简单音频
“Test_04” AudioTest public class AudioTest : MonoBehaviour {// 声明音频// AudioClippublic AudioClip music;public AudioClip se;// 声明播放器组件private AudioSource player;void Start(){// 获取播放器组件player GetComponent<AudioSource>();// 赋值…...
SpringCloud中服务间通信(应用间通信)-亲测有效-源码下载-连载2
1、微服务概述 本案例主要解决微服务之间的相互调用问题 如果已经理解什么是微服务,可以直接跳到实战。 本案例采用springBoot3.1.7springCloud2022.0.4版本测试 本案例使用springboot2.7.x版本测试代码相同 1、微服务是分布式架构,那么为什么要需要…...
Axios取消请求:AbortController
AbortController AbortController() 构造函数创建了一个新的 AbortController 实例。MDN官网给出了一个利用AbortController取消下载视频的例子。 核心逻辑是:利用AbortController接口的只读属性signal标记fetch请求;然后在需要取消请求的时候࿰…...
【江科大】STM32:(超级详细)定时器输出比较
文章目录 输出比较单元特点 高级定时器:均有4个通道 PWM简介PWM(Pulse Width Modulation)脉冲宽度调制输出比较通道PWM基本结构基本定时器 参数计算捕获/比较通道的输出部分详细介绍如下: 舵机介绍硬件电路 直流电机介绍ÿ…...
Go 复合数据类型
1. 数组(array)(OK) 数组数组的概念数组是具有固定长度且拥有零个或多个相同数据类型元素的序列 i. 元素的数据类型相同 ii. 长度固定的序列 iii. 零个或多个元素的序列 与 slice 对比 由于数组的长度固定,所以在 G…...
Redis(01)——常用指令
基础指令 select 数字:切换到其他数据库flushdb:清空当前数据库flushall:清空所有数据库dbsize:查看数据库大小exists key1[key2 …]:判断当前的key是否存在keys *:查看所有的keyexpire key 时间ÿ…...
基本语法和 package 与 jar
3.基本语法 1.输入输出 // 导入 java.util 包中的 Scanner 类 import java.util.Scanner;// 定义名为 ScannerExample 的公共类 public class ScannerExample {// 主方法,程序的入口点public static void main(String[] args) {// 创建 Scanner 对象,用…...
本地读取Excel文件并进行数据压缩传递到服务器
在项目开发过程中,读取excel文件,可能存在几百或几百万条数据内容,那么对于大型文件来说,我们应该如何思考对于大型文件的读取操作以及性能的注意事项。 类库:Papa Parse - Powerful CSV Parser for JavaScript 第一步…...
【开源】基于JAVA的停车场收费系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…...
基于java+Springboot操作系统教学交流平台详细设计实现
基于javaSpringboot操作系统教学交流平台详细设计实现 🍅 作者主页 央顺技术团队 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 文末获取源码联系方式 📝 🍅 查看下方微信号获取联系方式 承接各种定制系统…...
Nginx 基础使用
目录结构 进入Nginx的主目录我们可以看到这些文件夹 client_body_temp conf fastcgi_temp html logs proxy_temp sbin scgi_temp uwsgi_temp其中这几个文件夹在刚安装后是没有的,主要用来存放运行过程中的临时文件 client_body_temp fastcgi_temp proxy_temp scg…...
JavaEE:多线程(2):线程状态,线程安全
目录 线程状态 线程安全 线程不安全 加锁 互斥性 可重入 死锁 死锁的解决方法 Java标准库中线程安全类 内存可见性引起的线程安全问题 等待和通知机制 线程饿死 wait notify 线程状态 就绪:线程随时可以去CPU上执行,也包含在CPU上执行的…...
Flutter 自定义AppBar实现滚动渐变
1、使用ListView实现上下滚动。 2、使用Stack:允许将其子部件放在彼此的顶部,第一个子部件将放置在底部。所以AppBar,写在ListView下面。 3、MediaQuery.removePadding:当使用ListView的时候发现,顶部有块默认的Padd…...
编程语言MoonBit新增矩阵函数的语法糖
MoonBit更新 1. 新增矩阵函数的语法糖 新增矩阵函数的语法糖,用于方便地定义局部函数和具有模式匹配的匿名函数: fn init {fn boolean_or { // 带有模式匹配的局部函数true, _ > true_, true > true_, _ > false}fn apply(f, x) {f(x)}le…...
Angular:跨域请求携带 cookie
新建拦截器,设置 XMLHttpRequest:withCredentials 属性 1. 新建文件夹 http-interceptors 该文件夹下可有多个不同用途的拦截器2. 新建拦截器 common.interceptor.ts import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "an…...
【C++】list容器迭代器的模拟实现
list容器内部基本都是链表形式实现,这里的迭代器实现的逻辑需要注意C语言中指针的转换。 list容器如同数据结构中的队列,通常用链式结构进行存储。在这个容器中,我们可以模仿系统的逻辑,在头结点后设置一个“ 哨兵 ”,…...
Docker镜像操作
镜像名称 镜名称一般分两部分组成:[repository]:[tag]。 在没有指定tag时,默认是latest,代表最新版本的镜像。 这里的mysql就是repository,5.7就是tag,合一起就是镜像名称,代表5.7版本的MySQL镜像。 镜像…...
千问3.5-2B在物流场景:运单图片自动识别+收发件信息结构化
千问3.5-2B在物流场景:运单图片自动识别收发件信息结构化 1. 物流行业的痛点与机遇 每天,物流企业需要处理数以百万计的运单信息录入工作。传统的人工录入方式存在三个明显问题: 效率低下:一个熟练的录入员每小时最多处理50-80…...
5个简单步骤掌握LiteDB.Studio:免费开源的LiteDB数据库终极GUI管理工具
5个简单步骤掌握LiteDB.Studio:免费开源的LiteDB数据库终极GUI管理工具 【免费下载链接】LiteDB.Studio A GUI tool for viewing and editing documents for LiteDB v5 项目地址: https://gitcode.com/gh_mirrors/li/LiteDB.Studio 在当今数据驱动的软件开发…...
从播放卡顿到流媒体优化:深入MP4的stbl盒子,理解视频流畅播放的关键
从播放卡顿到流媒体优化:深入MP4的stbl盒子,理解视频流畅播放的关键 当你在深夜调试一个在线视频播放器,发现用户总是抱怨卡顿和拖拽不准时,是否曾思考过问题可能隐藏在MP4文件最核心的stbl盒子中?作为流媒体开发者&am…...
2026年AI Agent将迎来爆发!这五大趋势将重塑企业未来,你准备好了吗?
2026年AI Agent将进入规模化部署阶段,应用渗透率将大幅提升。文章分析了五大核心趋势:多智能体协同、企业级部署规模化、行业垂直化、可信性与透明度提升,以及人机协作模式重构。同时,文章也提醒企业需警惕项目失败风险࿰…...
【建议收藏】彻底剥离“机器味”:2026硬核横评10款降AI神器,实录97.98%极限降至7.46%
现在的知网、维普AIGC检测已经不同往日了,哪怕你逐字手打,只要句式稍显机械,系统就会判定疑似AI生成。很多同学为了降低ai率,把论文改成了毫无逻辑的口水话,结果AI率反而炸了。 别再盲目试错,为了帮大家在…...
使用Cosmos-Reason1-7B分析网络协议交互逻辑:以TCP三次握手为例
使用Cosmos-Reason1-7B分析网络协议交互逻辑:以TCP三次握手为例 最近在尝试用大模型来理解一些复杂的系统交互逻辑,发现了一个挺有意思的用法。我们团队在测试Cosmos-Reason1-7B时,没有让它写代码或者生成文案,而是给了它一个更“…...
突发!国行苹果 AI 凌晨偷跑又紧急下线
3 月 31 日凌晨,大量升级 iOS 26.4 的国行 iPhone 16 及后续机型用户,突然发现设置里 “Siri” 变成 “Apple 智能与 Siri”,可下载 9.5GB 本地 AI 模型,解锁实时翻译、视觉智能、照片消除等全套功能。不过这场“惊喜”仅持续了数…...
别再手动转格式了!用Python的docx2pdf库5行代码搞定Word转PDF(Windows/Mac通用教程)
5行代码终结格式转换焦虑:Python自动化Word转PDF全攻略 每次市场部门催着要电子合同时,你是不是还在手忙脚乱地点击"另存为PDF"?当运营团队需要批量生成上百份产品手册时,是否还在忍受重复机械的格式转换操作࿱…...
无需编程!Qwen3-ASR语音识别服务5分钟快速部署指南
无需编程!Qwen3-ASR语音识别服务5分钟快速部署指南 1. 开篇:语音识别零门槛体验 想象一下,你刚结束一场跨国会议,需要将录音快速转为文字;或者你收集了大量方言访谈,急需整理成文档。传统方法要么费时费力…...
ComfyUI ControlNet模型与预处理器搭配秘籍:提升AI绘画精度的关键技巧
ComfyUI ControlNet模型与预处理器搭配秘籍:提升AI绘画精度的关键技巧 在AI绘画领域,ControlNet已经成为精细控制图像生成的重要工具。对于已经熟悉ComfyUI基础操作的用户来说,掌握ControlNet模型与预处理器的搭配技巧,是突破创作…...
