Rhino scopes and contexts

この翻訳は不完全です。英語から この記事を翻訳 してください。

Rhinoをマルチスレッドの環境で使う前に、コンテキストとスコープの間の関係を理解することが大切です。両方ともスクリプトを実行するのに必要ですが、それぞれは異なる役割を担います。簡単なRhinoの埋め込みであればここに書いてある情報はあまり必要ではないでしょう。しかしより複雑な埋め込みやパフォーマンスと柔軟性を上げるためにはこれらのことが役に立つでしょう。

コンテキスト

コンテキストオブジェクトは実行環境に関するスレッド固有の情報を保持するために使われます。それぞれのスレッドに関連付けられたJavaScript実行環境のコンテキストは一つだけであるべきです。

現在のスレッドをコンテキストに関連付けるためには、 enter メソッドを呼びます:

Context cx = Context.enter();

実行が終わったら、コンテキストを抜けます:

Context.exit();

これらのコールはすでに現在のスレッドにコンテキストが関連付けられていたとしても、正しく動作します。これは内部的にカウンターをインクリメントします。カウンターが0になると、スレッドからの関連付けが解除されます。

exit() の呼び出しは実行中に例外が発生しても大丈夫なように、 finally ブロックで行うようにしてください。

スコープ

スコープはJavaScriptオブジェクトのセットです。スクリプトの実行には FunctionObject などのオブジェクトを保持しておくためのトップレベルスクリプト変数スコープを必要とします。

スコープはそれを作成したコンテキストとは独立しているということを理解することが大事です。あるコンテキストでスコープを作り、それを別のコンテキストでスクリプトを評価することが可能です。(一度コンテキストを抜けて再度入った場合や、別のスレッドで実行する場合など) 同じスコープに関して複数のスレッドで同時にスクリプトを実行することもできます。RhinoはJavaScriptオブジェクトのプロパティへのアクセスの原始性を保証します。しかしそれ以外の同時に実行されるスクリプトの保証はしません。もし二つのスクリプトが同じスコープを同時に使用する場合、スクリプトは共有変数へのアクセスを制御する必要があります。

トップレベルスコープは Context.initStandardObjects で作れます。

ScriptableObject scope = cx.initStandardObjects();

Rhinoを埋め込む最も簡単な方法は、必要に応じてこの方法で単に新しいスコープを作ることです。しかし initStandardObjects は高価なメソッドで、たくさんのメモリを確保します。複数のスコープやスレッドで作成したスコープを共有する方法は、後述します。

名前ルックアップ

ではどのようにスコープは名前を見つけるのに使われるのでしょう?簡単に言えば、変数は現在の変数から始まって (これはプログラム中でどんなコードが実行されるかによります)、プロトタイプチェインを辿ります。そして親子チェインを辿ります。下の図では、6個のオブジェクトが辿られる様子が描かれています。

Order of lookups in a two-deep scope chain with prototypes.

より具体的な例として、次のスクリプトを考えてみましょう:

var g = 7;

function f(a) {
  var v = 8,
      x = v + a;
}

f(6);

トップレベル変数 g があり、 f の呼び出しは新たなトップレベル変数 x を作成します。全てのトップレベル変数はスコープオブジェクトのプロパティです。 f の実行を開始するとき、スコープチェインは関数のアクティベーションオブジェクトから開始し、トップレベルスコープで終わります (下図参照)。アクティベーションオブジェクトは二つのプロパティを持っています。'a' は引数、'v' は変数です。トップレベルスコープは変数 g と関数 f を持っています。

シンプルなスクリプトのスコープチェインの例

x = v + a; というステートメントが実行されるとき、スコープチェインが辿られ 'x' プロパティを探します。何も見つからなければ新たな'x' プロパティがトップレベルスコープに作られます。

スコープの共有

JavaScriptはクラスベースの継承よりも委譲を使う言語です。これはそれ自体が大きなトピックですが、これにより複数のスコープで読み取り専用の変数を共有する簡単な方法が与えられます。

これを行うためには、オブジェクトのプロトタイプを設定します。JavaScriptでオブジェクトのプロパティにアクセスするとき、与えられた名前をまずオブジェクトのプロパティから探します。もし見つからなければ、オブジェクトのプロトタイプを探しにいきます。これはプロトタイプチェインの最後に到達するまで続きます。

なので、複数のスコープで情報を共有する場合、まず共有したいオブジェクトを作成します。通常このオブジェクトは initStandardObjects で作られます。組み込み用にいくつかの追加のオブジェクトを持つかもしれません。そしてさらに新しいオブジェクトを作成して、そのオブジェクトの setPrototypemethod を呼び、共有オブジェクトをプロトタイプに設定します。そして新しいスコープの親はnullにします:

Scriptable newScope = cx.newObject(sharedScope);
newScope.setPrototype(sharedScope);
newScope.setParentScope(null);

The call to newObject simply creates a new JavaScript object with no properties. It uses thesharedScope passed in to initialize the prototype with the standard Object.prototype value.

これで newScope をスクリプトを評価するスコープとして使うことができます。このスコープをインスタンススコープと呼びましょう。スクリプトで定義される全てのトップレベル関数や変数はインスタンススコープのプロパティになります。Uses of standard objects like Function, String, or RegExp will find the definitions in the shared scope. Multiple instance scopes can be defined and have their own variables for scripts yet share the definitions in the shared scope. These multiple instance scopes can be used concurrently.

Sealed shared scopes

The ECMAScript standard defines that scripts can add properties to all standard library objects and in many cases it is also possible to change or delete their properties as well. Such behavior may not be suitable with shared scopes since if a script by mistake adds a property to a library object from the shared scope, that object would not be garbage collected until there are no active references to the shared scope potentially leading to memory leaks. In addition if a script alters some of the standard objects, the library may not work properly for other scripts. Such bugs are hard to debug and to remove a possibility for them to occur one can seal the shared scope and all its objects.

A notion of a sealed object is a JavaScript extension supported by Rhino and it means that properties can not be added/deleted to the object and the existing object properties can not be changed. Any attempt to modify sealed object throws an exception. To seal all objects in the standard library passtrue for the sealed argument when calling Context.initStandardObjects(ScriptableObject, boolean):

ScriptableObject sealedSharedScope = cx.initStandardObjects(null, true);

This seals only all standard library objects, it does not seal the shared scope itself thus after callinginitStandardObjects, sealedSharedScope can be farther populated with application-specific objects and functions. Then after a custom initialization is done, one can seal the shared scope by callingScriptableObject.sealObject():

sealedSharedScope.sealObject();

Note that currently one needs to explicitly seal any additional properties he adds to the sealed shared scope since although after calling sealedSharedScope.sealObject(); it would no be possible to set the additional properties to different values, one still would be able to alter the objects themselves.

Note that currently in order to use Java classes (LiveConnect) from a sealed shared scope you need to pre-load a number of objects needed for LiveConnect into the scope before it gets sealed. These objects would normally be lazy loaded but the lazy loading fails if the scope is sealed.

ScriptableObject sealedSharedScope  = cx.initStandardObjects(null, true);

// Force the LiveConnect stuff to be loaded. 
String loadMe = "RegExp; getClass; java; Packages; JavaAdapter;";
cx.evaluateString(sealedSharedScope , loadMe, "lazyLoad", 0, null);
sealedSharedScope .sealObject();

Dynamic Scopes

There's one problem with the setup outlined above. Calls to functions in JavaScript use static scope, which means that variables are first looked up in the function and then, if not found there, in the lexically enclosing scope. This causes problems if functions you define in your shared scope need access to variables you define in your instance scope.

With Rhino 1.6, it is possible to use dynamic scope. With dynamic scope, functions look at the top-level scope of the currently executed script rather than their lexical scope. So we can store information that varies across scopes in the instance scope yet still share functions that manipulate that information reside in the shared scope.

The DynamicScopes example illustrates all the points discussed above.

More on Scopes

The key things to determine in setting up scopes for your application are

  1. What scope should global variables be created in when your script executes an assignment to an undefined variable, and
  2. What variables should your script have access to when it references a variable?

The answer to 1 determines which scope should be the ultimate parent scope: Rhino follows the parent chain up to the top and places the variable there. After you've constructed your parent scope chain, the answer to question 2 may indicate that there are additional scopes that need to be searched that are not in your parent scope chain. You can add these as prototypes of scopes in your parent scope chain. When Rhino looks up a variable, it starts in the current scope, walks the prototype chain, then goes to the parent scope and its prototype chain, until there are no more parent scopes left.

ドキュメントのタグと貢献者

 このページの貢献者: niusounds
 最終更新者: niusounds,