この記事では、seajs モジュール間の依存関係の読み込みとモジュールの実行について説明します。以下では詳しく説明しません。
エントリーメソッド
どのプログラムにも c の main 関数と同様のエントリーメソッドがあり、seajs も例外ではありません。シリーズ 1 のデモでは、ホームページのエントリー方法である seajs.use() を使用しています。エントリ メソッドは 2 つのパラメータを受け入れることができます。最初のパラメータはモジュール名、2 番目のパラメータはコールバック関数です。エントリ メソッドは新しいモジュールを定義します。この新しく定義されたモジュールは、入力パラメータによって提供されるモジュールに依存します。次に、ロードされた状態の後に新しいモジュールのコールバック関数が呼び出されるように設定します。このコールバック関数は主にすべての依存モジュールのファクトリ関数を実行し、最後にエントリ メソッドによって提供されるコールバックを実行します。
// Public API // 入口地址 seajs.use = function(ids, callback) { Module.preload(function() { Module.use(ids, callback, data.cwd + "_use_" + cid()) }) return seajs } // Load preload modules before all other modules Module.preload = function(callback) { var preloadMods = data.preload var len = preloadMods.length if (len) { Module.use(preloadMods, function() { // Remove the loaded preload modules preloadMods.splice(0, len) // Allow preload modules to add new preload modules Module.preload(callback) }, data.cwd + "_preload_" + cid()) } else { callback() } } // Use function is equal to load a anonymous module Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod.callback = function() { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } // 回调函数的入参对应依赖模块的返回值 if (callback) { callback.apply(global, exports) } delete mod.callback } mod.load() }
Module.preload は、seajs が提供するプラグインをプリロードするために使用されます。これはメイン関数ではないため、無視できます。 Module.use はコア メソッドです。前述したように、このメソッドは新しいモジュールを作成し、コールバック関数を設定し、最後に新しいモジュールのすべての依存モジュールをロードします。
依存関係を読み込むloadメソッド
loadメソッドはseajsの本質とも言えます。このメソッドは主に依存モジュールをロードし、依存モジュールのコールバック関数を順番に実行します。最後のコールバック関数は、seajs.use("./name") によって作成された新しいモジュールのコールバック (mod.callback) です。
load メソッドは、依存モジュールを再帰的にロードします。依存モジュールが他のモジュールにも依存している場合は、このモジュールをロードします。これは、Module クラスの _waitings と _remain によって実現されます。
Module.prototype.load = function() { var mod = this // If the module is being loaded, just wait it onload call if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // Emit `load` event for plugins such as combo plugin var uris = mod.resolve() emit("load", uris, mod) var len = mod._remain = uris.length var m // Initialize modules and register waitings for (var i = 0; i < len; i++) { m = Module.get(uris[i]) // 修改 依赖文件 的 _waiting属性 if (m.status < STATUS.LOADED) { // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1 m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1 } else { mod._remain-- } } // 加载完依赖,执行模块 if (mod._remain === 0) { mod.onload() return } // Begin parallel loading var requestCache = {} for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 该依赖并未加载,则先fetch,将seajs.request函数绑定在对应的requestCache上,此时并未加载模块 if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { m.load() } } // Send all requests at last to avoid cache bug in IE6-9. Issues#808 // 加载所有模块 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { // 此时加载模块 requestCache[requestUri]() } } } // 依赖模块加载完毕执行回调函数 // 并检查依赖该模块的其他模块是否可以执行 Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED if (mod.callback) { mod.callback() } console.log(mod) // Notify waiting modules to fire onload var waitings = mod._waitings var uri, m for (uri in waitings) { if (waitings.hasOwnProperty(uri)) { m = cachedMods[uri] m._remain -= waitings[uri] if (m._remain === 0) { m.onload() } } } // Reduce memory taken delete mod._waitings delete mod._remain }
まず、モジュールの _waitings 属性と _remain 属性を初期化します。_remain が 0 の場合は、依存関係がないか、依存関係がロードされていることを意味します。0 でない場合は、onload 関数を実行できます。アンロードされたモジュール。ここには、すべての依存関係を同時にロードするためのちょっとした実装のコツがあります: requestCache オブジェクトは、ロード関数を保存します: (fetch 関数で定義)
if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() }
その中で、sendRequest 関数は次のように定義されています。 :
function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset) }
すべての依存関係を並列ロードします。依存関係がロードされると、onRequest コールバックが実行され、依存モジュールがなくなるまで依存関係をロードします。
最上位の依存関係がモジュールに依存しなくなったら、onload 関数を実行し、関数本体でステータスをロード済みに設定し、mod.callback を実行して、モジュールの _waitings 属性を確認して設定し、下位レベルの依存関係がモジュールに依存しているかどうかを判断します。モジュールにまだ依存関係がある場合、下位モジュールの mod.callback が実行され、最終的には seajs.use を通じて作成された匿名モジュールの mod.callback が実行されます。
例
簡単な例を使用して、上記のプロセスを示します:
tst.html <script> seajs.use('./b'); </script> ------------------------------------- a.js define(function(require,exports,module){ exports.add = function(a,b){ return a+b; } }) ------------------------------------ b.js define(function(require,exports,module){ var a = require("./a"); console.log(a.add(3,5)); })
デバッグ ツールを通じて、onload が実行される順序を確認できます:
最後に、匿名モジュールのステータスコードは 4 、つまりモジュールは実行されていません。実際、匿名モジュールにはファクトリ関数が定義されていないため、モジュール実行の
Exec が呼び出されます。 seajs.use で定義された mod.callback 内で順番に呼び出され、依存するすべての exec メソッドがプログラム ロジックを実行します。 exec メソッドには、require、exports などの commonJS の重要なキーワードまたは関数がいくつかあります。見てみましょう:
Module.prototype.exec = function () { var mod = this // When module is executed, DO NOT execute it again. When module // is being executed, just return `module.exports` too, for avoiding // circularly calling if (mod.status >= STATUS.EXECUTING) { return mod.exports } mod.status = STATUS.EXECUTING // Create require var uri = mod.uri function require(id) { return Module.get(require.resolve(id)).exec() } require.resolve = function(id) { return Module.resolve(id, uri) } require.async = function(ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } // Exec factory var factory = mod.factory // 工厂函数有返回值,则返回; // 无返回值,则返回mod.exports var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory if (exports === undefined) { exports = mod.exports } // Reduce memory leak delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED // Emit `exec` event emit("exec", mod) return exports }
require 関数はモジュールを取得し、モジュールのファクトリ関数を実行して、戻り値。 require 関数のsolve メソッドは、対応するモジュール名の絶対 URL を取得し、require 関数の async メソッドは依存関係を非同期に読み込み、コールバックを実行します。ファクトリ メソッドの戻り値の場合、ファクトリ メソッドがオブジェクトの場合は、exports の値になります。ファクトリ メソッドに戻り値がある場合は、exports の値または module.exports の値になります。輸出額。エクスポート値が取得できたら、ステータスを実行済みに設定します。
注意すべき点: オブジェクトに値を代入してエクスポートする場合
define(function(require,exports,module){ exports ={ add: function(a,b){ return a+b; } } })
が失敗する場合 まず、上記のメソッドを実行して、最終的にエクスポートされたエクスポートの値を判断します。関数は値を返しません。第二に、mod.exports は未定義であり、最終的にエクスポートされる ,exports は未定義です。なぜこのようなことが起こるのでしょうか? jsでの参照代入が原因です。 jsの代入方針は「共有による受け渡し」です。最初はexports === module.exportsですが、オブジェクトがexportsに代入されると、exportsはオブジェクトを指しますが、module.exportsはまだ初期化されておらず、未定義であるため、何かがうまくいかないでしょう。
正しい書き方は
define(function(require,exports,module){ module.exports ={ add: function(a,b){ return a+b; } } })
です。
概要
seajsのコアモジュールの実装について多くのコーディングスキルを見て、その創意工夫を評価したと言えます。コールバック モードと微妙な点の考慮。コードのすべての部分でメモリ リークとこのポインタ参照オフセットの危険性が考慮されており、この精神は学ぶ価値があります。