JavaScript 既是一个 面向过程的语言 又是一个 面向对象的语言。在 JavaScript 中,通过在运行时给空对象附加方法和属性来创建对象,与编译语言如 C++ 和 Java 中常见的通过语法来定义类相反。对象构造后,它可以用作是创建相似对象的原型。

JavaScript 的动态特性包括运行时构造对象、可变参数列表、函数变量、动态脚本执行(通过 eval)、对象内枚举(通过 for ... in)和源码恢复(JavaScript 程序可以将函数反编译回源代码)。

JavaScript方面,之前写过《ECMAScript进化史(1):话说Web脚本语言王者JavaScript的加冕历史

在看 各JavaScript引擎的简介,及相关资料/博客收集帖 ,结合自己的理解,整理一个笔记。现代JavaScript引擎都有哪些特征呢?跟以前的JavaScript引擎有怎样的差别,为什么变快了那么多?

JavaScript引擎历史

早期JavaScript引擎的实现普遍跟同时代的其它脚本语言一样,比较“偷懒”。反正是“脚本语言”,当时的JavaScript脚本通常只包含很简单的逻辑,只运行很短时间就完事。没啥性能压力,得不到足够的重视与开发资源,性能自然是好不到哪里去,却也足以满足当时的需求。

Mocha

非常早期的“Mocha”引擎实现得确实非常偷懒。字节码解释器、引用计数方式的自动内存管理、fat discriminated union形式的值表现形式犀牛书第4版写了点JavaScript与引用计数的历史

SpiderMonkey

1996年,祖师爷Brendan Eich新写的SpiderMonkey已经改为使用mark-and-sweep GC、tagged value

在V8出现前,SpiderMonkey是native application嵌入JavaScript的最流行选择。如果大家没留意过的话,UltraEdit就内嵌了SpiderMonkey来让用户使用JavaScript写宏与插件[/url];Adobe Acrobat也类似。

于是其实早期的两个主要的JavaScript引擎实现,Mozilla SpiderMonkey和Microsoft JScript其实都一直在用mark-and-sweep GC。也没啥别的主流JavaScript引擎用过引用计数方式来实现自动内存管理的。这点别被忽悠了。

在叫得出名字的JavaScript引擎里只有quad-wheel(没听说过么?不奇怪,非主流嘛)是用引用计数方式实现自动内存管理的。

老版本IE里JScript虽说是有因为循环引用而导致内存泄漏的问题,但那不是因为JScript自身用引用计数。问题出在JScript与DOM交互的边界上:IE的DOM节点(及其它host对象)是COM对象,而COM对象自身是引用计数的。在JS一侧GC时DOM节点被看作根节点,所以被DOM节点引用的JS对象不会死;反过来,被JS对象引用的DOM节点的引用计数不为0所以也不会死。这导致JScript与DOM交互时有可能被连累引发循环引用->内存泄漏的问题。

IE9/Chakra里已经通过把DOM对象变成由JavaScript一侧的GC来管理解决了这个问题。

早期JavaScript引擎得到的投入实在不足,而当时的Java虚拟机(JVM)却得到了大量资源实现各种优化,包括JIT编译器之类。这使得用Java写的Rhino一度能比用C写的SpiderMonkey跑得还快,因为Rhino得益于JVM里优秀的JIT编译器和GC,而SpiderMonkey还在用简易的解释器和GC

这个阶段中,JavaScript对象的布局或者说表现方式通常可以叫做“property bag”,本质上就跟hashmap一样。

Rhino/Nashorn

Rhino是Java版的SpiderMonkey。当时Netscape想用纯Java来实现新版浏览器,自然需要一个Java版的JavaScript引擎实现;另外也希望能在服务器端把JavaScript当作Java应用里的脚本语言使用。于是Rhino就诞生了。

具体查看《Java集成JavaScript项目工程:基于Rhino的javascript后台开发》

KJS

Apple把KHTML拿去演化出了WebKit,其中的KJS演化成了JavaScriptCore。KJS影响力远不如JavaScriptCore。KJS是为数不多的没有JIT编译器的。

JavaScriptCore

JavaScriptCore源自KJS,但持续得到苹果的大力投入,终而青出于蓝胜于蓝,已经完全超越了它的前身。

QtScript背后也使用JavaScriptCore。

虽然iOS的Safari和UIWebView控件里跑的都是JavaScriptCore,但只有Apple自己的程序才可以启用JIT编译,而第三方的则不行。所以Mobile Chrome for iOS就用不了JavaScriptCore的JIT。

Chakra

Chakra问世后的JScript已非当日吴下阿蒙。

即便Chakra的解释器也是字节码解释器,它的字节码设计与老版本JScript的已经相当不同,解释器自身的速度都已经有所提升。

Chakra里的隐藏类变迁机制叫做“type evolution”。每个产品都必须发明些新名词

E9版Chakra里字段数量不超过16个的对象可以使用紧凑布局;IE10版Chakra将这限制放宽到30多个字段。

IE9 Chakra的对象布局是对象头与property数组分离的。IE10版则将构造器函数里赋值的属性直接跟对象头粘在一起分配。

Chakra里的value representation跟V8的比较类似,都是在最低的几位放tag;不过Chakra的是tagged-value,也就是在小整数的后面带上一个0x1的tag,而对象地址是8字节对齐的于是对象指针的最低3位为0。打tag的取舍正好与V8的tagged-pointer相反,而与更多其它用tagged-value的VM相似,例如说更传统的Smalltalk实现,包括现在还可以用到的Squeak,或者是像Ruby等受Smalltalk影响的VM。

注意:IE9在x64上的版本里的Chakra只有解释器,没实现JIT编译器;到IE10才开始在x64版上提供JIT编译器。

同样只有字节码解释器,IE9 64-bit的Chakra仍然可以比IE8 64-bit的JScript 5.8快近10倍

JScript

JScript 5.8(IE8里的JScript)之后版本号重新计算了,下一个大版本就是IE9里的JScript 9.0,代号Chakra,在前面有介绍

JScript里对象里属性的存储基本上是靠Hashtable;数组性质的对象最初也是为稀疏数组优化,背后仍然是用Hashtable来存储。到IE8/JScript 5.8才加上了对密集数组的存储/访问优化。

执行引擎是个简单的解释器,switch-threading形式的解释器主循环,位于CScriptRuntime::Run(VAR*)。在jscript.dll里这个switch被编译为一个table-based dispatch。

被这两处调用:

ScrFncObj::CallWithFrameOnStack(VAR *,int,VAR *,VAR *,ulong)

ScrFncObj::Call(VAR *,int,VAR *,VAR *,ulong)

用于优化字符串拼接用的BuildString类。在Chakra里也继承了下来。

不常见的JavaScript引擎

上面的JavaScript引擎都是常见

IronJS

IronJS原本完全使用F#实现,后来改为只用F#来实现parser,而用C#来实现runtime部分。这是个非常妙的搭配。F#(以及许多函数式语言)天生就非常适合用来写需要大量模式匹配的程序,写parser最适合不过。而runtime部分更多是与.NET的其它部分打交道,这里用C#就会更顺手些。

Ironjs是在Microsoft 动态语言运行时之上构建的ECMAScript 3.0实现,它使您可以将JavaScript运行时嵌入到.NET应用程序中。

使用Ironjs环境

  • .NET 3.5(Src / CLR2.sln)

  • .NET 4.0(Src / CLR4.sln)

  • Mono 2.10(Src / mono-build.sh)

Ironjs还具有对.NET 2.0和3.0的实验性支持,可使用CLR2解决方案进行编译并设置额外的NET2标志。

IronJS的parser整体采用top-down operator precedence(TDOP)方式,在JavaScript的引擎实现中比较少见。不过却正好与微软自家的Managed JScript相似。不知道作者在写IronJS时是否有受Managed JScript的思路影响呢?

如果采用TDOP不是Managed JScript的影响,那或许是受Douglas Crockford大神那篇TDOP教程的影响了。

最初的IronJS其实用的是基于ANTLR生成的parser。不过后来用F#新写的parser比老的ANTLR生成的parser快得多。

不过作者决定在下一版IronJS里改为完全使用C#,主要是出于性能方面的考虑。并不是F#本身不够快,而是F#的各种方便简洁的功能容易引人写出不那么快的代码,而要写比较高效的代码样子会跟C#看起来很像。于是还不如直接用C#好了。

IronJS使用了Nan-boxing,只不过比起那些用C/C++之类的native语言所实现的NaN-boxing tagged pointer而言,IronJS版的比较“肥”一些——例如说JavaScriptCore的一个tagged pointer在x86-64上就是64位,跟一个double一样大,指针类型的值跟值类型的值可以重叠在同一个位置上;而在IronJS的则要128位,其中值类型的值与tag在头64位,而指针类型在后64位。

虽然肥一些,作为Nan-boxing的思路和效果还是类似的。用了tagged pointer之后至少那些值类型的值的内存开销都变小了——不用tagged pointer的话自动装箱的double在32位CLR上也至少得要16字节,外加引用它的指针4字节也得要20字节了,而IronJS的BoxedValue则总共只要16字节而且不会有额外指针带来的间接层,在内存局部性上也比不用tagged pointer好。

参考文章:

关于 JavaScript https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/About_JavaScript

各JavaScript引擎的简介,及相关资料/博客收集帖 https://hllvm-group.iteye.com/group/topic/37596

用 JavaScript 解释 JavaScript 虚拟机-内联缓存(inline caches) https://segmentfault.com/a/1190000010819044

GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,优化收集方法的思路 https://blog.csdn.net/fateruler/article/details/81158510

转载本站文章《JS引擎(0):JavaScript引擎群雄演义—起底JavaScript引擎》,
请注明出处:https://www.zhoulujun.cn/html/webfront/browser/webkit/2020_0718_8521.html

JS引擎(0):JavaScript引擎群雄演义—起底JavaScript引擎的更多相关文章

  1. 开源HTML5游戏引擎Kiwi.js 1.0正式发布

    Kiwi.js是由GameLab开发的一款全新的开源HTML5 JavaScript游戏引擎.在经过一年多的开发和测试之后,终于在日前正式发布了Kiwi.js 1.0版本. 其创始人Dan Milwa ...

  2. JavaScript深入浅出第4课:V8引擎是如何工作的?

    摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...

  3. JavaScript 工作原理之十一-渲染引擎及性能优化小技巧

    原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland. 本系列持续更新中,Github 地址请查阅这里. 这是 JavaScript 工作原理的第十一章. 迄 ...

  4. JavaScript是如何工作的01:引擎,运行时和调用堆栈的概述!

    概述 几乎每个人都已经听说过 V8 引擎,大多数人都知道 JavaScript 是单线程的,或者它使用的是回调队列. 在本文中,我们将详细介绍这些概念,并解释 JavaScrip 实际如何运行.通过了 ...

  5. Ember.js 1.0 RC6 发布,JavaScript 框架

    Ember.js 1.0 发布了第 6 个 RC 版本,下载地址:https://github.com/emberjs/ember.js/tree/v1.0.0-rc.6 该版本包含众多的改进记录,详 ...

  6. 窥探Vue.js 2.0

    title: 窥探Vue.js2.0 date: 2016-09-27 10:22:34 tags: vue category: 技术总结 --- 窥探Vue.js2.0 令人兴奋的Vue.js 2. ...

  7. Vue.js 2.0 和 React、Augular

    Vue.js 2.0 和 React.Augular 引言 这个页面无疑是最难编写的,但也是非常重要的.或许你遇到了一些问题并且先前用其他的框架解决了.来这里的目的是看看Vue是否有更好的解决方案.那 ...

  8. 更轻更快的Vue.js 2.0与其他框架对比(转)

    更轻更快的Vue.js 2.0 崭露头角的JavaScript框架Vue.js 2.0版本已经发布,在狂热的JavaScript世界里带来了让人耳目一新的变化. Vue创建者尤雨溪称,Vue 2.0  ...

  9. 在 Ubuntu 14.04/15.04 上配置 Node JS v4.0.0

    大家好,Node.JS 4.0 发布了,这个流行的服务器端 JS 平台合并了 Node.js 和 io.js 的代码,4.0 版就是这两个项目结合的产物——现在合并为一个代码库.这次最主要的变化是 N ...

  10. 拥抱Node.js 8.0,N-API入门极简例子

    本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. N-API简介 Node.js 8.0 在2017年6月份发布, ...

随机推荐

  1. APIO 2023 游记

    真心话大冒险很有趣. rand 一个房间去敲门加 QQ 很有趣.这么看社恐猫好像也没那么社恐. 面到了 zpl pcq iee dx.单方面认识了很多神仙. 比赛只会写暴力,评测 queue 害人不浅 ...

  2. 【2023年更新】git 常用口令

    1.已关联远程 fatal: remote origin already exists.   先输入$ git remote rm origin(删除关联的origin的远程库)   2.关联新远程 ...

  3. QT(2)-QRegExp

    QT(2)-QRegExp 1 正则表达式 正则表达式--详情版+常用表达式 Qt中正则表达式(常用) Qt 正则表达式介绍 QRegExp的使用 2 QRegExp 2.1 indexIn int ...

  4. UML类图(最重要的三个关系)

    关联关系 ============= 关联关系:B是A的属性(A contains B),则A-->B:另外的: 都是特殊的关联关系 AB: 聚合(Aggregation)关系表示整体与部分的关 ...

  5. 支持C#的开源免费、新手友好的数据结构与算法入门教程

    前言 前段时间完成了C#经典十大排序算法(完结)然后有很多小伙伴问想要系统化的学习数据结构和算法,不知道该怎么入门,有无好的教程推荐的.今天给大家推荐一个支持C#的开源免费.新手友好的数据结构与算法入 ...

  6. 支持向量机SVM:从数学原理到实际应用

    本篇文章全面深入地探讨了支持向量机(SVM)的各个方面,从基本概念.数学背景到Python和PyTorch的代码实现.文章还涵盖了SVM在文本分类.图像识别.生物信息学.金融预测等多个实际应用场景中的 ...

  7. .NET中有多少种定时器

    .NET中至少有6种定时器,每一种定时器都有它的用途和特点.根据定时器的应用场景,可以分为UI相关的定时器和UI无关的定时器.本文将简单介绍这6种定时器的基本用法和特点. UI定时器 .NET中的UI ...

  8. 2. Shell 条件测试

    重点: 条件测试. read. Shell 环境配置. case. for. find. xargs. gzip,bzip2,xz. tar. sed. 1)位置 变量 位置变量:在 bash She ...

  9. 黑客玩具入门——4、漏洞扫描与Metasploit

    1.Legion漏洞扫描分析工具 Legion是Sparta的一个分支,它是一个开源的.易于使用的.超级可扩展的.半自动的网络渗透测试框架.它是一款Kali Linux系统默认集成的Python GU ...

  10. 自研、好用的ORM 读写分离功能使用

    Fast Framework 作者 Mr-zhong 代码改变世界.... 一.前言 Fast Framework 基于NET6.0 封装的轻量级 ORM 框架 支持多种数据库 SqlServer O ...