在多次阅读 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 中实现的最短算法路径算法即将推出。