在多次阅读 asm.js 规范并在 Firefox 中对其进行试验后,我同意 bks:
asm.js 程序只能通过数字句柄与外部数据间接交互
然而,这并不构成大问题。由于 asm.js 是 JavaScript 的子集,因此您将无法在 asm.js 中使用很多 JavaScript 结构,包括:
- JavaScript 对象
- 动态数组
- 高阶函数
尽管如此,asm.js 确实提供了一种使用外部函数接口 (FFI) 调用 JavaScript 函数的方法。这是一个非常强大的机制,因为它允许您从 asm.js 回调 JavaScript(允许您创建部分用 asm.js 编写和部分用 JavaScript 编写的过程)。
区分代码的哪些部分可以转换为 asm.js 并从使用 asm.js中受益,这一点很重要。例如 asm.js 非常适合图形处理,因为它需要大量计算。但是它不适合字符串操作。为此目的,纯 JavaScript 会更好。
回到主题,您面临的问题是您需要从 asm.js 代码中引用 JavaScript 对象。由于这样做的唯一方法是使用数字句柄(您不想要),因此我只看到了另一种解决方案:
不是从 asm.js 中引用 JavaScript 对象,而是从 JavaScript 中引用 asm.js 结构。
这种方法更好的原因有很多:
- 由于 JavaScript 是 asm.js 的超集,您已经可以在 JavaScript 中按原样使用 asm.js 结构。
- 由于 JavaScript 比 asm.js 更强大,因此更容易使 asm.js 结构表现得像 JavaScript 对象。
- 通过将 asm.js 结构导入 JavaScript,您的 asm.js 代码变得更简单、更有凝聚力且耦合度更低。
话不多说,我们来看一个例子。让我们以Dijkstra 的最短路径算法为例。幸运的是,我已经有一个工作演示(我必须为大学作业实现 Dijkstra 算法):
http://jsfiddle.net/3fsMn/
上面链接的代码完全用普通的旧 JavaScript 实现。让我们提取这段代码的某些部分并将其转换为 asm.js(请记住,数据结构将在 asm.js 中实现,然后导出为 JavaScript)。
从具体的开始,这是我在 JavaScript 中创建图形的方式:
var graph = new Graph(6)
.addEdge(0, 1, 7)
.addEdge(0, 2, 9)
.addEdge(0, 3, 14)
.addEdge(1, 2, 10)
.addEdge(1, 4, 15)
.addEdge(2, 3, 2)
.addEdge(2, 4, 11)
.addEdge(3, 5, 9)
.addEdge(4, 5, 6);
我们希望保持相同的界面。因此,首先要修改的是Graph
构造函数。这是它目前的实现方式:
function Graph(v) {
this.v = --v;
var vertices = new Array(v);
for (var i = 0, e; e = v - i; i++) {
var edges = new Array(e);
for (var j = 0; j < e; j++)
edges[j] = Infinity;
vertices[i] = edges;
}
this.vertices = vertices;
}
我不会费心去深入解释所有代码,但需要大致了解:
- 首先要注意的是,假设我正在创建一个由 4 个顶点组成的图,那么我只创建一个包含 3 个顶点的数组。最后一个顶点不是必需的。
- 接下来,对于每个顶点,我在两个顶点之间创建一个新数组(表示边)。对于具有 4 个顶点的图:
- 第一个顶点有 3 条边。
- 第二个顶点有 2 条新边。
- 第三个顶点有 1 条新边。
- 第四个顶点有 0 个新边(这就是我们只需要一个包含 3 个顶点的数组的原因)。
通常,n
顶点图具有n * (n - 1) / 2
边。所以我们可以将图表以表格的形式表示如下(下表是上面演示中的图表):
+-----+-----+-----+-----+-----+-----+
| | f | e | d | c | b |
+-----+-----+-----+-----+-----+-----+
| a | | | 14 | 9 | 7 |
+-----+-----+-----+-----+-----+-----+
| b | | 15 | | 10 |
+-----+-----+-----+-----+-----+
| c | | 11 | 2 |
+-----+-----+-----+-----+
| d | 9 | |
+-----+-----+-----+
| e | 6 |
+-----+-----+
这是我们需要在 asm.js 模块中实现的数据结构。现在我们知道了它的样子,让我们开始实现它:
var Graph = (function (constant) {
function Graph(stdlib, foreign, heap) { /* asm.js module implementation */ }
return function (v) {
this.v = --v;
var heap = new ArrayBuffer(4096);
var doubleArray = this.doubleArray = new Float62Array(heap);
var graph = this.graph = Graph(window, {}, heap);
graph.init(v);
var vertices = { length: v };
for (var i = 0, index = 0, e; e = v - i; i++) {
var edges = { length: e };
for (var j = 0; j < e; j++) Object.defineProperty(edges, j, {
get: element(index++)
});
Object.defineProperty(vertices, i, {
get: constant(edges)
});
}
this.vertices = vertices;
function element(i) {
return function () {
return doubleArray[i];
};
}
};
}(constant));
如您所见,我们的Graph
构造函数变得更加复杂。除了v
和vertices
我们还有两个新的公共属性doubleArray
和graph
,它们分别用于公开来自 asm.js 模块的数据结构和数据操作。
该vertices
属性是特别的,现在实现为对象而不是数组,它使用 getter 来公开 asm.js 数据结构。这就是我们在 JavaScript 中引用 asm.js 数据结构的方式。
堆很简单ArrayBuffer
,可以通过 asm.js 代码或普通的旧 JavaScript 对其进行操作。这允许您在 asm.js 代码和 JavaScript 之间共享数据结构。在 JavaScript 方面,您可以将此数据结构包装在一个对象中,并使用 getter 和 setter 来动态更新堆。在我看来,这比使用数字句柄要好。
结论: 由于我已经回答了您的问题并演示了如何将 asm.js 数据结构导入 JavaScript,因此我认为这个答案是完整的。尽管如此,我还是想留下一个工作演示作为概念证明。然而,这个答案已经变得太大了。我会写一篇关于这个主题的博客文章,并尽快在此处发布指向它的链接。
JSFiddle for Dijkstra 在 asm.js 中实现的最短算法路径算法即将推出。