JavaScript には参照変数(=エイリアス)にあたるものはあるという話
前回は arguments[n] と 対応した仮引数に 参照関係がある話でしたが、今回は esmodule に於ける export した変数は参照変数という話をします。
module を import するということ
まず、 変数 と その setter を公開します。
export let value;
export function setValue(v) {
value = v;
}
ここでなぜ setter が必要だというと、 esmodule に於いて、 export した それぞれ は const ……つまり上書きによる元の値への反映ができないからです。
具体的にはこう
import { value } from "./module.js";
value = 1; // -> Assignment to constant variable.
let で宣言して export しようとも それは 受け取る側では const です。
いや、そもそも function であろうと同じなので それはそう です。
import { setValue } from "./module.js";
setValue = 2; // -> Assignment to constant variable.
ちなみにこれは dynamic import の直下のプロパティでも同様です。
const module = await imprt('./module.js');
module.value = 3; // -> Cannot assign to read only property 'value' of object '[object Module]'
本題
Import した 変数(関数も変数という扱いにつき)は 再代入禁止なのがわかったところで今回の本題、
ソースはこちら
https://gist.github.com/juner/ed21a255fac53ec03318e589b703fb41
動作するサンプルへのリンク
https://gistcdn.githack.com/juner/ed21a255fac53ec03318e589b703fb41/raw/
import { format, addMessage } from "./sub.js";
import { value as newValue, setValue } from "./module.js";
// #region set
{
const name = "set";
const type = "set";
setValueButton.addEventListener('click', () => {
const time = format.format(new Date());
const value = Symbol(`${time}`);
setValue(value);
const dataset = { type, time, description: value.description };
addMessage({ name, time, dataset });
});
}
// #endregion
// #region change 1
(async () => {
const name = "change 1";
const type = "change1";
const timeout = null;
let oldValue;
while (true) {
await new Promise(resolve => setTimeout(resolve, timeout));
if (oldValue === newValue) continue;
console.log('%o: %O', name, newValue);
oldValue = newValue;
const time = format.format(new Date());
const dataset = { type, time, description: newValue.description };
addMessage({ name, time, dataset });
}
})();
// #endregion
// #region change 2
(async () => {
const name = "change 2";
const type = "change2";
const timeout = null;
let oldValue;
const module = await import('./module.js');
while (true) {
await new Promise(resolve => setTimeout(resolve, timeout));
if (oldValue === module.value) continue;
console.log('%o: %O', name, module.value);
oldValue = module.value;
const time = format.format(new Date());
const dataset = { type, time, description: module.value.description };
addMessage({ name, time, dataset });
}
})();
// #endregion
注目すべきは以下
set
setValue() を呼び出して値の設定を行います。
画面でいうところの call set value
ボタンの挙動のところです。
change 1
Import 経由で とってきた newValue もとい value の値を永久ループで確認します。
(参照変数の変更をチェックしています)
change 2
dynamic import 経由でとってきた module.value の値を 永久ループで確認します。
(こっちは 参照変数 ではなく プロパティの更新確認ではありますが、動作の違いが無いかの確認用です)
結果
参照変数の特性である エイリアスの機能としてちゃんとボタンを押すことでそれぞれのチェックが動作しているところがわかると思います。
(※時間出してるのはチェックタイミングの確認用で、設定された値の確認用ではありません。)
参考
インポートした値はエクスポートしたモジュールだけが変更できる
インポートした識別子は「動的バインド」と呼ばれます。エクスポートしているモジュールが再代入するとインポートしている値も変わるからです。しかしながら、当の変数をインポートしているモジュールは再代入できません。それでも、エクスポートしたオブジェクトを保持しているモジュールは、インポートしたオブジェクトを書き換えることができますし、変更した値は同じ値をインポートしているすべてのモジュールが観測できるようになっています。