《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镜像。 镜像…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
二叉树-144.二叉树的前序遍历-力扣(LeetCode)
一、题目解析 对于递归方法的前序遍历十分简单,但对于一位合格的程序猿而言,需要掌握将递归转化为非递归的能力,毕竟递归调用的时候会调用大量的栈帧,存在栈溢出风险。 二、算法原理 递归调用本质是系统建立栈帧,而非…...
