コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

利用者: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上での名前」が定義されている必要がある。

  • testPackageLoader[ResourceLoader|package]|testPackageLoader.js|testPackage.js
  • testPackage[ResourceLoader|hidden]|testPackage.js

モジュールの名称一覧は、mw.loader.getModuleNamesでも確認可能。なお、上の「他言語版のガジェット」の読み込みで手数が多くなるのは、他言語版のガジェットの時点でローカルにはext.gadget.testPackageというモジュールがないため、まずローカルにモジュールとして取り込む (mw.loader.getScript) 操作をしないとext.gadget.testPackageがモジュールとして認識されず、mw.loader.usingがエラーを吐くため。