Observable 是如何运行的

如果你与以前使用过其他交互式 notebooks,则可能习惯于从上到下运行代码。第一个单元格首先运行,第二个单元格第二个运行,并且可以引用第一个单元格中定义的值,以此类推。

Observable 是不同的:它的功能类似于电子表格,其中,当单元格(如公式)的引用值发生更改时,它们会自动运行。Observable 独立于它们在 notebook 中的顺序运行单元格,这让你可以灵活地按照你喜欢的顺序排列单元格。

Observable 使用 topological 顺序来运行单元格,这由单元格引用决定。如果单元格 B 需要单元格 A 的值,在计算单元格 A 之前,Observable 不会运行单元格 B。同样,每当单元格 A 的值发生变化时,单元格 B 都会自动重新计算。Observable notebook 不是单元格的线性序列,而是一个有向图,其中值从上到下流动。
graph (12).png
我们稍后将 bar chart notebook 作为一个整体来考虑,但首先,让我们仔细检查一个单元格:y-scale。scale 是一个函数,它接受一个数值数据值作为输入(英文中字母的相对频率)并返回相应的高度(以像素为单位)。
image.png
要构造这个 scale,我们需要几个单元格外部的值:

  • d3(用于可视化数据的 JavaScript 库)
  • data(从 CSV 文件加载的数据集)
  • margin(以像素为单位定义图表边距的对象)
  • height(图标的高度,同样以像素为单位)

换句话说,在我们可以计算 maxValue 之前,我们需要加载 data ,等等。

Observable 通过解析 JavaScript 并查找没有在作用域中声明的变量来识别这些外部引用。这些变量是由 notebook 中的其它单元格定义的。(或者如果它们丢失了,Observable 将不会计算引用单元格:你将得到一个 RuntimeError。)

考虑一个更简单的例子:
image.png
单元格 bar 引用 foo 而没有定义它。Observable 把这个作为对单元格 foo 的引用,所以它首先计算 foo,然后使用这个值来计算 bar

Observable 理解作用域,因此可以用另一个单元格的名称定义一个局部变量,而这个局部变量将屏蔽对该单元格的引用。
image.png
如上所述,我们可以将 notebook 看作一个表示数据流的有向图,其中图中的节点是单元,有向边从一个被引用的单元移动到另一个引用的单元。单元格的引用是它的传入边。
image.png
在我们可以计算 y-scale 之前,我们需要它的传入引用的值:heightdatad3margin。这些可能是异步定义的——例如,D3 需要从 unpkg 加载,而 data set 需要从 GitHub 加载。它们甚至可能是动态的,比如随着时间变化的数据集。

我们还可以查看单元格的输出边:引用此单元格值的其他单元格。如果 y-scale 发生变化,Observable 将重新计算下游的单元格:yAxischart
image.png
不幸的是,仅仅知道立即输出的边还不足以知道如何重新计算下游单元。我们需要整个图,因为我们需要重新计算这些单元格的传出应用,它们的传出引用,等等,直到 notebook 是最新的。

下面的动画显示了条形图 notebook 是如何按拓扑顺序计算的。括号中的数字是每个单元格尚未计算的传入引用的计数:只有在计算完成所有引用之后才能计算单元格(显示为零的计数)。当前计算单元显示为红色,准备计算单元显示为灰色,计算单元显示为黑色
SDGIF_Rusult_1.gif
这种设计有一些有趣的结果。

首先,相同拓扑级别的单元格的计算顺序是任意的!例如,widthheight margin 不引用任何其他单元格,因此 Obseravable 可以以任何顺序计算它们。如果单元是异步定义的,例如承诺从远程服务器获取数据集,那么单元将同时进行。因此,要注意副作用和对其他单元的隐式依赖(例如从 DOM 中选择),因为计算的顺序并不能保证。

其次,不允许循环引用。如果在数据流图中创建一个循环,该循环中的所有单元格都将抛出一个 RuntimeError。
image.png
数据流图还允许 Observable 在发生变化时高效地重新计算图的子集。例如,每当窗口调整大小时,来自 Observable standard librarywidth 反应变量就会改变。这对于响应性图表很方便:你所要做的就是引用内置的 width,如果窗口大小调整,图表将自动更新。

下面是我们的例子条形图的工作原理。当 width 改变时,重新计算 x-scale。然后使用新的 x-scale 创建新的 x-scale。最后,再创建一个 chart
image.png
注意,当只有 width 变化时,不需要重新计算图形的其余部分——data,y-scale等等。只有受到更改影响的单元格(间接或直接)才会重新计算。这种选择性的重新计算使 Observable 变得更快。

在2020年3月,我们引入了 visual dataflow,这样你就可以看到数据是如何流经你的 notebook 的。有关你的 notebook 图形的更详细视图,请参见 notebook visualizer


画图工具:https://observablehq.com/@observablehq/graphviz