seajs モジュール間の依存関係の読み込みとモジュールの実行

高洛峰
リリース: 2016-12-09 14:21:50
オリジナル
1140 人が閲覧しました

この記事では、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&#39;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(&#39;./b&#39;);
</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のコアモジュールの実装について多くのコーディングスキルを見て、その創意工夫を評価したと言えます。コールバック モードと微妙な点の考慮。コードのすべての部分でメモリ リークとこのポインタ参照オフセットの危険性が考慮されており、この精神は学ぶ価値があります。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート