2009年12月27日

javascript:同期/非同期で動的なjavascriptの読込み3種

同期/非同期で動的な javascript の読込み3種

・ eval(firefox etc)/execScript(ie)で直書きタイプ(非同期)
・ eval(firefox etc)/execScript(ie)で直書きタイプ(同期)
・ script タグ追加タイプ(非同期)


※ それぞれ再読み込みを防止している。
※ 非同期は call back 指定付き。

今更な気もしますが、動的な javascript の読み込み3種を書いたので全部UP。必要に駆られたのは1番目だけなのに、余計なもん作ってるあたり現実逃避です。そもそも同期が要るのかどうか疑問だけども、まぁインクルードのような使い方になるのでしょうか。call back を意識しない分、同期の方(インクルードの方)が構築も単純かな。

■ 追加・修正
・ 2010/01/19

■ 確認環境
msie 7
firefox 3
opera 10
chrome 3
safari 4

関数化していなかった物を関数化したのでバグがあるかも知れません。一応確認環境は上記です。

■ 一応メリットとデメリット

eval や execScript で直接実行している方は、XMLHttpRequest でコードを取得します。そのため以下の留意事項があります。
・ js の文字セットは html の文字セットに関わらず utf-8
・ 外部 js ファイルは取得できない。


script タグ追加タイプの方の留意点。
・ js の文字セットは html の文字セットと同一にしなければならない。もしくは charset 属性を指定できるように改造か meta で chaset 指定。
・ タイマを利用して call back している。
・ 読み込んだかどうかの判定は、対象のスクリプトにある変数や関数名を指定してやる事で対処している。


あと、全体を通して・・・。
再読み込みを防止しつつ call back 出来るようにしているために冗長。他、Ajax 部分は他のライブラリ使うべきなんだろうけど、極力1関数で完結させたい!などというのが、そもそもの間違いなのかも知れない。



 ソース


■ 引数
url : 読み込むスクリプト
_success : 成功時の call back(オプション)
_failure : 失敗時の call back(オプション)
_check : タグ書き込みタイプでは、存在を調べられる変数や関数名。call back を指定する場合はこれが無いと判定できない(オプション)

※ call back には url が返ってくる。


■ eval(firefox etc)/execScript(ie)で直書きタイプ(非同期)
function load(url, _success, _failure){

  // cash
  if(!arguments.callee.cash) arguments.callee.cash = [];
  var csh = arguments.callee.cash;
  var len = csh.length;
  for(var i = 0; len > i; i++){
    if(csh[i].url == url){
      if(csh[i].code === void(0)){
        if(_success) csh[i].success.push(_success);
        if(_failure) csh[i].failure.push(_failure);
      }else if(csh[i].code === false){
        if(_failure) _failure(url);
      }else{
        if(_success) _success(url);
      }
      return;
    }
  }
  var c = {
    url : url,
    code : void(0),
    success : _success ? [_success] : [],
    failure : _failure ? [_failure] : []
  }
  csh[len] = c;
  var success = function (){
    for(var i = 0; c.success.length > i; i++) c.success[i](url);
  }
  var failure = function (){
    for(var i = 0; c.failure.length > i; i++) c.failure[i](url);
  }

  // XMLHttpRequest obj
  var obj = false;
  try{obj = new XMLHttpRequest();}catch(e){
    try{obj = new ActiveXObject("Msxml2.XMLHTTP");}catch(e){
      try{obj = new ActiveXObject("Microsoft.XMLHTTP");}catch(e){}
    }
  }

  // load
  if(obj){

    // call back
    obj.onreadystatechange = function (){
      if(obj.readyState != 4) return;
      if(obj.status == 200){
        var code = obj.responseText;
        if(window.execScript){
          window.execScript(code, 'JavaScript');
        }else{
          window.eval(code);
        }
        csh[len].code = code;
        if(c.success.length > 0) success();
      }else{
        csh[len].code = false;
        if(c.failure.length > 0) failure();
      }
      obj.onreadystatechange = E;
    }

    // request
    obj.open('get', url, true);
    obj.setRequestHeader('Connection', 'close');
    obj.send('');

  }else if(_failure) _failure(url);
}

function E(){}

■ 2010/01/19 追記(太字部分)
テストではメモリーリークに対処していたのですが、こちらにも追記しました。
参考:prototype.js 内 leak で検索

例)
load(
  'script.js',
  function (){alert('success');},
  function (){alert('failure');}
);
テストはこちら



■ eval(firefox etc)/execScript(ie)で直書きタイプ(同期)
function include(url){

  // cash
  var cee = arguments.callee;
  if(!cee.cash) cee.cash = [];
  for(var i = 0; cee.cash.length > i; i += 2){
    if(cee.cash[i] == url) return true;
  }

  // XMLHttpRequest obj
  var obj = false;
  try{obj = new XMLHttpRequest();}catch(e){
    try{obj = new ActiveXObject("Msxml2.XMLHTTP");}catch(e){
      try{obj = new ActiveXObject("Microsoft.XMLHTTP");}catch(e){}
    }
  }

  // load
  if(obj){
    obj.open('get', url, false);
    obj.setRequestHeader('Connection', 'close');
    obj.send('');
    if(obj.readyState == 4 && obj.status == 200){
      var code = obj.responseText;
      if(window.execScript){
        window.execScript(code, 'JavaScript');
      }else{
        window.eval(code);
      }
      cee.cash[cee.cash.length] = url;
      cee.cash[cee.cash.length] = code;
      return true;
    }
  }

  return false;
}

例)
include('script.js');
テストはこちら
include より include_once にした方がいいのだろうか。


■ script タグ追加タイプ(非同期)
function loadOnElement(url, _check, _success, _failure){

  // cash
  if(!arguments.callee.cash) arguments.callee.cash = [];
  var csh = arguments.callee.cash;
  var len = csh.length;
  for(var i = 0; len > i; i++){
    if(csh[i].url == url){
      if(csh[i].code === void(0)){
        if(_success) csh[i].success.push(_success);
        if(_failure) csh[i].failure.push(_failure);
      }else if(csh[i].code === false){
        if(_failure) _failure(url);
      }else{
        if(_success) _success(url);
      }
      return;
    }
  }
  var c = {
    url : url,
    code : void(0),
    success : _success ? [_success] : [],
    failure : _failure ? [_failure] : []
  }
  csh[len] = c;
  var success = function (){
    for(var i = 0; c.success.length > i; i++) c.success[i](url);
  }
  var failure = function (){
    for(var i = 0; c.failure.length > i; i++) c.failure[i](url);
  }

  // load
  var e = document.createElement('script');
  e.type = 'text/javascript';
  e.src = url;
  document.getElementsByTagName('head')[0].appendChild(e);

  // call back
  if(_check && (_success || _failure)){
    var n = 1;
    var t = setInterval(
      function (){
        if(typeof(window[_check]) != 'undefined'){
          clearInterval(t);
          csh[len].code = true;
          if(c.success.length > 0) success();
        }else if(n > 50){
          clearInterval(t);
          csh[len].code = false;
          if(c.failure.length > 0) failure();
        }
        n++;
      },
      200
    );
  }
}

例)
load(
  'script.js',
  'function name',
  function (){alert('success');},
  function (){alert('failure');}
);
テストはこちら

■ 2010/01/19 追記
こちらは書き変えようとして放置 ^^;
うーん。独りごち・・・。
37toさん
動的なscriptタグの読み込みを同期的に行う
http://blog.37to.net/2008/08/script/
onreadystatechange いいすね。使いたいけど失敗時も loaded なんですよね。で、onerror で補足しようとすると、onreadystatechange との兼ね合いが良く分からない。(必ずどちらかが先行する。など確証があれば良いんですが、とりあえず放置・・・)
というかタイマをやめて onreadystatechange 使ってグローバルな変数や関数チェックさせればいいけど折角だしなぁ。やはり onerror との兼ね合いが分かるまで書き換えはやめとくかぁ。不明なら不明で諦めがつくんだけど、どっかに情報あるのかな。



タグ:javascript
posted by HiFa at 02:58 | 🌁 | Comment(0) | TrackBack(0) | JavaScript雑感 | このブログの読者になる | 更新情報をチェックする
>>> スパムコメントは消してますよん。 お互い無駄な労力は避けましょう。 <<<

この記事へのコメント

コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック