JavaScript를 동 기적으로 동적으로로드
모듈 패턴을 사용하고 있습니다. 제가 원하는 작업 중 하나는 외부 JavaScript 파일을 동적으로 포함하고 파일을 실행 한 다음 return { }
내 모듈 의 파일에있는 함수 / 변수를 사용하는 것 입니다.
이 작업을 쉽게 수행 할 수 없습니다. 의사 동기 외부 스크립트로드를 수행하는 표준 방법이 있습니까?
function myModule() {
var tag = document.createElement("script");
tag.type = "text/javascript";
tag.src = "http://some/script.js";
document.getElementsByTagName('head')[0].appendChild(tag);
//something should go here to ensure file is loaded before return is executed
return {
external: externalVariable
}
}
스크립트 리소스를 동기식으로로드하고 실행하는 유일한 방법은 동기식 XHR을 사용하는 것입니다.
이 작업을 수행하는 방법의 예입니다.
// get some kind of XMLHttpRequest
var xhrObj = createXMLHTTPObject();
// open and send a synchronous request
xhrObj.open('GET', "script.js", false);
xhrObj.send('');
// add the returned content to a newly created script tag
var se = document.createElement('script');
se.type = "text/javascript";
se.text = xhrObj.responseText;
document.getElementsByTagName('head')[0].appendChild(se);
그러나 다른 모든 것을 차단하므로 일반적으로 동기 요청을 사용해서는 안됩니다. 그러나 물론 이것이 적절한 시나리오가 있습니다.
onload 핸들러를 사용하여 포함하는 함수를 비동기 패턴으로 리팩터링 할 것입니다.
허용 대답 이다 NOT 올바른.
파일을 동 기적으로로드하는 것은 파일을 동 기적으로 실행하는 것과 같지 않습니다. 이것은 OP가 요청한 것입니다.
수락 된 답변은 파일 동기화를로드하지만 DOM에 스크립트 태그를 추가하는 것 이상을 수행하지 않습니다. 해서 에 appendChild ()가 돌아왔다 스크립트가 실행을 완료하고 그것의 회원이 사용하기 위해 초기화됩니다 어쨌든 보증하지 않습니다.
OP 질문을 달성하는 유일한 방법은 명시된대로 XHR을 통해 스크립트로드를 동기화 한 다음 텍스트로 읽고 eval () 또는 새로운 Function () 호출에 전달하고 해당 함수가 반환 될 때까지 기다리는 것입니다. 이로드 된 스크립트를 보장하는 유일한 방법입니다 및 비동기 적으로 실행합니다.
UI 또는 보안 측면에서 이것이 현명한 것인지에 대해서는 언급하지 않지만 동기화로드 및 실행을 정당화하는 사용 사례는 확실히 있습니다.
주의 사항 : 웹 워커를 사용하지 않는 한 loadScripts ();
이것은 내 앱에서 여러 파일로드에 사용하는 코드입니다.
Utilities.require = function (file, callback) {
callback = callback ||
function () {};
var filenode;
var jsfile_extension = /(.js)$/i;
var cssfile_extension = /(.css)$/i;
if (jsfile_extension.test(file)) {
filenode = document.createElement('script');
filenode.src = file;
// IE
filenode.onreadystatechange = function () {
if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') {
filenode.onreadystatechange = null;
callback();
}
};
// others
filenode.onload = function () {
callback();
};
document.head.appendChild(filenode);
} else if (cssfile_extension.test(file)) {
filenode = document.createElement('link');
filenode.rel = 'stylesheet';
filenode.type = 'text/css';
filenode.href = file;
document.head.appendChild(filenode);
callback();
} else {
console.log("Unknown file type to load.")
}
};
Utilities.requireFiles = function () {
var index = 0;
return function (files, callback) {
index += 1;
Utilities.require(files[index - 1], callBackCounter);
function callBackCounter() {
if (index === files.length) {
index = 0;
callback();
} else {
Utilities.requireFiles(files, callback);
}
};
};
}();
그리고이 유틸리티는 다음에서 사용할 수 있습니다.
Utilities.requireFiles(["url1", "url2",....], function(){
//Call the init function in the loaded file.
})
내가 생각해 낼 수있는 가장 Node.js와 유사한 구현은 JS 파일을 동시에로드하여 객체 / 모듈로 사용할 수 있다는 것입니다.
var scriptCache = [];
var paths = [];
function Import(path)
{
var index = 0;
if((index = paths.indexOf(path)) != -1) //If we already imported this module
{
return scriptCache [index];
}
var request, script, source;
var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;
request = new XMLHttpRequest();
request.open('GET', fullPath, false);
request.send();
source = request.responseText;
var module = (function concealedEval() {
eval(source);
return exports;
})();
scriptCache.push(module);
paths.push(path);
return module;
}
예제 소스 ( addobjects.js
) :
function AddTwoObjects(a, b)
{
return a + b;
}
this.exports = AddTwoObjects;
다음과 같이 사용하십시오.
var AddTwoObjects = Import('addobjects.js');
alert(AddTwoObjects(3, 4)); //7
//or even like this:
alert(Import('addobjects.js')(3, 4)); //7
이 질문에 대한 기존 답변 (및 다른 stackoverflow 스레드 에서이 질문의 변형)에 다음과 같은 문제가 있습니다.
- 로드 된 코드는 디버깅 할 수 없습니다.
- 대부분의 솔루션은로드가 완료된 시점을 알기 위해 콜백이 필요했습니다. 즉,로드 된 (즉,로드하는) 코드를 즉시 호출하면 실행 오류가 발생합니다.
또는 약간 더 정확하게 :
- 로드 된 코드 중 어느 것도 디버깅 할 수 없었습니다 (HTML 스크립트 태그 블록을 제외하고, 솔루션이 dom에 스크립트 요소를 추가 한 경우에만 가능하며 개별적으로 볼 수있는 스크립트가 아닌 경우). =>로드해야하는 스크립트 수 ( 및 디버그), 이것은 용납 할 수 없습니다.
- 'onreadystatechange'또는 'onload'이벤트를 사용하는 솔루션을 차단하지 못했습니다. 코드가 원래 'require ([filename,'dojo / domReady ']);'를 사용하여 동적 스크립트를 동 기적으로로드했기 때문에 큰 문제였습니다. 도장을 벗기고있었습니다.
반환하기 전에 스크립트를로드하고 디버거에서 모든 스크립트에 올바르게 액세스 할 수있는 최종 솔루션 (최소한 Chrome의 경우)은 다음과 같습니다.
경고 : 다음 코드는 '개발'모드에서만 사용되어야합니다. ( '릴리스'모드의 경우 동적 스크립트로드없이 또는 최소한 eval없이 사전 패키징 및 축소를 권장합니다.)
//Code User TODO: you must create and set your own 'noEval' variable
require = function require(inFileName)
{
var aRequest
,aScript
,aScriptSource
;
//setup the full relative filename
inFileName =
window.location.protocol + '//'
+ window.location.host + '/'
+ inFileName;
//synchronously get the code
aRequest = new XMLHttpRequest();
aRequest.open('GET', inFileName, false);
aRequest.send();
//set the returned script text while adding special comment to auto include in debugger source listing:
aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';
if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
{
//create a dom element to hold the code
aScript = document.createElement('script');
aScript.type = 'text/javascript';
//set the script tag text, including the debugger id at the end!!
aScript.text = aScriptSource;
//append the code to the dom
document.getElementsByTagName('body')[0].appendChild(aScript);
}
else
{
eval(aScriptSource);
}
};
var xhrObj = new XMLHttpRequest();
xhrObj.open('GET', '/filename.js', false);
xhrObj.send(null);
eval(xhrObj.responseText);
교차 도메인 요청 인 경우 작동하지 않습니다. 이 경우 요청 된 파일을 서버에 업로드하거나이를 출력하는 미러 php를 만들고 해당 php를 요구해야합니다.
jquery 사용 (도메인 간 요청에서도 작동) :
$.getScript('/filename.js',callbackFunction);
callbackFunction
동 기적으로 호출됩니다.
더 많은 스크립트를로드하려면 이 스레드를 참조하십시오 .
임의의 수의 스크립트를로드하고 마지막 스크립트가 완료 될 때만 진행해야하고 XHR을 사용할 수없는 경우 (예 : CORS 제한으로 인해) 다음을 수행 할 수 있습니다. 동기식은 아니지만 마지막 파일로드가 완료 될 때 정확히 콜백이 발생하도록 허용합니다.
// Load <script> elements for all uris
// Invoke the whenDone callback function after the last URI has loaded
function loadScripts(uris,whenDone){
if (!uris.length) whenDone && whenDone();
else{
for (var wait=[],i=uris.length;i--;){
var tag = document.createElement('script');
tag.type = 'text/javascript';
tag.src = uris[i];
if (whenDone){
wait.push(tag)
tag.onload = maybeDone;
tag.onreadystatechange = maybeDone; // For IE8-
}
document.body.appendChild(tag);
}
}
function maybeDone(){
if (this.readyState===undefined || this.readyState==='complete'){
// Pull the tags out based on the actual element in case IE ever
// intermingles the onload and onreadystatechange handlers for the same
// script block before notifying for another one.
for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
if (!wait.length) whenDone();
}
}
}
편집 : IE7, IE8 및 IE9 (쿼크 모드)에서 작동하도록 업데이트되었습니다. 이러한 IE 버전은 onload
이벤트를 발생시키지 않지만 onreadystatechange
. 표준 모드의 IE9는 두 가지를 모두 실행 합니다 ( onreadystatechange
모든 스크립트가 먼저 실행 됨 onload
).
을 바탕으로 이 페이지를 IE의 이전 버전은 보내지 않을 것이라는 점을 작은 기회가있을 수 있습니다 onreadystatechange
와 이벤트를 readyState=='complete'
; 이 경우 (이 문제를 재현 할 수 없음) 위 스크립트가 실패하고 콜백이 호출되지 않습니다.
실제로 스크립트 목록을로드 하고 동 기적으로 실행하는 방법이 있습니다. 각 스크립트 태그를 DOM에 삽입하고 async
속성을 명시 적 으로 false로 설정해야 합니다.
script.async = false;
DOM에 삽입 된 스크립트는 기본적으로 비동기 적으로 실행되므로이 async
문제를 해결하려면 수동으로 속성을 false 로 설정해야 합니다.
예
<script>
(function() {
var scriptNames = [
"https://code.jquery.com/jquery.min.js",
"example.js"
];
for (var i = 0; i < scriptNames.length; i++) {
var script = document.createElement('script');
script.src = scriptNames[i];
script.async = false; // This is required for synchronous execution
document.head.appendChild(script);
}
// jquery.min.js and example.js will be run in order and synchronously
})();
</script>
<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>
참고 문헌
- Google의 Jake Archibald 가 작성한 스크립트 로딩의 어두운 물 속으로 Deep dive 라는 훌륭한 기사가 있습니다.
- 태그 의 WHATWG 사양 은 태그가로드되는 방법에 대한 훌륭하고 철저한 설명입니다.
수락 된 답변이 올바르지 않습니다.
script.async = false;
지시어는 HTML 구문 분석 스크립트를 실행하는 동안 일시 중지됩니다 것을 의미합니다. 이것은 자바 스크립트 코드가 실행될 순서를 보장하지 않습니다. 참조 https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/를
아직 여기에 언급되지 않은 가장 쉽고 우아한 솔루션은 다음과 같이 promise를 사용하는 것입니다.
function loadScript(url) {
return new Promise((resolve, reject) => {
var script = document.createElement('script')
script.src = url
script.onload = () => {
resolve()
}
script.onerror = () => {
reject('cannot load script '+ url)
}
document.body.appendChild(script)
})
}
그런 다음 스크립트를 순서대로 실행하려는 경우 :
loadScript('myfirstscript.js').then(() => {
console.log('first script ran');
loadScript('index.js').then(() => {
console.log('second script ran');
})
})
당신은
할 수없고
분명한 이유 기적으로 서버 작업을 수행해서는 안된다. 하지만 할 수있는 일은 스크립트가로드 될 때 알려주는 이벤트 핸들러를 갖는 것입니다.
tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); };
tag.onload = function(load) {/*init code here*/}
onreadystatechange
위임은 메모리에서 .NET에 대한 패치 지원이있는 IE의 해결 방법입니다 onload
.
Sean의 대답과 동일하지만 스크립트 태그를 만드는 대신 평가하십시오. 이렇게하면 코드를 실제로 사용할 수 있습니다.
내 전략, jQuery UI를로드 할 때의 고전적인 예, 이것이 도움이되기를 바랍니다.
( function( tools, libs ){
// Iterator
var require = function( scripts, onEnd ){
onEnd = onEnd || function(){};
if( !scripts || scripts.length < 1 )return onEnd();
var src = scripts.splice( 0, 1),
script = document.createElement( "script" );
script.setAttribute( "src", src );
tools.addEvent( "load", script, function(){
require( scripts, onEnd );
} );
document.getElementsByTagName( "head" )[ 0 ].appendChild( script );
};
// Install all scripts with a copy of scripts
require( libs.slice(), function(){
alert( "Enjoy :)" );
} );
// Timeout information
var ti = setTimeout( function(){
if( !window.jQuery || !window.jQuery.ui )alert( "Timeout !" );
clearTimeout( ti );
}, 5000 );
} )(
{ // Tools
addEvent : function( evnt, elem, func ){
try{
if( elem.addEventListener ){
elem.addEventListener( evnt, func, false );
}else if( elem.attachEvent ){
var r = elem.attachEvent( "on" + evnt, func );
}
return true;
}catch( e ){
return false;
}
}
},
[ // Scripts
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"
]
);
Angular를 사용할 때 다른 서비스가 인스턴스화되기 전에 모든 공급자가 인스턴스화된다는 사실을 활용할 수 있습니다. 이 사실을 @Neil에서 언급 한 xhr 및 eval () 사용과 결합 할 수 있습니다. 코드는 다음과 같습니다.
app.provider('SomeScriptSyncLoader', function() {
var resourceUrl = 'http://some/script.js';
var dummy = {};
this.$get = function() {
var q = jQuery.ajax({
type: 'GET', url: resourceUrl, cache: false, async: false
});
if (q.status === 200) {
eval(q.responseText); // execute some script synchronously as inline script - eval forces sync processing
}
return dummy;
};
});
Provider를 강제로 초기화하려면 하나 이상의 다른 지시문 / 서비스에 삽입해야합니다. 가급적 이것은 스크립트에 의해로드 된 코드를 이용하는 서비스 일 것입니다.
app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// some ode
},
template: "this is my template"
};
}]);
나는 이것이 오래된 질문이라는 것을 알고 있지만 다른 누군가가 이것을 읽고 유용하다고 생각할 수도 있습니다! 방금 만든 새 구성 요소는 ES6를 사용하여 동기식으로 스크립트를 동적으로로드합니다. 프로젝트 세부 정보 및 소스 코드는 GitHub https://github.com/amgadfahmi/scripty 에 있습니다.
이 질문에 답하는 데 늦을 수 있습니다.
내 현재 솔루션은 <script>
후속 스크립트의 추가가 이전 스크립트의 콜백에 있도록 태그 를 재귀 적으로 추가 하는 것입니다. 각 함수가 하나의 함수를 포함하고 해당 함수가 파일 이름 (확장자 제외)과 동일하다고 가정합니다. 이것은 아마도 일을하는 가장 좋은 방법은 아니지만 괜찮습니다.
고려해야 할 코드
코드 디렉토리 구조 :
- directory
---- index.html
---- bundle.js
---- test_module/
-------- a.js
-------- b.js
-------- log_num.js
-------- many_parameters.js
index.html
<head>
<script src="bundle.js"></script>
</head>
bundle.js
// Give JS arrays the .empty() function prototype
if (!Array.prototype.empty){
Array.prototype.empty = function(){
return this.length == 0;
};
};
function bundle(module_object, list_of_files, directory="") {
if (!list_of_files.empty()) {
var current_file = list_of_files.pop()
var [function_name, extension] = current_file.split(".")
var new_script = document.createElement("script")
document.head.appendChild(new_script)
new_script.src = directory + current_file
new_script.onload = function() {
module_object[function_name] = eval(function_name)
bundle(module_object, list_of_files, directory)
/*
nullify the function in the global namespace as - assumed - last
reference to this function garbage collection will remove it. Thus modules
assembled by this function - bundle(obj, files, dir) - must be called
FIRST, else one risks overwritting a funciton in the global namespace and
then deleting it
*/
eval(function_name + "= undefined")
}
}
}
var test_module = {}
bundle(test_module, ["a.js", "b.js", "log_num.js", "many_parameters.js"], "test_module/")
a.js
function a() {
console.log("a")
}
b.js
function b() {
console.log("b")
}
log_num.js
// it works with parameters too
function log_num(num) {
console.log(num)
}
many_parameters.js
function many_parameters(a, b, c) {
var calc = a - b * c
console.log(calc)
}
여기 내 코드입니다
var loaded_script = [];
function loadScript(urls, callback, sync) {
var len = urls.length, count = 0;
// check are all js loaded, then execute callback (if any)
var check = function() {
if (count == len) {
callback && typeof callback=="function" && callback();
}
};
for (var i = 0; i < len; i++) {
var url = urls[i];
// check if script not loaded (prevent load again)
if (loaded_script.indexOf(url) == -1) {
var script = document.createElement("script");
script.type = "text/javascript";
// set sync loading here (default is async)
if (sync) {
script.async = false;
}
// script onload event
if (script.readyState) { // IE
script.onreadystatechange = function() {
if (script.readyState=="loaded" || script.readyState=="complete") {
script.onreadystatechange = null;
count++, check();
}
};
} else { // Others
script.onload = function() {
count++, check();
};
}
// add script to head tag
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
// mark this script has loaded
loaded_script.push(url);
} else {
count++, check();
}
}
}
나는 이것을 pjax 사이트에서 사용합니다.
loadScript(
[
"js/first.js",
"js/second.js",
],
function() {
alert("Scripts loaded.");
},
true
);
며칠 전에 비슷한 작업을했는데, 여기에 제가 한 방법이 있습니다.
이 로더는 file://
접두사 http://
및 및 에서 모두 작동하며 https://
브라우저 간 호환됩니다.
그러나 스크립트에서 특정 클래스 또는 함수를 모듈로로드 할 수 없습니다. 전체 스크립트를 모두로드하여 DOM에서 사용할 수 있도록합니다.
// Loads a script or an array of scripts (including stylesheets)
// in their respective index order, synchronously.
// By Sayanjyoti Das @https://stackoverflow.com/users/7189950/sayanjyoti-das
var Loader={
queue: [], // Scripts queued to be loaded synchronously
loadJsCss: function(src, onl) {
var ext=src.toLowerCase().substring(src.length-3, src.length);
if(ext=='.js') {
var scrNode=el('script', null, null, null);
scrNode.type='text/javascript';
scrNode.onload=function() {onl();};
scrNode.src=src;
document.body.appendChild(scrNode);
}else if(ext=='css') {
var cssNode=el('link', null, null, null);
cssNode.rel='stylesheet';
cssNode.type='text/css';
cssNode.href=src;
document.head.appendChild(cssNode);
onl();
}
},
add: function(data) {
var ltype=(typeof data.src).toLowerCase();
// Load a single script
if(ltype=='string') {
data.src=data.src;
Loader.queue.splice(0, 1, data, Loader.queue[0]);
Loader.next();
}
// Load an array of scripts
else if(ltype=='object') {
for(var i=data.src.length-1; i>=0; i--) {
Loader.queue.splice(0, 1, {
src: data.src[i],
onload: function() {
if(Loader.next()==false) {
data.onload();
return;
}
Loader.next();
}
}, Loader.queue[0]);
}
Loader.next();
}
},
next: function() {
if(Loader.queue.length!=0 && Loader.queue[0]) {
var scr=Loader.queue[0];
// Remove the script from the queue
if(Loader.queue.length>1)
Loader.queue.splice(0, 2, Loader.queue[1]);
else
Loader.queue=[];
// Load the script
Loader.loadJsCss(scr.src, scr.onload);
}else return false;
}
};
위의 기능은 매우 강력하고 우아합니다 . 단일 스크립트 또는 스크립트 배열을 동기식으로로드 할 수 있습니다 (예 : 이전 스크립트로드가 완료 될 때까지로드되지 않은 다음 스크립트). 또한로드 된 스크립트는 더 많은 스크립트를로드하여 상위 스크립트의 대기열을 지연시킬 수 있습니다.
BTW, 스크립트는 여기에 자바 스크립트 파일 또는 CSS 스타일 시트를 의미한다 .
사용 방법은 다음과 같습니다.
// Load a single script
Loader.add({
src: 'test.js',
onload: function() {
alert('yay!');
}
});
// Load multiple scripts
Loader.add({
src: ['test1.js', 'test2.js', 'mystyles.css', 'test3.js'],
onload: function() {
alert('all loaded!');
}
});
Note that, the onload
function in the Loader arguments is called when all of the scripts have loaded, not when one or a single script is loaded.
You can also load more scripts in the scripts you loaded, such as in test.js
, test1.js
, etc. By doing this, you will defer the load of the next parent script and the queue in the child script will be prioritized.
Hope it helps :-)
I use jquery load method applied to div element. something like
<div id="js">
<!-- script will be inserted here -->
</div>
...
$("#js").load("path", function() { alert("callback!" });
You can load scripts several times and each time one script will completely replace the one loaded earlier
참고URL : https://stackoverflow.com/questions/2879509/dynamically-loading-javascript-synchronously
'Nice programing' 카테고리의 다른 글
SQL Server에서 "WITH SCHEMABINDING"의 단점은 무엇입니까? (0) | 2020.12.11 |
---|---|
쿠키에 비밀번호를 저장하는 것이 안전합니까? (0) | 2020.12.11 |
Qt의 양식 레이아웃 메커니즘 이해 (0) | 2020.12.11 |
Hibernate는 엔티티 객체의 더티 상태를 어떻게 감지합니까? (0) | 2020.12.11 |
삽입 시간별로 Meteor 컬렉션을 정렬하려면 어떻게해야합니까? (0) | 2020.12.11 |