Nice programing

JavaScript를 동 기적으로 동적으로로드

nicepro 2020. 12. 11. 19:23
반응형

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>

참고 문헌


수락 된 답변이 올바르지 않습니다.

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

반응형