이 글에서는 seajs 모듈 간의 종속성 로딩과 모듈 실행을 소개합니다. 아래에서는 자세히 설명하지 않겠습니다.
입력 방법
모든 프로그램에는 c의 주요 기능과 유사한 입력 방법이 있으며, seajs도 예외는 아닙니다. 시리즈 1의 데모에서는 홈페이지의 seajs.use()를 진입 방식으로 사용하고 있습니다. 진입 방법은 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는 앞서 언급한 것처럼 새로운 모듈을 생성하고 콜백 함수를 설정한 후 최종적으로 새로운 모듈의 모든 종속 모듈을 로드하는 핵심 메서드입니다.
의존성 로딩의 로드 방식
로드 방식은 seajs의 핵심이라고 할 수 있습니다. 이 메소드는 주로 종속 모듈을 로드하고 종속 모듈의 콜백 함수를 순차적으로 실행합니다. 최종 콜백 함수는 seajs.use("./name")을 통해 생성된 새 모듈의 콜백인 mod.callback입니다.
load 메소드는 종속 모듈을 재귀적으로 로드합니다. 종속 모듈도 다른 모듈에 종속되는 경우 이 모듈을 로드합니다. 이는 모듈 클래스의 _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이면 종속성이 없거나 종속성이 로드되었음을 의미합니다. onload 함수를 실행할 수 있습니다. 0이 아닌 경우 언로드된 모듈을 가져옵니다. 여기에는 모든 종속성을 동시에 로드하는 약간의 구현 트릭이 있습니다. 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)); })
디버깅을 통해 확인할 수 있습니다. tools onload 실행 순서 표시:
마지막으로 익명 모듈의 상태 코드가 4로, 실제로 모듈이 실행되지 않았음을 알 수 있습니다. 익명 모듈에 정의된 팩토리 함수가 없습니다.
모듈 실행 실행
모듈 실행은 seajs.use에 정의된 mod.callback과 모든 종속 실행 메서드에서 호출됩니다. 프로그램 로직을 실행하기 위해 순서대로 호출됩니다. exec 메서드에는 require, 내보내기 등과 같은 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 함수의 resolve 메소드는 해당 모듈 이름의 절대 URL을 획득하고, require 함수의 async 메소드는 의존성을 비동기적으로 로드하고 콜백을 실행합니다. 팩토리 메소드의 반환 값의 경우 팩토리 메소드가 객체인 경우 이는 내보내기 값이고, 팩토리 메소드에 반환 값이 있는 경우 이는 내보내기 값이거나 module.exports의 값입니다. 수출 가치. 내보내기 값을 얻을 수 있으면 상태를 실행으로 설정합니다.
주의할 점: 내보내기에 값을 할당하여 개체를 내보내려는 경우
define(function(require,exports,module){ exports ={ add: function(a,b){ return a+b; } } })
이 실패했다고 판단합니다. 위의 방법을 실행하여 최종적으로 내보낸 내보내기의 값입니다. 첫째, 함수에 반환 값이 없습니다. 둘째, mod.exports가 정의되지 않았고 최종 내보낸 내보내기가 정의되지 않았습니다. 왜 이런 일이 발생합니까? 이는 js의 참조 할당으로 인해 발생합니다. js의 할당 전략은 "공유를 통한 전달"입니다. 처음에는 내보내기 === module.exports이지만 객체를 내보내기에 할당하면 내보내기가 객체를 가리키지만 module.exports는 아직 초기화되지 않았으며 정의되지 않았습니다. 뭔가 잘못될 것이다.
를 올바르게 쓰는 방법은
define(function(require,exports,module){ module.exports ={ add: function(a,b){ return a+b; } } })
요약
이라고 할 수 있습니다. seajs 모듈에 대해 설명하면서 많은 코딩 기술을 보았고 콜백 모드의 독창성과 미묘함을 고려했습니다. 코드의 모든 부분은 메모리 누수의 위험과 이 포인터 참조 오프셋을 고려하고 적극적인 예방 조치를 취합니다.