모듈 로딩은 실제로 개발과 유지 관리를 용이하게 하기 위해 js를 여러 모듈로 나누는 것입니다. 따라서 많은 js 모듈을 로드할 때 사용자 경험을 향상시키기 위해 동적으로 로드해야 합니다.
모듈 로딩 라이브러리를 소개하기 전에 먼저 메소드를 소개하겠습니다.
Situ Zhengmei는 자신이 작성한 대량 프레임워크를 사용하여 모듈 로딩을 도입하므로 업계에서 가장 일반적으로 사용되는 프레임워크는 require.js와 sea.js입니다. 그래서 성격이 강한 것 같아요.
sea.js의 모듈 로딩 과정에 대해 이야기하겠습니다.
chaojidan.jsp 페이지의 head 태그에 sea.js를 입력하면 seajs 객체를 얻게 됩니다.
index.js도 동시에 소개해보세요.
index.js의 코드는 다음과 같습니다.
위에서 알 수 있듯이 모듈 a는 b에 의존하고, b는 c에 의존합니다.
프로그램이 index.js에 들어가면 seajs가 use 메소드를 호출합니다.
seajs.use = 함수(id, 콜백) {
globalModule._use(ids, 콜백)
}
참고: seajs에 대해 globalModule이 초기화되면(sea.js가 도입된 경우) 모듈의 인스턴스는 var globalModule = new Module(util.pageUri, STATUS.COMPILED)
입니다.
이때 ids -> ['./a','jquery'], callback -> function(a,$){var num = a.a;$('#J_A').text(num);}
GlobalModule._use(ids, callback)
이 다음에 호출됩니다.
Module.prototype._use = 함수(id, 콜백) {
var uris = 해결(ids, this.uri); //Resolve['./a','jquery']
This._load(uris, function() { //파싱된 a, jquery 모듈의 주소 [url1, url2]를 계산하고 _load 메서드를 호출합니다.
//util.map: 모든 데이터 멤버가 지정된 함수를 한 번에 실행하도록 하고, 원래 배열 멤버가 실행한 콜백의 결과인 새 배열을 반환합니다
var args = util.map(uris, function(uri) {
return uri ?
})
if (콜백) { callback.apply(null, args) }
})
}
_load 메소드를 호출하면 두 개의 콜백 함수가 나타나기 때문에 function(a,$){var num = a.a;$('#J_A').text(num);}를 callback1로 표시합니다.
this._load(uris, function() { }) 콜백 메서드를 callback2
로 표시하세요.
해결 방법은 모듈 주소를 해결하는 것이므로 여기서는 자세히 설명하지 않겠습니다.
마지막으로 var uris =solve(ids, this.uri)의 uri는 ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery로 구문 분석됩니다. /1.7.2/juqery-debug.js'], 모듈 경로 해결이 완료되었습니다.
그리고 this._load
가 다음에 실행됩니다.
// _load() 메소드는 주로 아직 준비되지 않은 리소스 파일을 확인합니다. 모든 리소스 파일이 준비 상태이면 callback2
가 실행됩니다.
// 이 과정에서 순환 종속성도 판단되어 언로드된 js가 로드됩니다
Module.prototype._load = function(uris, callback2) {
//util.filter: 모든 데이터 멤버가 지정된 함수를 한 번에 실행하고 새 배열을 반환하도록 합니다. 이는 원래 배열 멤버가 콜백을 실행한 후 true를 반환하는 멤버입니다
//unLoadedUris는 컴파일되지 않은 모듈 uri 배열입니다.
var unLoadedUris = util.filter(uris, function(uri) {
//실행 함수의 불리언 값이 true인 멤버를 반환한다. uri가 존재하고 내부 변수인 캐시Modules에 존재하지 않거나, 저장된 정보의 상태 값이 STATUS.READY
보다 작은 경우 true를 반환한다.
// STATUS.READY 값은 4입니다. 4보다 작으면 해당 값을 얻거나 다운로드하는 중일 가능성이 있습니다.
uri && (!cachedModules[uri] ||
반환
캐시된 모듈[uri].status
});
//uris의 모든 모듈이 준비되면 콜백을 실행하고 함수 본문을 종료합니다. (이때 모듈의 _compile 메소드가 호출됩니다.)
var 길이 = unLoadedUris.length
if (길이 === 0) { callback2() return }
//아직 로드되지 않은 모듈의 개수
var 유지 = 길이
//클로저를 생성하고 로드되지 않은 모듈을 로드해 봅니다
for (var i = 0; i < length; i ) {
(함수(uri) {
//uri의 저장정보가 내부 변수인 cashedModules에 존재하지 않는지 확인하고, 모듈 객체를 인스턴스화
var module = cashedModules[uri] ||
(cachedModules[uri] = 새 모듈(uri, STATUS.FETCHING))
//모듈의 상태 값이 2보다 크거나 같으면 모듈이 다운로드되어 이미 로컬에 존재한다는 의미입니다. 이때 onFetched()
가 실행됩니다.
//그렇지 않으면 fetch(uri, onFetched)를 호출하고 리소스 파일 다운로드를 시도합니다. 리소스 파일이 다운로드된 후 onload가 트리거되고 onload에서 onFetched 콜백 메서드가 실행됩니다.
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
onFetched() 함수 {
모듈 = 캐시모듈[uri]
//모듈의 상태 값이 STATUS.SAVED보다 크거나 같다면 모듈의 모든 의존성 정보를 얻었음을 의미
if (module.status >= STATUS.SAVED) {
//getPureDependency: 순환 종속성 없이 종속성 배열 가져오기
var deps = getPureDependency(모듈)
//종속성 배열이 비어 있지 않은 경우
if (deps.length) {
//모든 종속성이 로드되고 콜백이 실행될 때까지 _load() 메서드를 다시 실행합니다
Module.prototype._load(deps, function() {
cb(모듈)
})
}
//종속성 배열이 비어 있으면 cb(module)
을 직접 실행합니다.
그 외 {
cb(모듈)
}
}
// 404 등 획득에 실패하거나 모듈 사양을 준수하지 않는 경우
//이 경우 module.status는 FETCHING 또는 FETCHED 상태로 유지됩니다
그 외 {
CB()
}
}
})(unLoadedUris[i])
}
// cb 메서드 - 모든 모듈을 로드한 후 실행되는 콜백
함수 cb(모듈) {
// 모듈의 저장 정보가 존재하는 경우 모듈 저장 정보의 상태 값을 STATUS.READY
로 수정합니다.
모듈 && (module.status = STATUS.READY)
// 모든 모듈이 로드된 경우에만 콜백을 실행합니다.
--remain === 0 && 콜백2()
}
}
}
여기서 unLoadedUris의 배열 길이는 2입니다. ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery- debug .js']이므로 js 경로로 명명된 두 개의 클로저가 다음에 생성됩니다.
http://localhost/test/SEAJS/a.js를 예로 들어
다음: 먼저 모듈이 생성됩니다:
캐시된모듈('http://localhost/test/SEAJS/a.js') = 새 모듈('http://localhost/test/SEAJS/a.js',1)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
이번에는 모듈 a가 로드되지 않았기 때문에 다음으로 fetch(uri, onFetched), 즉 fetch('http://localhost/test/SEAJS/a.js', onFetched)가 실행됩니다.
함수 가져오기(uri, onFetched) {
//지도의 규칙에 따라 URI를 새 요청 주소로 바꿉니다
var requestUri = util.parseMap(uri)
// 먼저, 획득한 목록에 requestUri 레코드가 있는지 확인
If (fetchedList[requestUri]) {
// 이때 맵을 통해 재정의한 requestUri로 원본 uri의 모듈 저장 정보를 새로 고칩니다.
캐시된모듈[uri] = 캐시된모듈[requestUri]
// onFetched를 실행하고 반환합니다. 이는 모듈이 성공적으로 획득되었음을 의미합니다.
onFetched()
반품
}
//획득 목록에서 requestUri의 저장 정보를 조회합니다
If (fetchingList[requestUri]) {
//콜백리스트에 uri에 해당하는 콜백을 추가하고
을 반환합니다.
CallbackList[requestUri].push(onFetched) //가져오는 중인 경우 이 모듈의 onFetched 콜백 메서드를 배열에 푸시하고 반환합니다.
반품
}
// 가져오려는 모듈이 fetchedList 및 fetchingList에 없으면 해당 정보를 요청 목록과 콜백 목록에 각각 추가합니다
fetchingList[requestUri] = true
callbackList[requestUri] = [onFetched]
// 가져옵니다
Module._fetch(
요청Uri
함수() {
fetchedList[requestUri] = true
// 모듈 상태 업데이트
// module.status가 STATUS.FECTCHING과 같은 경우 모듈 상태를 FETCHED로 수정합니다
var 모듈 = 캐시모듈[uri]
If (module.status === STATUS.FETCHING) {
module.status = STATUS.FETCHED
}
if (fetchingList[requestUri]) {
fetchingList 삭제[requestUri]
}
// callbackList 호출 콜백 통합 실행
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
Fn () // fn은 모듈 A에 해당하는 onfeched 메서드입니다.
})
콜백 목록 삭제[requestUri]
}
},
config.charset
)
}
다음으로 Module._fetch()가 실행됩니다. 여기서 콜백 함수는 callback3입니다.
loadJs 메소드를 호출하여 a.js 파일을 동적으로 다운로드하는 방법입니다. (a와 jquery가 있기 때문에 새로운 스크립트가 2개 생성됩니다.) 여기서 궁금한 점이 있는데, 새로운 스크립트를 생성해서 헤드에 추가하면 js 파일이 다운로드가 되지 않습니다. 다운로드되지만 jquery를 기다립니다. 스크립트는 생성되어 헤드에 추가된 후에만 다운로드됩니다(Google 디버거는 중단점을 설정하고 항상 보류 중으로 표시합니다). 왜 이런가요?
(여기에서 읽어보는 것이 좋습니다: http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff. 여기서는 추가 문제에 대해 이야기하겠습니다. 레이아웃에 테이블을 덜 사용해야 하는 이유를 알 수 있습니다. 레이아웃할 때 여러 계산이 필요하지만 div는 하나만 필요합니다. 동시에 Midea 전자상거래 면접관은 테이블이 표시되기 전에 완전히 구문 분석되어야 한다고 말했습니다. 태그는 tbody에 따라 세그먼트로 표시됩니다. 따라서 IE6, 7, 8에서는 innerHTML을 사용하여 "
"을 생성하면 < ;tbody><가 자동으로 추가됩니다.
다운로드가 성공한 후 구문 분석 및 실행되며 정의 메소드가 실행됩니다. 모듈 a의 코드가 먼저 실행됩니다.
정의(id,deps,function(){}) 메서드 분석
//정의 정의, id: 모듈 ID, deps: 모듈 종속성, 공장
Module._define = 함수(id, deps, 공장) {
//종속성 해결 //deps가 배열 유형이 아니고 Factory가 함수인 경우
if (!util.isArray(deps) && util.isFunction(factory)) { // 함수 본문의 필수 문자열을 규칙적으로 일치시키고 배열을 구성하여 반환하고 해당 값을 deps에 할당합니다
deps = util.parseDependency(factory.toString())
}
//메타정보 설정
var 메타 = { id: id, 종속성: deps, 공장: 공장 }
if (document.attachEvent) {
// 현재 스크립트의 노드를 가져옵니다
var script = util.getCurrentScript()
// 스크립트 노드가 존재하는 경우
if (스크립트) {
// 원래 URI 주소 가져오기
파생Uri = util.unParseMap(util.getScriptAbsoluteSrc(script)) }
if (!derivedUri) {
util.log('다음에 대한 대화형 스크립트에서 URI를 파생하지 못했습니다:', Factory.toString(), 'warn')
}
}
.........
}
define은 먼저 함수인지 판단하기 위해 공장에서 판단을 수행합니다. (이유는 Define이 파일과 객체도 포함할 수 있기 때문입니다.)
함수라면 Factory.toString()을 통해 함수를 얻고, 정규 매칭을 통해 a.js의 의존성을 얻고, 의존성은 deps에 저장됩니다
a.js의 경우 종속성은 b.js이므로 deps는 ['./b']
그리고 a.js var Meta = { id: id, dependency: deps, Factory: Factory }의 정보를 저장합니다
a.js 메타 = { id : 정의되지 않음, 종속성 : ['./b'] , 팩토리 : function(xxx){xxx}}
IE 6~9 브라우저에서는 현재 실행 중인 js의 경로를 얻을 수 있지만 표준 브라우저에서는 이것이 불가능하므로 임시로 익명모듈메타 = 메타에 메타정보를 할당합니다.
그러면 onload가 트리거되고 콜백 메서드인 callback3이 호출됩니다. 이 콜백 메서드는 현재 콜백 모듈(a.js)의 상태 값을 수정하고 이를 module.status = STATUS.FETCHED로 설정합니다.
다음으로 콜백 큐인 callbackList의 a.js에 해당하는 콜백이 균일하게 실행되는 onFetched가 됩니다.
onFetched 메서드는 모듈 a에 종속 모듈이 있는지 확인합니다. a가 b에 의존하기 때문에 _load()는 모듈 a가 의존하는 b.js에서 실행됩니다.
은 b 모듈을 다운로드한 다음 먼저 jquery의 정의 메소드를 실행합니다. jquery는 onload 콜백 이후에 모듈에 의존하지 않기 때문입니다. onFetched는 cb 메서드를 호출합니다.
b가 a와 동일한 프로세스에 따라 구현되면 c 모듈이 다운로드됩니다. 마지막으로 모듈 c, b, a가 모두 다운로드되어 실행됩니다.
모든 모듈이 준비된 후 callback2 메소드가 호출됩니다.
마지막으로 callback2에 대한 콜백, a 및 jquery 모듈의 _compile 메서드를 실행합니다.
먼저 a.js 모듈을 컴파일하면 a 모듈의 기능이 실행됩니다. a에는 require(b.js)가 포함되어 있으므로 b 모듈의 기능이 실행됩니다.
모듈 a의 기능이 실행되기 시작합니다
모듈 b의 기능이 실행되기 시작합니다
모듈 c의 기능이 실행되기 시작합니다
모듈 c의 기능이 실행됩니다
모듈 b의 기능이 실행됩니다
모듈 a의 기능이 실행됩니다
마지막으로 jquery 함수를 실행합니다.
컴파일이 완료된 후 callback1이 실행되고 a 및 jquery 객체를 사용할 수 있습니다.
PS: seajs 버전이 업데이트되었으며 이제 _compile 메소드가 없습니다. (다들 직접 가서 보시길, 저도 꼭 가보고 싶어요)
그럼 seajs의 모듈compile_compile 과정에 대해 이야기해보겠습니다.
첫 번째는 a.js 컴파일입니다
코드 복사 코드는 다음과 같습니다.
Module.prototype._compile = function() {
126 var 모듈 = 이
127 // 모듈이 컴파일된 경우 module.exports를 직접 반환합니다
128 if (module.status === STATUS.COMPILED) {
129 모듈 반환.수출
130 }
133 // 1. 모듈 파일은 404입니다.
134 // 2. 모듈 파일이 유효한 모듈 형식으로 작성되지 않았습니다.
135 // 3. 기타 오류 사례.
136 //여기서 비정상적인 상황을 처리하고 null을 직접 반환합니다
137 if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138 null을 반환
139 }
140 // 모듈 상태를 COMPILING으로 변경하여 모듈이 컴파일되고 있음을 나타냅니다.
141 모듈.상태 = 상태.컴파일 중
142
143 // 모듈 내에서 내부적으로 사용되며, 다른 모듈(서브모듈이라고 함)에서 제공하는 인터페이스를 얻어서 동기 연산을 수행하는데 사용하는 방식입니다
144 함수 require(id) {
145 // id에 따라 모듈의 경로를 파싱
146 var uri = 해결(id, module.uri)
147//모듈 캐시에서 모듈 가져오기(여기서 서브모듈은 실제로 메인 모듈의 종속성으로 다운로드되었다는 점에 유의하세요)
148 var child = 캐시된모듈[uri]
149
150//uri가 유효하지 않은 경우 null을 반환합니다.
151//하위 항목이 비어 있으면 매개변수 채우기가 잘못되고 URI가 잘못되었음을 의미할 수 있으며 null이 직접 반환됩니다
152 if (!child) {
153 null을 반환
154 }
155
156 // 순환 호출을 방지합니다.
157//하위 모듈의 상태가 STATUS.COMPILING인 경우, 순환 종속성으로 인해 모듈이 반복적으로 컴파일되는 것을 방지하기 위해 child.exports를 직접 반환합니다
158 if (child.status === STATUS.COMPILING) {
159 child.exports 반환
160 }
161//초기화 중에 현재 모듈을 호출하는 모듈을 가리킵니다. 이 속성에 따라 모듈 초기화 시 Call Stack을 얻을 수 있습니다.
162 child.parent = 모듈
163//컴파일된 하위 모듈을 반환합니다.exports
164 자식을 반환합니다._compile()
165 }
166 // 모듈을 비동기적으로 로드하고 로드가 완료된 후 지정된 콜백을 실행하기 위해 모듈에서 내부적으로 사용됩니다.
167 require.async = 함수(id, 콜백) {
168 module._use(id, 콜백)
169 }
170 // 모듈 시스템 내부의 경로 구문 분석 메커니즘을 사용하여 모듈 경로를 구문 분석하고 반환합니다. 이 함수는 모듈을 로드하지 않고 확인된 절대 경로만 반환합니다.
171 require.resolve = 함수(id) {
172 반환 해결(id, module.uri)
173 }
174 // 이 속성을 통해 모듈 시스템에서 로드한 모든 모듈을 볼 수 있습니다.
175 // 경우에 따라 모듈을 다시 로드해야 하는 경우 모듈의 uri를 가져온 다음 delete require.cache[uri]를 사용하여 해당 정보를 삭제할 수 있습니다. 이렇게 하면 다음에 사용할 때 다시 얻을 수 있습니다.
176 요구.cache=캐시된모듈
177
178 // require는 다른 모듈에서 제공하는 인터페이스를 얻는 데 사용되는 메서드입니다.
179 모듈.require = 요구
180 // 내보내기는 외부 세계에 모듈 인터페이스를 제공하는 데 사용되는 개체입니다.
181 모듈.수출 = {}
182 var 공장 = module.factory
183
184 // 팩토리가 함수인 경우 모듈의 구성 방법을 나타냅니다. 이 메소드를 실행하면 모듈에서 제공하는 인터페이스를 얻을 수 있습니다.
185 if (util.isFunction(factory)) {
186 compileStack.push(모듈)
187 runInModuleContext(팩토리, 모듈)
188 compileStack.pop()
189 }
190 // 팩토리가 객체 또는 문자열과 같은 비함수 유형인 경우 모듈의 인터페이스가 객체, 문자열 또는 기타 값임을 의미합니다.
191 // 예: 정의({ "foo": "bar" });
192 // 예: 정의('나는 템플릿입니다. 내 이름은 {{name}}입니다.');
193 else if (공장 !== 정의되지 않음) {
194 모듈.수출=공장
195 }
196
197 // 모듈 상태를 COMPILED로 변경하여 모듈이 컴파일되었음을 나타냅니다.
198 모듈.상태 = 상태.컴파일
199 // seajs.modify()를 통해 모듈 인터페이스 수정을 실행
200 execModifiers(모듈)
201 반환 모듈.내보내기
202 }
if (util.isFunction(factory)) {
186 compileStack.push(모듈)
187 runInModuleContext(팩토리, 모듈)
188 compileStack.pop()
189 }
여기서 module.export를 초기화합니다. runInModuleContext 메소드:
// 모듈 컨텍스트에 따라 모듈 코드를 실행
489 함수 runInModuleContext(fn, 모듈) {
490 // 모듈 및 모듈 자체와 관련된 두 개의 매개변수를 전달합니다
491 // 내보내기는 인터페이스를 노출하는 데 사용됩니다
492 // require는 종속 모듈을 얻는 데 사용됩니다(동기화)(컴파일)
493 var ret = fn(module.require, module.exports, 모듈)
494 // 다음과 같은 반환 값 노출 인터페이스 형식을 지원합니다.
495 // 반환 {
496 //fn1 :xx
497 // ,fn2:xx
498 // ...
499 // }
500 if (ret !== 정의되지 않음) {
501 module.exports = ret
502 }
503 }
a.js에서 함수 메서드를 실행하면 var b = require("b.js"),
가 호출됩니다.
require 메소드는 b의 컴파일 메소드의 반환 값을 반환하며, b 모듈에는 var c = require('c.js')가 있습니다.
이때 c의 컴파일 메소드가 호출되고 c의 함수가 호출됩니다. c에서 객체를 노출하거나 객체 c를 반환하려는 경우 모듈 c = c의 내보내기가 수행됩니다. 또는 직접 module.export = c; 간단히 말해서 module c.export = c가 결국 반환되므로 var c = module c.export = c, 모듈 b에서는 변수 c를 사용하여 메서드를 호출할 수 있습니다. 모듈 c 속성의 c 개체.
비유하자면, 결국 모듈 a는 모듈 b에 있는 개체 b의 속성과 메서드를 호출할 수도 있습니다.
어떤 모듈이든 module.export = xx 모듈이 사용되는 한 다른 모듈은 require("xx module")을 사용하여 xx 모듈의 다양한 메서드를 호출할 수 있습니다.
최종 모듈 상태는 module.status = STATUS.COMPILED가 됩니다.
Module.prototype._use = 함수(id, 콜백) {
var uris = 해결(ids, this.uri); //Resolve['./a','jquery']
This._load(uris, function() { //파싱된 a, jquery 모듈의 주소 [url1, url2]를 계산하고 _load 메서드를 호출합니다.
//util.map: 모든 데이터 멤버가 지정된 함수를 한 번에 실행하도록 하고, 원래 배열 멤버가 실행한 콜백의 결과인 새 배열을 반환합니다
var args = util.map(uris, function(uri) {
return uri ?
})
if (콜백) { callback.apply(null, args) }
})
}
이때 args = [module a.export, module jquery.export];
seajs.use(['./a','jquery'],function(a,$){
var num = a.a;
$('#J_A').text(num);
})
이때 함수의 a와 $는 모듈 a.export와 모듈 jquery.export이다.
저는 현재 jquery 소스 코드와 jquery 프레임워크 디자인을 공부하고 있기 때문에 몇 가지 경험을 공유하고 싶습니다.
인터넷에서 jquery 소스 코드에 대한 분석을 많이 읽었지만 더 이상 참을 수 없었습니다. 별로 의미가 없습니다. Miaowei Classroom의 jquery 소스 코드 분석을 권장합니다.
Situ Zhengmei의 JavaScript 프레임워크 디자인은 개인적으로 어렵지만, 주의 깊게 읽으면 수석 프론트엔드 엔지니어가 될 것입니다.
Yu Bo의 sea.js를 배워서 사용해보시길 권합니다. 결국 중국인이 직접 만든 것입니다. 우리 회사의 새로운 프로젝트나 리팩토링은 seajs를 사용하여 수행됩니다.
다음 단계는 모듈식 핸드바와 mvc의 백본 또는 mvvm의 각도의 소스 코드를 읽는 것입니다. 빨리 배우기 위해 어떤 책을 읽어야 할지, 어떤 웹 사이트를 읽어야 할지, 어떤 비디오를 봐야 할지에 대해 누군가가 나에게 조언을 해줄 수 있기를 바랍니다.