document.createElement ( "script") 동기식


81

.js파일을 동 기적 으로 호출 한 다음 즉시 사용할 수 있습니까?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

이것은 간단합니다. 내 구현에서 createElement 물건은 함수에 있습니다. 컨트롤을 반환하기 전에 특정 변수가 인스턴스화되었는지 확인할 수있는 함수에 무언가를 추가하는 것에 대해 생각했습니다. 그러나 내가 제어 할 수없는 다른 사이트의 js를 포함 할 때해야 할 일에 대한 문제가 여전히 있습니다.

생각?

편집하다:

무슨 일이 일어나고 있는지에 대한 좋은 설명을 제공하기 때문에 지금은 최선의 답변을 수락했습니다. 그러나 누군가 이것을 개선하는 방법에 대한 제안이 있다면 나는 그들에게 열려 있습니다. 여기 제가하고 싶은 일의 예가 있습니다.

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

내부를 너무 많이 알지 않고 "이 모듈을 사용하고 싶습니다. 이제 코드를 사용할 것입니다."라고 말할 수 있기를 원합니다.


배열을 만들지 않고 동일한 값을 참조하는 방법을 알아 내지 못했습니다. 그렇지 않으면 자명하다고 생각합니다 (모든 것이로드되면 eval()모든 파일이 주어진 순서대로, 그렇지 않으면 응답을 저장합니다).
Kang Rofingi

답변:


134

<script>"onload"핸들러를 사용하여 요소를 만들 수 있으며 이는 브라우저에서 스크립트를로드하고 평가할 때 호출됩니다.

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

동 기적으로 할 수는 없습니다.

편집 — IE가로 <script>드 / 평가되는 태그에 대해 "로드"이벤트를 발생시키지 않는다는 점이 지적되었습니다 . 따라서 다음으로 할 일은 XMLHttpRequest로 스크립트를 가져온 다음 eval()직접 가져 오는 것입니다. (또는 <script>추가 한 태그에 텍스트를 넣는다 고 생각 합니다. 실행 환경은 eval()로컬 범위의 영향을 받기 때문에 원하는 작업을 수행하지 않아도됩니다.)

편집2013 년 초부터 Requirejs 와 같은보다 강력한 스크립트 로딩 도구를 찾는 것이 좋습니다 . 걱정할 특별한 경우가 많이 있습니다. 정말 간단한 상황을 위해 현재 Modernizr에 내장 된 yepnope 가 있습니다 .


3
불행히도 크로스 브라우저가 아닙니다.
gblazex 2010

69
정말?? 스크립트가로드 될 때 "로드"이벤트를 발생시키지 않는 사람은 누구입니까? 잠깐 -말하지 마세요.
Pointy 2010-07-14

1
@Pointy XMLHttpRequest를 사용하여이 문제를 해결 한 다음 eval(). 그러나 디버깅은 악몽입니다 b / c 오류 메시지 eval()가 실제 오류가 아니라 줄이 나타남을 보고합니다
puk

3
그러나 requirejs는 어떻게 이것을합니까 ?? 어떻게 많은 스크립트를 포함하고 올바른 순서로 실행합니까?
mmm '

4
물론, document.write ()는 당신이 찾고있는 것입니다. 예쁘지는 않지만 작동합니다.
Jiri Vetyska 2014-06-10

26

이것은 예쁘지는 않지만 작동합니다.

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

또는

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

스크립트는 별도의 <script>태그 또는 앞에 포함되어야합니다 window.onload().

작동하지 않습니다.

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

Pointy가했던 것처럼 노드를 만들 때도 똑같이 할 수 있지만 FF에서만 가능합니다. 스크립트가 다른 브라우저에서 언제 준비되는지 보장 할 수 없습니다.

XML 순수 주의자이기 때문에 나는 이것을 정말로 싫어한다. 하지만 예상대로 작동합니다. 당신은 document.write()그들을 볼 필요가 없도록 그 추악한들을 쉽게 감쌀 수 있습니다 . 테스트를 수행하고 노드를 생성하고 추가 한 다음 폴백 할 수도 document.write()있습니다.


첫 번째 코드 조각이 모든 브라우저에서 작동합니까?
Bogdan Gusiev 2013 년

@BogdanGusiev 100 % 확실하지 않습니다. IE 8 (당시 현재 버전) Firefox 및 Chrome에서 테스트했습니다. 콘텐츠 유형으로 제공되는 XHTML 문서 유형에서는 작동하지 않을 가능성이 application/xhtml+xml있습니다.
Josh Johnson

1
불행히도 스크립트 태그는 JS 파일에서 사용할 수 없습니다.
Clem

@Clem 당신은 document.write("<SCR" + "IPT>" + "...").
John Weisz

이것은 <head>몇 가지 다른 종속성 (또는 개인 파일)을로드하는 내부 스크립트의 대체 가능합니다 .
alecov

18

이 작업은 늦었지만이 작업을 원하는 사람이 나중에 참조 할 수 있도록 다음을 사용할 수 있습니다.

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

얼마 전에 짧은 블로그 게시물을 작성했습니다. http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -짐을 실은/


정말 효과가 있나요? 내 질문을 참조하십시오 : stackoverflow.com/questions/17978255/…
mmm

1
흥미로워 보입니다. 한 가지 질문 ... 왜 콜백 메서드를 두 번 실행해야합니까? (script.onload = callback 및 callback () onreadystatechange에서 사용됨)
Clem

1
onreadysteatechange는 IE입니다 만 온로드로 IE에 발광하는 것이다 IE에 대한 화재 없습니다
Guilherme 페레이라

7

비동기 프로그래밍은 요청을 수행 한 결과가 요청 문을 따르는 대신 함수에 캡슐화되기 때문에 약간 더 복잡 합니다. 그러나 사용자가 경험 하는 실시간 동작 은 느린 서버 나 느린 네트워크를 보지 않기 때문에 브라우저가 마치 충돌 한 것처럼 작동 하게 하기 때문에 훨씬 더 나아질 수 있습니다 . 동기 프로그래밍은 무례 하며 사람이 사용하는 응용 프로그램에 사용해서는 안됩니다 .

Douglas Crockford ( YUI 블로그 )

좋습니다. 좌석을 버클로 묶으십시오. 타는 것이 울퉁불퉁 할 것이기 때문입니다. 점점 더 많은 사람들이 자바 스크립트를 통해 스크립트를 동적으로로드하는 것에 대해 묻습니다.이 문제는 뜨거운 주제 인 것 같습니다.

이것이 인기를 얻은 주된 이유는 다음과 같습니다.

  • 클라이언트 측 모듈성
  • 더 쉬운 종속성 관리
  • 오류 처리
  • 성능 이점

모듈성에 대하여 : 클라이언트 측 종속성 관리가 클라이언트 측에서 바로 처리되어야한다는 것은 분명합니다. 특정 개체, 모듈 또는 라이브러리가 필요한 경우 요청하고 동적으로로드합니다.

오류 처리 : 리소스가 실패하더라도 영향을받는 스크립트에 의존하는 부분 만 차단하거나 약간의 지연을두고 다시 시도 할 수 있습니다.

성능 은 웹 사이트 간의 경쟁 우위가되었으며 이제 검색 순위 요소입니다. 동적 스크립트가 할 수있는 것은 브라우저가 스크립트를 처리하는 방법의 기본 차단 방식과는 반대로 비동기 동작을 모방하는 것입니다. 스크립트는 다른 리소스 를 차단 하고 스크립트 는 HTML 문서의 추가 구문 분석을 차단 하며 스크립트 는 UI를 차단 합니다. 이제 동적 스크립트 태그 및 브라우저 간 대안을 사용하여 실제 비동기 요청을 수행하고 사용 가능한 경우에만 종속 코드를 실행할 수 있습니다. 스크립트는 다른 리소스와도 병렬로로드되며 렌더링은 완벽합니다.

일부 사람들이 동기식 스크립팅을 고수하는 이유는 익숙해지기 때문입니다. 그들은 그것이 기본 방법이라고 생각하고 더 쉬운 방법이며 일부는 그것이 유일한 방법이라고 생각할 수도 있습니다.

그러나 애플리케이션의 디자인과 관련하여 이것이 결정되어야 할 때 우리가주의해야 할 유일한 것은 최종 사용자 경험 입니다. 그리고이 영역에서 비동기식은 이길 수 없습니다. 사용자는 즉각적인 응답을 받고 (또는 약속을 말하며) 약속은 항상없는 것보다 낫습니다. 빈 화면은 사람들을 두렵게합니다. 개발자는 인지 된 성능 을 향상시키기 위해 게으르지 않아야 합니다 .

그리고 마지막으로 더러운면에 대한 몇 마디. 브라우저에서 작동하도록하려면 다음을 수행해야합니다.

  1. 비동기 적으로 생각하는 법 배우기
  2. 모듈 식으로 코드 구성
  3. 오류와 엣지 케이스를 잘 처리하도록 코드 구성
  4. 점진적으로 향상
  5. 항상 적절한 양의 피드백을 처리합니다.

고마워요, galam. 더 명확 했어야했는데. 나는 이것이 결국 비동기적일 것이라고 예상했다. 프로그래머가 논리적으로 이해할 수있는 액세스 방법을 원합니다. 나는 다음과 같은 것을 피하고 싶었다 : Import ( "package.mod1", function () {// do stuff with mod1}); Import ( "package.mod2", function () {// mod2로 작업 수행}); 나는 당신의 스크립트와 labjs를 살펴 보았지만, 내 요구에 대해 더 복잡한 것 같습니다. 더 간단한 방법이있을 수 있다고 생각했고 추가 종속성을 가져 오는 것을 피하고 싶었습니다.
Josh Johnson

1
내 게시물의 요점을 놓쳤습니다. 그것은 사용자에 관한 것입니다. 이것이 최우선 순위 여야합니다. 다른 모든 것은 부차적입니다.
gblazex 2010

2
Galam, 아주 좋은 지적입니다. 사용자 경험은 매우 중요합니다. 명확하게 말하면 사용자 경험이나 품질, 유지 관리 가능한 코드를 희생하지 않으려 고합니다. 나는 클로저와 labjs를 살펴보고 그들이 나를 위해 무엇을 할 수 있는지 볼 것입니다. 그러나 당분간은 <script> 태그를 고수해야 할 수도 있습니다. 불행히도 저는이 작업을 혼자서하지 않습니다. 저는 중간 규모의 개발자 팀과 협력하므로 유지 관리 가능한 코드가 최우선 순위입니다. 모든 사람이 lib를 효율적으로 사용하는 방법을 알 수 없으면 사용자 exp가 창 밖으로 나갑니다. 콜백은 직관적입니다. 패키지를 가져 왔기 때문에 콜백이 아닙니다.
Josh Johnson

다시 말하지만, 명확성을 위해 "동기"는 내 요점을 전달하는 데 사용 된 단어의 잘못된 선택이었습니다. 로드되는 동안 브라우저가 멈추는 것을 원하지 않습니다.
Josh Johnson

1
동기식로드가 필요한 경우 어떻게해야합니까? 사용자 경험을 보존하기 위해 실제로 차단해야하는 경우. JavaScript 기반 A / B 또는 MVT 테스트 시스템을 사용하는 경우. 사용자 경험을 망치는 깜박임 효과없이 콘텐츠를 비동기 적으로로드하고 기본값을 바꾸려면 어떻게해야합니까? 나는 제안에 열려 있습니다. 이에 대한 해결책을 알고 싶은 500 명 이상의 동료가 있습니다. 없는 경우 "동기 프로그래밍은 무례하며 사람이 사용하는 응용 프로그램에 사용해서는 안됩니다."와 같은 표현을 사용하지 마십시오.
transilvlad

6

위의 답변은 저를 올바른 방향으로 안내했습니다. 다음은 내가 일한 것의 일반적인 버전입니다.

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      

언제 postLoadFunction()부르나요?
Josh Johnson

1
@JoshJohnson script.addEventListener('load', postLoadFunction);은 스크립트로드시 postLoadFunction이 호출 됨을 의미합니다.
에릭

4

이 질문에 대한 기존 답변 (및 다른 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);
    }
};

4
function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}


1

내 웹 사이트에 서로 의존하는 여러 .js 파일을 사용하는 데 익숙합니다. 파일을로드하고 종속성이 올바른 순서로 평가되는지 확인하기 위해 모든 파일을로드 한 다음 파일이 모두 수신되면 파일을로드하는 함수를 작성 eval()했습니다. 가장 큰 단점은 이것이 CDN에서 작동하지 않기 때문입니다. 이러한 라이브러리 (예 : jQuery)의 경우 정적으로 포함하는 것이 좋습니다. HTML에 스크립트 노드를 동적으로 삽입 한다고해서 스크립트가 올바른 순서로 평가되는 것은 아닙니다. 적어도 Chrome에서는 그렇지 않습니다 (이가이 함수를 작성하는 주된 이유였습니다).

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

배열을 만들지 않고 동일한 값을 참조하는 방법을 알아 내지 못했습니다. 그렇지 않으면 자명하다고 생각합니다 (모든 것이로드되면 eval()모든 파일이 주어진 순서대로, 그렇지 않으면 응답을 저장합니다).

사용 예 :

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;

0

아이러니하게도 나는 당신이 원하는 것을 가지고 있지만 당신이 가진 것에 더 가까운 것을 원합니다.

동적 및 비동기 적으로 물건을로드하고 있지만 load이와 같은 콜백을 사용합니다 (dojo 및 xmlhtpprequest 사용).

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

자세한 설명은 여기를 참조 하십시오.

문제는 라인 어딘가에서 코드가 평가되고 코드에 문제가있는 경우 console.error(errorMessage);명령문이 eval()실제 오류가 아닌 위치를 표시한다는 것 입니다. 이것은 실제로 <script>문 으로 다시 변환하려는 큰 문제입니다 ( 여기 .


재미있는 사실 : 나도 <script>태그로 돌아가서 (일부 빌드 패키지와 함께) 규칙을 사용하여 내 js를 의미있는 방식으로 패키징했습니다.
Josh Johnson

@JoshJohnson 나는 그다지 운이 좋은 b / c가 아닙니다. 링 내의 스크립트가 비동기 적으로로드되고 링 사이의 스크립트가 동 기적으로로드되는 패키지의 폭 넓은 첫로드를 수행해야합니다
puk

나는 운이 좋았고 무언가를 할 수 있었다. 당신의 입장이 부럽지 않습니다.
Josh Johnson

0

이것은 async / awaitfetch 를 지원하는 최신 '에버그린' 브라우저에서 작동합니다 .

이 예제는 오류 처리없이 단순화 되어 작업의 기본 원칙을 보여줍니다.

// This is a modern JS dependency fetcher - a "webpack" for the browser
const addDependentScripts = async function( scriptsToAdd ) {

  // Create an empty script element
  const s=document.createElement('script')

  // Fetch each script in turn, waiting until the source has arrived
  // before continuing to fetch the next.
  for ( var i = 0; i < scriptsToAdd.length; i++ ) {
    let r = await fetch( scriptsToAdd[i] )

    // Here we append the incoming javascript text to our script element.
    s.text += await r.text()
  }

  // Finally, add our new script element to the page. It's
  // during this operation that the new bundle of JS code 'goes live'.
  document.querySelector('body').appendChild(s)
}

// call our browser "webpack" bundler
addDependentScripts( [
  'https://code.jquery.com/jquery-3.5.1.slim.min.js',
  'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js'
] )

우리는 다음과 같이 말할 수 없습니다 webpack... 1. 모든 스크립트에 대해 new HTTP request, 2. 이것은 또한 그들 사이의 종속성을 확인하지 않습니다. 3. 모든 브라우저가 지원하는 것은 아닙니다 async/await. 4. 성능면에서는 지루하고 정상입니다. 이것을 추가하는 것이 좋을 것입니다head
santosh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.