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()すべてのファイルが指定された順序で、それ以外の場合は応答を保存するだけです)。
カンロフィンギ2015年

回答:


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のようなより堅牢なスクリプト読み込みツールを検討することを強くお勧めします。心配する特別なケースがたくさんあります。本当に単純な状況では、yepnopeがあります。これは現在Modernizrに組み込まれています。


3
残念ながら、それはクロスブラウザではありません。
gblazex 2010

69
本当に??スクリプトがロードされたときに「ロード」イベントを発生させないのは誰ですか? 待ってください-私に言わないでください。
とがった2010

1
@Pointy XMLHttpRequestを使用してこの問題を解決し、次にeval()。ただし、デバッグは悪夢ですb / cエラーメッセージeval()は、実際のエラーではなく、行が表示されることを報告します
puk 2011年

3
しかし、requirejsはどのようにこれを行うのですか?彼らはどのように多くのスクリプトを含み、正しい順序でそれらを起動していますか?
mmm 2013

4
もちろん、document.write()はあなたが探しているものです。きれいではありませんが、機能します。
Jiri Vetyska 2014年

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()


最初のコードスニペットがすべてのブラウザで機能することを確認しますか?
ボグダングシエフ2013年

@BogdanGusiev 100%確信が持てません。私はIE8(当時の最新バージョン)FirefoxとChromeでテストしました。これは、コンテンツタイプとして提供されるXHTMLDoctypeでは機能しない可能性がありapplication/xhtml+xmlます。
Josh Johnson

1
残念ながら、スクリプトタグはJSファイルでは使用できません。
クレム

@Clemあなたはすることができますdocument.write("<SCR" + "IPT>" + "...")
ジョン・ワイズ

これは、<head>他のいくつかの依存関係(またはプライベートファイル)をロードするスクリプトのOKの代替手段です。
alecov 2016

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
これは面白そうだ。1つの質問...なぜコールバックメソッドを2回実行する必要があるのですか?(script.onload = onreadystatechangeで使用されるcallbackおよびcallback())
Clem

1
onreadysteatechangeはIE用であり、オンロードはIEに対しては起動しないため、IEでのみ起動します
Guilherme Ferreira

7

非同期プログラミングは、要求を行った結果が要求ステートメントに従う代わりに関数にカプセル化されるため、少し複雑になります。ただし、サーバーやネットワークの速度が低下してもブラウザがクラッシュしたかのように動作することはないため、ユーザーエクスペリエンスのリアルタイムの動作は大幅に向上する可能があります。同期プログラミングは無礼で あり、採用すべきではありません、人々が使用するアプリケーションでは使用し。

ダグラス・クロックフォード YUIブログ

さて、それはでこぼこの乗り物になるだろうので、あなたの席を締めなさい。ますます多くの人々がjavascriptを介して動的にスクリプトをロードすることについて質問します、それはホットな話題のようです。

これが非常に人気になった主な理由は次のとおりです。

  • クライアント側のモジュール性
  • より簡単な依存関係管理
  • エラー処理
  • パフォーマンス上の利点

モジュール性について:クライアント側の依存関係の管理は、クライアント側で正しく処理する必要があることは明らかです。特定のオブジェクト、モジュール、またはライブラリが必要な場合は、それを要求して動的にロードします。

エラー処理:リソースに障害が発生した場合でも、影響を受けるスクリプトに依存する部分のみをブロックするか、少し遅れてもう一度試してみる機会があります。

パフォーマンスはWebサイト間の競争力になり、検索ランキングの要素になりました。動的スクリプトで実行できるのは、ブラウザーがスクリプトを処理するデフォルトのブロック方法とは対照的に、非同期動作を模倣することです。スクリプトは他のリソースをブロックしスクリプトはHTMLドキュメントのさらなる解析をブロックしスクリプトはブロックしますしはUIをします。動的スクリプトタグとそのクロスブラウザの代替手段を使用すると、実際の非同期リクエストを実行し、依存コードが利用可能な場合にのみ実行できます。スクリプトは他のリソースと並行して読み込まれ、レンダリングは完璧になります。

一部の人々が同期スクリプトに固執する理由は、彼らがそれに慣れているからです。彼らはそれがデフォルトの方法であり、より簡単な方法であると考えており、それが唯一の方法であると考える人さえいるかもしれません。

ただし、アプリケーションの設計に関してこれを決定する必要がある場合に注意する必要があるのは、エンドユーザーエクスペリエンスだけです。そして、この分野では非同期を打ち負かすことはできません。ユーザーは即座に応答を受け取り(または約束を言います)、約束は常に何もないよりも優れています。空白の画面は人々を怖がらせます。開発者は、知覚されるパフォーマンスを向上せるために怠惰であってはなりません。

そして最後に、汚い面についてのいくつかの言葉。ブラウザ間で機能させるために何をすべきか:

  1. 非同期的に考えることを学ぶ
  2. コードをモジュール化するように整理する
  3. エラーやエッジケースを適切に処理するようにコードを整理する
  4. 徐々に強化する
  5. 常に適切な量のフィードバックに注意してください

ありがとう、ガラム。もっと明確にすべきだったと思います。最終的にはこれが非同期になると思っていました。プログラマーにとって論理的に意味のあるアクセス方法が欲しいだけです。次のようなことは避けたかった:Import( "package.mod1"、function(){// mod1で何かをする}); Import( "package.mod2"、function(){// mod2で処理を行う}); 私はあなたのスクリプトとlabjsを調べましたが、すばらしいとはいえ、私のニーズにはもっと複雑なようです。もっと簡単な方法があるのではないかと思い、余分な依存関係を持ち込まないようにしたいと思いました。
Josh Johnson

1
あなたは私の投稿の要点を見逃しました。それはすべてユーザーに関するものです。これが最優先事項です。他のすべては二次的です。
gblazex 2010

2
ガラム、とても良い点です。ユーザーエクスペリエンスは非常に重要です。明確にするために、私はユーザーエクスペリエンスや品質、保守可能なコードを犠牲にするつもりはありません。クロージャーとlabjsを調べて、それらが私のために何ができるかを確認します。ただし、当面は<script>タグを使用する必要があるかもしれません。残念ながら、私はこれに自分で取り組んでいません。私は中規模の開発者チームと協力しているため、保守可能なコードが優先されます。誰もがlibを効率的に使用する方法を理解できない場合、ユーザーexpはウィンドウのすぐ外に出ます。コールバックは直感的です。パッケージをインポートしたためのコールバックはそうではありません。
Josh Johnson

繰り返しになりますが、明確にするために、「同期」は私の主張を伝えるために使用される言葉の悪い選択でした。読み込み中にブラウザがフリーズしたくありません。
Josh Johnson

1
同期ロードが必要な場合はどうなりますか?ユーザーエクスペリエンスを維持するために実際にブロックする必要がある場合。JavaScriptベースのA / BまたはMVTテストシステムを使用している場合。ユーザーエクスペリエンスを損なうちらつき効果を取得せずに、コンテンツを非同期にロードしてデフォルトを置き換えるにはどうすればよいですか?私は提案を受け入れます。これに対する解決策を知りたいと思っている500人以上の同僚がいます。お持ちでない場合は、「同期プログラミングは無礼であり、人が使用するアプリケーションには使用しないでください」などの表現を付けないでください。
transilvlad 2013年

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
@JoshJohnsonscript.addEventListener('load', postLoadFunction);は、スクリプトのロード時にpostLoadFunctionが呼び出されることを意味します。
エリック

4

この質問に対する既存の回答(および他のスタックオーバーフロースレッドでのこの質問のバリエーション)には、次の問題がありました。

  • ロードされたコードはどれもデバッグ可能ではありませんでした
  • 多くのソリューションでは、真にブロックするのではなく、ロードがいつ終了したかを知るためにコールバックが必要でした。つまり、ロードされた(つまりロード中の)コードをすぐに呼び出すと実行エラーが発生します。

または、もう少し正確に:

  • ロードされたコードはどれもデバッグ可能ではありませんでした(ソリューションがスクリプト要素をdomに追加した場合に限り、HTMLスクリプトタグブロックを除いて、個別の表示可能なスクリプトとしては決してありません) =>ロードする必要のあるスクリプトの数を考えると(およびデバッグ)、これは受け入れられませんでした。
  • '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

私は自分のWebサイトに複数の.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 2020
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.