単一のグローバル変数なしでプロジェクトを構築する

複数のモジュールと、たとえばjQueryまたはCDN内の他のライブラリで構成されるプロジェクトがあるとします。 ユーザーにグローバル変数を表示せず、可能であればjQueryと$を表示しないことを強く望んでいます。 もちろん、プロジェクトコードを変更せずにすべてを行います。

グローバルを非表示にする理由は異なる場合があります。美しさ、セキュリティ上の理由、コード分析の難しさなどです。 ユーザーは、自分が破ることができないイベントを使用してコードを操作します。他に何も必要ありません。



最も明白な方法は、他のオブジェクトを受動的にエクスポートする単一の名前空間を作成し、最後にjQueryと$を削除することです。



アセンブリ後、コードは次のようになります。

(function(window, undefined){ // include ./js/YourNamespace.js var YourNamespace = (function () { // -  return {}; }()); // include ./js/YourNamespace/SomeObject.js YourNamespace.SomeObject = (function () { // -  return function () { }; }()); // Cleanup delete window.$; delete window.jQuery; }(window));
      
      





これは理想的ですが、大抵はそうではありません。 あなたのコードを見て、そうですか?



アンダーカットは、単一のグローバル変数なしで任意のコードを収集できる汎用ソリューションです。



コンセプトアクティブ/パッシブインポート/エクスポート



アクティブなエクスポート

 (function (window) { var ModuleA = {}; // Export window.ModuleA = ModuleA; }(window));
      
      



ModuleAモジュールはアクティブにエクスポートされます



パッシブエクスポート

 var ModuleC = (function (window) { var ModuleC = {}; // Export return ModuleC; }(window));
      
      



ModuleCモジュールは受動的にエクスポートされます



アクティブなインポート

 (function (window, $) { console.log($); }(this, jQuery));
      
      



JQueryモジュールはアクティブにインポートされます



パッシブインポート

 (function (window) { console.log(ModuleC); }(this));
      
      



ModuleCモジュールは受動的にインポートされます



組立



とにかくインポート/エクスポートするモジュールがいくつかあります。 それぞれが使用するモジュールを知っています。 各モジュールをクロージャでラップして、使用するすべてのオブジェクトのリストを転送できます。 確かに、小さな問題があります:モジュールAがモジュールBを使用し、モジュールBがモジュールAを使用し、それらがすべて直列に接続されている場合、ループにラップされたモジュールAを接続すると、ReferenceErrorエラーが発生します-モジュールBはまだありません この問題は、モジュールBをロードした後にオブジェクトを回路Aに転送することで解決します。モジュールが受動的にエクスポートされ、回路でラップすると、グローバル変数は消えます。 この問題は、ローカル変数をグローバルコンテキストに転送することで解決されます。

スクリプトの初期化中に作成したグローバル変数のリストを知っています-グローバルをクリアするには、deleteを使用してそれらを削除します。





ランダムにインポート/エクスポートするjQueryと3つのモジュールで構成されるサンプルプロジェクトを見てみましょう。



モジュールリスト


 // Uses ModuleC, $ (function (window) { var ModuleA = { a: 'ModuleA.a', b: 2, d: function () { console.log(ModuleC.c === 'ModuleC.c'); console.log(typeof $ === 'function'); } }; // Export window.ModuleA = ModuleA; }(window));
      
      





 // Uses ModuleA var ModuleC = (function (window) { var ModuleC = { a: 1, b: 2, c: 'ModuleC.c', d: function () { console.log(ModuleA.a === 'ModuleA.a'); } }; // Export return ModuleC; }(window));
      
      





 // Uses ModuleA, ModuleC ModuleA.c(); window.setTimeout(function () { ModuleC.d(); ModuleA.d(); }, 0);
      
      





モジュールは、jQuery個別、ModuleA + ModuleC + ModuleDの順序で接続されます。 モジュールAはモジュールCに依存し、モジュールCはモジュールAに依存します(上記の競合)。 モジュールAは積極的にエクスポートし、モジュールCはそれ自体を受動的にエクスポートし、モジュールDは何もエクスポートしません。



集める


 //     (function(window, undefined){ //       var Medium = { wait: function (varName, callback) {/* -   */}, ready: function (varName, varValue) {/* -   */} }; //   A,      (function (ModuleC, $) { // ..  ModuleC   ,    Medium.wait('ModuleC', function (value) {ModuleC = value;}); (function (window) { var ModuleA = { a: 'ModuleA.a', b: 2, d: function () { console.log(ModuleC.c === 'ModuleC.c'); console.log(typeof $ === 'function'); } }; // Export window.ModuleA = ModuleA; }(window)); } (undefined, $)); //   C,      (function (ModuleA) { var ModuleC = (function (window) { var ModuleC = { a: 1, b: 2, c: 'ModuleC.c', d: function () { console.log(ModuleA.a === 'ModuleA.a'); } }; // Export return ModuleC; }(window)); // ..  ModuleC  ,      ,    window.ModuleC = ModuleC; //   C  -      Medium.ready('ModuleC', ModuleC); } (ModuleA)); //   ,      (function (ModuleA,Module) { // Uses ModuleA, ModuleC ModuleA.c(); window.setTimeout(function () { ModuleC.d(); ModuleA.d(); }, 0); } (ModuleA,Module)); //      -         try { delete window.$; delete window.jQuery; delete window.ModuleA; delete window.Module; } catch (e){ // IE  window.$ = undefined; window.jQuery = undefined; window.ModuleA = undefined; window.Module = undefined; } //          ! }(window));
      
      





変数キャプチャの問題



このソリューションは、コードを分析し、不必要な変数のグローバルコンテキストをクリアすることを難しくするだけです(美しさのため)。

スクリプト(通常のユーザースクリプトまたは拡張機能)を接続する前に組み込まれたスクリプトが、watch、__ defineSetter __、ES5 setを使用して、場合によっては最小setIntervalを介して削除する前に、ModuleAおよびModuleオブジェクトをインターセプトできることは秘密ではありません。



この問題には3つの解決策があります。

1.アクティブなエクスポートを使用しないでください(キャプチャの機会はゼロです。コードを変更する必要があります)

2.グローバル変数にランダムな名前を使用します(キャプチャチャンスはゼロになる傾向があるため、コードを変更する必要があります)

3.組み立てる前にオブザーバーとタイマーを削除します(キャプチャチャンスはゼロになります)



方法3を使用して、アセンブリを変更します。

 //     (function(window, undefined){ //  __defineSetter__, ES5 set, watch window.unwatch && window.unwatch('ModuleA'); window.unwatch && window.unwatch('Module'); try { delete window.ModuleA; delete window.Module; } catch (e){ // IE  window.ModuleA = undefined; window.Module = undefined; } //   var maxIntervalId = window.setInterval(function (){}, 1e10); var maxTimeoutlId = window.setTimeout(function (){}, 1e10); while (maxIntervalId--) { window.clearInterval(maxIntervalId); } while (maxTimeoutlId--) { window.clearTimeout(maxTimeoutlId); } //    // ... }(window));
      
      





これで、JavaScript Ninjaの最も洗練されたテクニック-「グローバル変数の非表示」をマスターできました!



例外


このメソッドは、特にグローバルに散らばる場合、onsmth = "..."を介してイベント呼び出しを使用する場合には役立ちません。つまり、 不十分な動作;)グローバルにオブジェクトがない場合、メソッドは機能しません。



Ninja JavaScript Builder-Ninjs



このアセンブリメソッドの実装を書く必要がないように、私はエース-Ninjsを書きました。 これは、上記の方法を使用したJavaScriptプロジェクトビルダーです。 ビルダーは、彼の名前を正当化します-忍者として、彼はいつの間にか自分の仕事をし、トラックを整理します。



プロジェクトはgithubにありますgithub.com/azproduction/ninjs

Node.jsを使用してビルドします。

npmにはまだ登録されていませんが、確実に登録されます。

彼が変数のキャプチャを防ぐことができるまで-になります。



使用する


Ninjsの使用は非常に簡単です-モジュールがインポート/エクスポートする変数とそれらが存在する場所を知る必要があります。 すべてのNinjs依存関係が解決されます。

 var ninjs = new (require('../Ninjs.js').Ninjs); ninjs .add({ //  ModuleA file: './files/ModuleA.js', //    ModuleC  ModuleB imports: ['ModuleC', 'ModuleB'], //   ModuleA exports: 'ModuleA' }) .add({ //  ModuleB file: './files/ModuleB.js', //    ModuleA    jQuery imports: 'ModuleA', //   ModuleB exports: 'ModuleB' }) .add({ //  ModuleD file: './files/ModuleD.js', //    ModuleA, ModuleB and ModuleC imports: ['ModuleA', 'ModuleB', 'ModuleC'] //    }) .add({ //  ModuleC file: './files/ModuleC.js', //    ModuleA, ModuleB imports: ['ModuleA', 'ModuleB'], //    ModuleC exports: 'ModuleC', //  ModuleC   ,     forceExports: 'ModuleC' }) //   .cleanup('ModuleA', 'ModuleB', 'ModuleC', '$', 'jQuery') //  STDOUT .print(true); //         ,    // .print(false);
      
      





サンプルコードとモジュールコードはこちらgithub.com/azproduction/ninjs/tree/master/examples



お楽しみに。 提案、希望、批判を歓迎します!



PS私はそれをNinjsのロゴに変えるためにNinjaアイコンを探しています。



All Articles