« 2007年11月 | トップページ | 2008年1月 »

2007年12月の8件の投稿

2007/12/27

ひとまず今年の出張期間終了

今年の2月初めから11ヶ月に渡る出張期間も、本日でひとくぎり。
確か、当初は4月までって聞いていたような気もするけれど……それは、たぶんソラミミ。
あ、といっても仕事収めは明日(午前移動→出社→掃除→忘年会)なんですが。うちに帰るまでが出張です、ってか?
以降、しばらくは関西でのお仕事となる、予定。あくまで予定ですけどね。

2007/12/22

【JavaScript】これ(this)はなに?

JavaScriptでプログラムを組んだり人の作ったコードを読んでいると、よく出てくる“this”という表現。
これ、例えば同一の関数内のはずなのに、そのときどきで指しているものが異なったりして結構わかりにくい、と感じる方も多いのではないでしょうか。
■同一関数内で'this'が指すものが異なる例
function whatisthis(obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
}

var obj={
    obj_whatisthis : whatisthis
};
var ref_whatisthis = obj.obj_whatisthis;

var obj2={
    ref2_whatisthis : obj.obj_whatisthis
};

//(1)関数として呼び出し
/* 1-1 */   whatisthis(obj);                //  [A]true  [B]false
/* 1-2 */   obj.obj_whatisthis(obj);        //  [A]false [B]true
/* 1-3 */   ref_whatisthis(obj);            //  [A]true  [B]false
/* 1-4 */   obj2.ref2_whatisthis(obj);      //  [A]false [B]false

//(2)コンストラクタ(constructor)として呼び出し
/* 2-1 */   new whatisthis(obj);            //  [A]false [B]false
/* 2-2 */   new obj.obj_whatisthis(obj);    //  [A]false [B]false
/* 2-3 */   new ref_whatisthis(obj);        //  [A]false [B]false
/* 2-4 */   new obj2.ref2_whatisthis(obj);  //  [A]false [B]false
私的には、結局のところ(関数オブジェクトの実体がどこにあるかにはよらず)実際に呼び出されるときに、
  1. あるオブジェクトのプロパティとして関数が呼び出される場合、thisは当該オブジェクトを指す。
  2. 上記以外の場合には、thisはGlobalオブジェクト(ブラウザの実装では通常"window")を指す。
というルールに集約されるのかな、と理解しています。

また、補足事項としては、
  • 関数がコンストラクタ(constructor)として呼ばれた場合(var obj=new <関数名>()のようにnewを付けて呼びだした場合)、その関数は新規に作成されたオブジェクトのプロパティとして呼び出される。
    従って、ルール1から関数内のthisは新規に作成されたオブジェクトを指す。
  • 関数がcallもしくはapplyで呼びだされた場合(<関数名>.call(<オブジェクト>,<引数1>[,<引数2>...])、もしくは、<関数名>.apply(<オブジェクト>,<引数の配列>))、その関数はcallもしくはapplyの第1引数(<オブジェクト>)に指定されたオブジェクトのプロパティとして呼び出される。
    従って、ルール1から関数内のthisは第1引数で指定されたオブジェクトとなる(言い換えると、関数内で"this"として扱いたいオブジェクトをcallもしくはapplyの第1引数で明示出来る)。
  • Global領域で'function <関数名>(){...}'もしくは'var <関数名>=function(){...}'で定義された関数は、Globalオブジェクト("window")のプロパティとなる。
    従って(この関数が<関数名>を直接指定して呼び出される場合)ルール1から関数内のthisはGlobalオブジェクトとなる。
  • 関数内で'function <関数名>(){...}'もしくは'var <関数名>=function(){...}'で定義された関数は、オブジェクトのプロパティではない。
    従って(この関数が<関数名>を直接指定して呼び出される場合)ルール2から関数内のthisはGlobalオブジェクトとなる。
のようになるのかな、と。
例えば、上の方で挙げた例(関数whatisthis()はGlobal領域で定義された関数)では、
  • 1-1
     →"whatisthis"がGlobalオブジェクトのプロパティであるため、"this"はGlobalオブジェクト("window")。
  • 1-2
     →"obj_whatisthis"が"obj"オブジェクトのプロパティであるため、"this"は"obj"オブジェクト。
  • 1-3
     →"ref_whatisthis"がGlobalオブジェクトのプロパティであるため、"this"はGlobalオブジェクト("window")。
  • 1-4
     →"ref2_whatisthis"が"obj2"オブジェクトのプロパティであるため、"this"は"obj2"オブジェクト。
  • 2-x
     →関数がコンストラクタとして呼び出される場合、新規に作成されたオブジェクトのプロパティとしての呼び出しとなる。従って、"this"は新規に作成されたオブジェクトを指す。
もう少し複雑な例をあげると、
function    test_whatisthis() {
    function    whatisthis(obj) {
        alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
    }
    this.whatisthis=whatisthis;
    
    whatisthis(this);           // #1
    this.whatisthis(this);      // #2
    whatisthis.call(this,this); // #3
    
    var obj={whatisthis:whatisthis};
    whatisthis(obj);            // #4
    obj.whatisthis(obj);        // #5
    whatisthis.call(obj,obj);   // #6
}

//(1)関数として呼び出し
test_whatisthis();
    //  #1:[A]true  [B]true
    //  #2:[A]true  [B]true  ※IE6では [A]false [B]false ??
    //  #3:[A]true  [B]true
    //  #4:[A]true  [B]false
    //  #5:[A]false [B]true
    //  #6:[A]false [B]true

//(2)コンストラクタ(constructor)として呼び出し
var temp=new test_whatisthis();
    //  #1:[A]true  [B]false
    //  #2:[A]false [B]true
    //  #3:[A]false [B]true
    //  #4:[A]true  [B]false
    //  #5:[A]false [B]true
    //  #6:[A]false [B]true
関数test_whatisthis()はGlobal領域で定義されたものです。

まず、「(1)関数として呼び出し」た場合には、"test_whatisthis"内でのthisはGlobalオブジェクト("window")になります。
この前提で、
  • #1  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #2  →"test_whatisthis"内での"this"オブジェクト(即ちGlobalオブジェクト)のプロパティとして呼び出されているため、"whatisthis"内のthisはGlobalオブジェクト("window")。
  • #3  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=this=window)のプロパティとして<引数>(=this=window)を指定して呼び出す』という意味になるため、"whatisthis"内のthisはGlobalオブジェクト("window")。
  • #4  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #5  →"obj"オブジェクトのプロパティとして呼び出されているため、"whatisthis"内のthisは"obj"オブジェクト。
  • #6  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=obj)のプロパティとして<引数>(=obj)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"obj"オブジェクト。
次に「(2)コンストラクタ(constructor)として呼び出し」た場合には、"test_whatisthis"内でのthisは新規に作成されたオブジェクト(仮に"temp"とします)になります。
この前提で、
  • #1  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #2  →"test_whatisthis"内での"this"オブジェクト(即ち"temp"オブジェクト)のプロパティとして呼び出されているため、"whatisthis"内のthisは"temp"オブジェクト。
  • #3  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=this=temp)のプロパティとして<引数>(=this=temp)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"temp"オブジェクト。
  • #4  →"whatisthis"が特定のオブジェクトのプロパティとして扱われていないため、"whatisthis"内のthisはルール2に基づいてGlobalオブジェクト("window")。
  • #5  →"obj"オブジェクトのプロパティとして呼び出されているため、"whatisthis"内のthisは"obj"オブジェクト。
  • #6  →'whatisthis.call(<オブジェクト>,<引数>)'は、『"whatisthis"を<オブジェクト>(=obj)のプロパティとして<引数>(=obj)を指定して呼び出す』という意味になるため、"whatisthis"内のthisは"obj"オブジェクト。
……と、大体説明出来るかなぁ?と思っていましたが、test_whatisthis()の(1)-#2の結果が、IE6では"[A]false [B]false"となってしまい、混乱してしまいました。
上記説明の通り、whatisthisはGlobalオブジェクト("window")のプロパティとして呼び出されており、また引数も"window"になることから、"[A]true [B]true "でないとおかしいはずです(実際に、FirefoxやOperaではこうなります)。
ところがIE6でも、(1)-#2のときのwhatisthis関数内における"this"の持つプロパティを調べてみると、"window"オブジェクトが持っているプロパティと一致するんですね。
とすると……window.whatisthis()が呼び出された瞬間、"window"のコピーが作成され、これが"this"として扱われている???
このIE6での現象をもっと単純に再現すると、
window.whatisthis=function (obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
};
window.whatisthis(window); // IE6:[A]false [B]false vs others:[A]true [B]true
となります。
これが、window.whatisthisをvar whatisthisに置換えたら、想定通りの動きとなる不思議。
var whatisthis=function (obj) {
    alert('[A](this===window)→'+(this===window)+' [B](this===obj)→'+(this===obj));
};
window.whatisthis(window); // [A]true [B]true
要はwindow.<関数名>=function(){...};という定義方法だと変な動きになる?これまた、ややこしいバグの温床となりそうな現象だ……。

2007/12/18

【JavaScript】function定義方法による動作の差異(覚書)

Javascriptで関数(function)を名前付きで定義したい場合、いろいろな書き方があります。
例えば、
  1. function TEST() {alert('TEST')}
  2. var TEST=function() {alert('TEST')};
  3. var TEST=function test() {alert('TEST')};
  4. var TEST=new Function("alert('TEST')");
といったもの。
これらは普段、特に意識して区別することなく使用しているのですが、調べてみると定義の仕方で微妙に動作が異なってくるようです(あ、new Function()は私には使いにくくてほとんど使っていないので検証はパス(苦笑))。
例えば→こんな感じ
上記のリンク先をIE6/Firefox/Operaで見てみると、それぞれ微妙に動作が異なってiいます(FirefoxとOperaは__parent__プロパティがあるかないかの違いだけなので、本質的なものではありませんが)。
もっとも顕著な違いとしては、
3.(右辺が名前付き関数)の書き方をしたあとで、右辺の関数名(例では'test')をtypeofしてみると、IE6ではfunction、Firefox/Operaではundefinedになる(IE6ではtest()で呼びだし可能、Firefox/Operaでは例外発生)。
ってことでしょうかね。
また、
IE6では、3.(右辺が名前付き関数)の前に 1.(通常の名前付き関数)の形式で 3. の右辺と同名の関数(例では'test')が定義されていると、3. によって上書きされてしまう。
という現象が発生してしまいます(サンプルの[CASE-2])。 2.のように右辺が無名関数になっていればそんな現象は起らないのですが、うっかり 3. のように右辺に関数名をつけて書いてしまうと、なかなか見つけにくいバグが発生しそうです。
ちなみに、Firefoxでは 3. の書き方をすると、TEST.__parent__が
({test:(function test() {p("TEST");})})
のように設定されます。
これから、まず新規に(無名の)Objectを作成し、そのプロパティ(上記例では'test')の値として右辺の関数を設定した後、当該プロパティ(への参照)を左辺に渡す、ということをやっているのではないかと推測されます。
実際、
var TEST=(function(){return {test:(function test() {p('TEST');})}.test;})();
あるいはもっとシンプルに
var TEST={test:function test() {p('TEST');}}.test;
のようにしてやっても、Firefoxでは 3. の場合と同様の動きになります。
これは 1. の書き方をした場合の関数の名前空間とは独立した名前空間を確保するため、かな?
多分、Operaでも似たようなことをしているのかとは思いますが、Operaは__parent__が無いため検証は出来ませんでした。
なお、FirefoxでTEST.__parent__を調べると、1./2.の場合には、windowオブジェクト(Global領域で定義された場合)もしくはnull(関数内で定義された場合)になります(名前空間用のObjectが新規に作成されるのは、1.~3.では3.のみ)。
右辺で名前付き関数を指定した場合、その都度専用の名前空間用オブジェクトが作成されてしまうことになるので(共用はされず、また同名にしても上書きにはならない模様)よほどの理由がない限り、無名関数とした方がよさそうです。

2007/12/09

【JavaScript】IE6でTABLE要素内をHTMLで書換える関数

IE6でTABLE/THEAD/TFOOT/TR要素内をHTML表記で書換えることができるような関数を作ってみました。
element.innerHTML='(HTML)';
とする代わりに、
replaceInnerHtml(element,'(HTML)');
として使用します。
上記要素以外の場合でも使えると思います(HEADとかBODYなどはダメですが)。
function    replaceInnerHtml(tgtElm, innerHTML) {
    for (;;) {
        if (typeof innerHTML!='string'||typeof tgtElm!='object'||tgtElm.nodeType!=1/*ELEMENT_NODE*/) break;
        try {
            tgtElm.innerHTML=innerHTML;
        }
        catch (e) {
            var chld;
            while (chld=tgtElm.firstChild) tgtElm.removeChild(chld);    //  remove all child elements
            if (innerHTML.match(/^\s*$/) ) break;   //  clear only
            
            var tagName=tgtElm.tagName.toLowerCase(), tmp, html='<'+tagName+'>'+innerHTML+'</'+tagName+'>';
            switch (tagName) {
                case    'thead' :
                case    'tbody' :
                case    'tfoot' :
                    tmp=document.createElement('table');
                    replaceInnerHtml(tmp, html);
                    break;
                case    'tr'    :
                    tmp=document.createElement('table');
                    replaceInnerHtml(tmp, '<tbody>'+html+'</tbody>');
                    tmp=tmp.firstChild;
                    break;
                default         :
                    tmp=document.createElement('div');
                    tmp.innerHTML=html;
                    break;
            }
            var tmpElm=tmp.firstChild;
            while (chld=tmpElm.firstChild) tgtElm.appendChild(chld);
        }
        break;
    }
    return tgtElm;
}
【テーブル置換サンプル】
THEAD要素
下のボタンを押して試して下さい。
TFOOT要素
【HTML】

【JavaScript】IE6におけるtable要素の初期化で、TABLE要素のinnerHTMLを直接書換えようとするとIE6ではエラーになってしまうと書きましたが、Microsoftによるとこれはやはり『仕様』とのこと。
PRB: Internet Explorer に Table.innerHTML を設定しているエラー
原因
TABLE と TFOOT と THEAD と TR 要素の innerHTML プロパティは、読み取り専用です。
状況
この動作は、仕様です。
仕様といわれてしまってはそれまでですが、やっぱり直接書換えたいような場合もあるんじゃないかなぁ、と思って上記のような関数を作成してみたしだい。
余談ですが、リンク(A)要素のinnerHTMLはIE6でも通常は書き込み可能ですが、
linkElement.innerHTML='<a href="http://~">hoge</a>';
のようにすると『未知の実行時エラーです。』と出ることに、『りんくぽっぷ』を作っていて気が付きました。
まぁ、リンク要素内にリンク要素を書くようなことは推奨されないとは思うんですが。Wikipediaなんかで実際にこうなっていたりする箇所があって、りんくぽっぷでエラーが出てしまい、原因究明にてこずりました(苦笑)。

続きを読む "【JavaScript】IE6でTABLE要素内をHTMLで書換える関数" »

2007/12/08

【JavaScript】IE6のString.split()の不具合と対策

IE6のString.split()では、区切り文字(delimiter)を正規表現で指定したときに不具合があるようです。
具体的には、例えば『javascript:alert(',,'.split(/,/).length)』を実行すると、結果が「0」になってしまいます(正しくは「3」)。
ちなみにIE6でも『javascript:alert(',,'.split(',').length)』のように、正規表現を使わなかった場合には、結果が正しく「3」となります。
どうも、区切り文字で正規表現を指定した場合には、結果の文字列が ""(null) になるものは候補から外れてしまう、という動作になっているように思われます。
これはどう考えても仕様じゃなくてバグだよなぁ……。
そんなわけで、String.split() を使う場合には注意が必要です。
対策としては、次のようにして String.prototype.split を置換してやるとよいかもしれません。

【2007/12/11追記】
他にもいろいろ問題がありそうなので(例えばString.split()のブラウザでの差異 - ?D of K)、ちゃんとした対策が必要なら、『JavaScript split Inconsistencies & Bugs: Fixed!』を使用するのがよいと思われます。
if (navigator.userAgent.match(/msie/i)) {
    String.prototype.split=function(sreg){
        var text=this, reg=(typeof sreg=='string')?RegExp(sreg,'g'):RegExp(sreg.source,'g'+((sreg.ignoreCase)?'i':'')+((sreg.multiline)?'m':''));
        if (!reg.source) return [text];
        var sindex=0, eindex, elms=[];
        do {
            reg.exec(text);
            eindex=reg.lastIndex;
            elms.push(text.substring(sindex,(eindex)?eindex:text.length).replace(sreg,''));
            sindex=eindex;
        } while (sindex);
        return elms;
    }
}
ちなみに、これも『404 Blog Not Found:Google Code Chart - 折れ線グラフうぃざ~ど』で気付きました。

続きを読む "【JavaScript】IE6のString.split()の不具合と対策" »

【JavaScript】IE6におけるtable要素の初期化

IE6では、HTMLで『<table></table>』とだけ記述したtable要素をJavaScriptで取得すると、なぜかtbody要素がデフォルトで入っています(document.createElement('table')で作成した場合は入りませんが)。
これは、『table要素内が空である』ということを前提にしてコーディングしようとした場合なんかは困ります。
それならばと、table要素を初期化するために『table.innerHTML=''』とかしてしまうと、『エラー:未知の実行時エラーです。』なんてわけのわからないエラーが発生してしまいます。
これでは、既存のテーブルを一旦初期化して書き替える、といったことをやろうとした場合にも困ってしまいます。
そこで、IE6でtable要素のinnerHTML部分を初期化するには、例えば、
function    clearTable(table) {
    var elm;
    while (elm=table.firstChild) table.removeChild(elm);
}
のような初期化用の関数を用意してやってコールする、といった工夫が必要になってきます。
なお、この関数自体はIE6に限らず使えると思います。また、table要素以外の初期化も可能と思われます。
【テーブル初期化サンプル】
table要素
下のボタンを押すと、テーブルが初期化されます。

【HTML】

ちなみに、これは『404 Blog Not Found:Google Code Chart - 折れ線グラフうぃざ~ど』を見ていて気がつきました。

2007/12/04

【JavaScript】リンクオブジェクトのhref参照時のブラウザ毎の振る舞いの違い

JavaScriptでリンクオブジェクトのhrefを参照したときの振る舞いが、ブラウザ毎に異なることに気がつきました。
きっと常識なんでしょうけど……。
例えば、UTF-8のページ上で、日本語がエンコードされた参照先(URI)を持つ
<a href="http://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E8%AA%9E" id="testlink">http://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E8%AA%9E</a>
のようなリンク(http://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E8%AA%9E)がある場合。
JavaScriptにより当該リンクオブジェクトを取得し、そのhrefの値を適当な変数に入れてから表示すると、IE6では'%E6'以下が化けてしまいますし、Operaでは日本語に変換されてしまいます。
1.元のURI
2.リンクオブジェクトのhref値
3.1をunescapeしたもの
4.1をdecodeURIしたもの
5.3をescapeしたもの
6.4をencodeURIしたもの
どうやら、
  1. IE6 SP2では、勝手にunescapeされてしまう。
  2. Opera9.10では、勝手にdecodeURIされてしまう。
  3. Firefox2.0.0.11やIE7では、特に変換は行わない。
という動きのように思えます。
Operaの場合はencodeURIしてやれば元に戻るのでまだましですが、IE6の場合、単純にescapeしてやると、':'が'%3A'になったりといやらしい動きになってしまいます……うーむ、何か上手い方法あるでしょうか?
とりあえず、escape(href).replace(/%[0-7][a-f\d]/gi,function(s){return unescape(s)}) とか考えてみましたが。
【2008/01/27追記】
昨日IE7を入れてみたら、escapeは行わないようになっているみたいだったので追記。
あとは、日本語ドメイン名(というかIDN)の扱いも調べておいた方がいいかなぁ?
例えば、日本語.jpをどのように扱われているか、など。

2007/12/01

りんくぽっぷ:いまみてるページのリンクリストを表示するブックマークレット/Greasemonkey/SeaHorse

現在見ているWebページ(http://~)上にあるリンクを一覧表示するようなブックマークレットを作成してみました。
それだけだとなんなので、まだ見てないリンク(VISITED LINKS)と見たことあるリンク(NON-VISITED LINKS)とを分けて表示するようにしてみました。
なお、見たことがあるかないかわかるのは、ブラウザが覚えている分に限ります。うーん……役に立つかどうかは微妙なところ(苦笑)。
■使い方
任意のWebページ上で、次のブックマークレットを実行します(当然ながらJavaScriptが有効になっている必要が有ります)。
ブックマークレットの登録方法等はこちら→【Web】ブックマークレットのブラウザ毎の登録方法
また、同スクリプトはSeaHorseやGreasemonkeyとしても使えます。
Seahorseの使い方については、例えばこちらとかこちら。 Greasemonkeyの使い方については、例えばこちら
実行すると、ページの上の方に

Linkpop1

のように下向き矢印のアイコンが表示されます。これをクリックすると、

Linkpop2

のように、リンク一覧が表示され(最初はNON-VISITED LINKS)、アイコンが上向き矢印になります(再度クリックするとリンク一覧を閉じます)。
また、下の方には、

Linkpop3

のように、VISITED LINKSも表示されています。
VISITEDとNON-VISITEDの切り分けは、リンクの色を調べることで実施しています。これには、【JavaScript】各種色指定用の文字列を16進カラーコード(#xxxxxx)に変換の技を使っています。→動作が重くなってしまうので止めました(2007/12/06 ver.0.01f以降)。

続きを読む "りんくぽっぷ:いまみてるページのリンクリストを表示するブックマークレット/Greasemonkey/SeaHorse" »

« 2007年11月 | トップページ | 2008年1月 »

戻るリンク追加

カレンダー

2025年5月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

ココログカレンダーPlus(旧2)

検索


    • Web全体 サイト内
    • 蔵書のISBNを入力して下さい
    • はじめる前
      初級者向け
      上級者向け
      ブログ紹介
      結果を表示

コメントリストツリー化

無料ブログはココログ