JavaScript 宿主对象是如何实现的?
Posted
技术标签:
【中文标题】JavaScript 宿主对象是如何实现的?【英文标题】:How are JavaScript host objects implemented? 【发布时间】:2011-12-12 14:48:56 【问题描述】:我今天在想这个,我意识到我在这里没有清晰的画面。
以下是我认为正确的一些陈述(如果我错了,请纠正我):
DOM 是 W3C 指定的接口集合。 在解析 html 源代码时,浏览器会创建一个 DOM 树,其中包含实现 DOM 接口的节点。 ECMAScript 规范没有引用浏览器宿主对象(DOM、BOM、HTML5 API 等)。 DOM 的实际实现方式取决于浏览器内部,并且可能在大多数浏览器中有所不同。 现代 JS 解释器使用 JIT 来提高代码性能并将其转换为字节码当我打电话给document.getElementById('foo')
时,我很好奇幕后会发生什么。调用是否由解释器委托给浏览器本机代码,或者浏览器是否具有所有主机对象的 JS 实现?您知道他们对此进行的任何优化吗?
我阅读了this overview of browser internals,但它没有提及任何关于此的内容。有时间我会看一下Chrome和FF的源码,不过我想先在这里问一下。 :)
【问题讨论】:
真棒的问题,亚历克斯 ... +1 并喜欢! 好吧,为了说明,认为它当然与 Java 完全不同,可以访问 javascript ScriptEngine 的“执行上下文”,并且可以使任意 Java 对象对 JavaScript 可见。跨语言接口只是来回桥接函数调用,在两个方向上执行一些必要的类型转换。 【参考方案1】:DOM 几乎在所有主要的浏览器实现中都是作为独立于语言的库来实现的,这意味着它与 Javascript 引擎位于不同的库中。例如在IE中,JS引擎在jscript.dll
中实现,而DOM在mshtml.dll
中实现。 Safari 有 Nitro(JS) 和 WebCore(DOM)。 Chrome 有 V8(JS) 和 WebCore(DOM),Firefox 有 SpiderMonkey/TraceMonkey(JS) 和 Gecko(DOM)。
这意味着,无论何时您的 JS 必须访问 DOM,它都必须访问 DOM 库——由于必须进行所有编组,这本来就很慢。曾经用过的一个比喻是两块土地被一座收费桥连接起来,任何时候你接触到DOM,你都必须越过桥再越过——付出性能代价。
参考
Video: Building High Performance Web Applications and Sites Book: High Performance Javascript (Chapter 3 on the DOM)【讨论】:
关于参考 2,“高性能 JavaScript”,请参阅 this PDF file。 感谢您的回答,我会检查参考资料。 这个答案还适用吗?【参考方案2】:JS 调用像getElementById
这样的 DOM 方法会导致 JS 引擎调用实现 DOM 的 C++ 代码。例如,在 Firefox 中,调用以nsDocument::GetElementById(const nsAString& aId, nsIDOMElement** aReturn)
结束。
如您所见,Firefox 维护了一个哈希表,将 id 映射到 C++ 中的元素作为这种情况下的优化,因此它不会遍历整个 DOM 树来寻找 id。
【讨论】:
【参考方案3】:你所有的要点都是正确的,除了:
现代 JS 解释器使用 JIT 来提高代码性能并将其转换为字节码
应该是“......并将其翻译为本机代码”。在当前的 JS 速度军备竞赛之前,SpiderMonkey(Firefox 中的 JS 引擎)作为字节码解释器工作了很长时间。
在 Mozilla 的 JS-to-DOM 桥上:
主机对象通常在 C++ 中实现,尽管implement DOM in JS 正在进行实验。因此,当网页调用document.getElementById('foo')
时,通过其 ID 检索元素的实际工作是在 C++ 方法中完成的,正如 hsivonen 所指出的。
调用底层 C++ 实现的具体方式取决于 API,并且随着时间的推移也会发生变化(请注意,我没有参与开发,所以可能对某些细节有误,这里是 a blog post by jst,实际参与的人在创建大部分代码时):
在最低级别,每个 JS 引擎都提供 API 来定义宿主对象。例如,浏览器可以调用 JS_DefineFunctions(如 SpiderMonkey User Guide 中所示)让引擎知道每当脚本调用具有指定名称的函数时,都应该调用提供的 C 回调。宿主对象的其他方面也一样(例如枚举、属性 getter/setter 等) 对于核心 ECMAScript 功能和一些棘手的 DOM 案例,JS 引擎/浏览器直接使用这些 API 来定义宿主对象及其行为,但它需要许多常见的样板代码,例如检查参数类型,将它们转换为适当的 C++ 类型,错误处理等。 出于我不会深入讨论的原因,假设历史上,Mozilla 大量使用XPCOM 来处理它的许多对象,包括大部分 DOM。 XPCOM 的一个特性是它与称为 XPConnect 的 JS 绑定。除此之外,XPConnect 可以采用 IDL 中的接口定义(例如 nsIDOMDocument;或更准确地说是其编译表示),将具有指定属性的对象公开给脚本,然后,当脚本调用getElementById
时,执行必要的参数检查/转换并将调用直接路由到 C++ 方法 (nsDocument::GetElementById(const nsAString& aId, nsIDOMElement** aReturn)
)
XPConnect 的工作方式非常低效:它将泛型函数注册为要在脚本访问主机对象时执行的回调,并且这些泛型函数会动态计算出它们在每个特定情况下需要执行的操作。 This post about quickstubs 为您介绍一个示例。
上一个链接中提到的“快速存根”是一种通过交换一些代码大小来优化 JS->C++ 调用时间的方法:而不是总是使用知道如何进行任何类型调用的通用 C++ 函数,专门的代码会在 Firefox 构建时自动生成一个预定义的“热”调用列表。
后来在 JIT(当时的 tracemonkey)was taught to generate the code calling C++ methods 作为为 JS 中的“热”路径生成的本机代码的一部分。我不确定新的 JIT (jaegermonkey) 在这方面是如何工作的。
使用"paris bindings" 对象are exposed to webpage JS 不依赖XPConnect,而是基于WebIDL(而不是XPCOM 时代的IDL)生成所有必要的粘合JSClass 代码。另请参阅从事此工作的开发人员的帖子:jst 和 khuey。另见How is the web-exposed DOM implemented?
我对最后三点的细节特别模糊,所以请持保留态度。
最近的改进被列为 bug 622298 的依赖项,但我没有密切关注它们。
【讨论】:
以上是关于JavaScript 宿主对象是如何实现的?的主要内容,如果未能解决你的问题,请参考以下文章