《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镜像。 镜像…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
