/ 最近 .rdf 追記 設定 本棚

脳log[2008-02-13~]



2008年02月13日 (水) ファイルを編集していつものように Subversionにコミットしたら http越しに http://hikidoc.rubyforge.org にコミットしにいっていた。拒否されて本当に良かった。こういうときは SVKか Mercurialですか?

[Hiki][Ruby] HikiDoc(revision 90)のリストアイテムに段落を。

リストを使うと途端にそこから文章に構造がなくなってしまうのが困りもので、できるだけ !!(h4)や !!!(h5)を使うようにしていたのだけど、とうとうリストアイテム内で段落を書けるようにいじってしまった。(少し前の変更で取り除かれた \ エスケープが復活している……)

 hikidoc.rb.extend-listitem.diff (2.4KiB)

HikiDoc(r90)のリストアイテムに文章構造(段落)を持ち込むための変更

 HikiDocフォーマット

*li1-p1\
\
li1-p2A\
li1-p2B
*li2

 HTML

  • li1-p1

    li1-p2A li1-p2B

  • li2

 HTMLソース

<ul>
<li><p>li1-p1</p>
<p>li1-p2A
li1-p2B</p>
</li>
<li>li2</li>
</ul>
  • 影響を最小限にするために \を使って行をまたいだときだけ <p>タグが挿入される。
  • 段落以外の、実はすべてのマークアップが有効になっているが、どういう表示になるかは未確認。
  • 思いつきのやっつけです。

行末の \ は見落としやすいので *-、**-、***- を、前行のリストアイテムを継続する、という意味にするのはどうだろう。

 hikidoc.rb.extend-listitem2.diff (1.6KiB)

HikiDoc(r90)のリストアイテムに文章構造(段落)を持ち込むための変更2 (以前の変更からの差分なので順番に適用する必要がある)

 HikiDocフォーマット

* li1-p1
*-
*-li1-p2A
*-li1-p2B
*li2

 HTML

  • li1-p1

    li1-p2A li1-p2B

  • li2

 HTMLソース

<ul>
<li><p>li1-p1</p>
<p>li1-p2A
li1-p2B</p>
</li>
<li>li2</li>
</ul>

なぜ *+ でなく *- にしたんだろう……。

[Ruby] Ruby-1.9の文字コード関連

面倒くさいという印象しか持っていない。

文字コードに unawareなスクリプトが文字コードがらみで余計なことをされたあげく、動かなくなるというのだけはやめてほしい。

必要なときだけエンコーディング情報を付加するし、そのときだけエンコーディング情報を利用したメリットを受け取りたい

実際のところはどうなってるのでしょう。


2008年02月12日 (火) エクスプローラがメモリを放さない。プライベートワーキングセットは 14MBでも、Vistaより前のタスクマネージャが表示していた数字では 215MBになる。仮想メモリ不足ってメッセージが何度も出るんですけどー。

最終更新: 2010-03-21T03:27+0900

[javascript][SHJS] JSLint <http://www.jslint.com>

SHJSの sh_main.jsを高速化したことを以前書いた。(20080204p01)

 対応ブラウザ

SHJSのページには動作を確認したブラウザとして以下が挙げられている。

  • Firefox 2
  • Internet Explorer 6
  • Internet Explorer 7
  • Opera 9
  • Safari 3

sh_main.jsの修正版は Firefox2IE7Opera9で正しく動作することの確認と速度の比較を行っている。

IE6での確認は IE7から戻すのが面倒なので省略する。

Sarari3は Vistaで動くものがダウンロードできるので確認してみたところ動いた。(表示も正常)

いじったことで対応ブラウザが減っていなくて良かった。(IE6は?)

 JSLint

SHJSの作者は Code Conventions for the JavaScript Programming Languagejslint: The JavaScript Verifier かそれに類似した文書を読んでいるに違いない。(これらのページを今日発見した)

というのも、sh_main.jsを JSLintでチェックしてみたが、こういうエラーしか出なかった。

Error:

Implied global: document 362, sh_languages 347, window 332

このエラーは JSLint向けにコメントを埋め込めば取り除けるものだし、そうしないと不可避だともいえるもの。

 さてさて、自分がいじったことでどれだけお行儀の悪いスタイルが混入したのでしょうか
Error:

Implied global: document 207 360, sh_languages 332, window 329

Problem at line 73 character 48: Use the array literal notation [].

   matchCaches = language.matchCaches = new Array(language.length);

Problem at line 86 character 17: 'i' is already defined.

   for(var i = matchCaches.length-1; i !== -1; --i) {

Problem at line 97 character 22: 'i' is already defined.

   for (var i = state.length-1; i !== -1; --i) {

Problem at line 110 character 17: 'i' is already defined.

   var i = (pair[0] & 0x3F);

Problem at line 280 character 15: Use '!==' to compare with '0'.

   while(0 != this._currentStyles.length) {

Problem at line 389 character 14: 'node' is already defined.

   var node = this.free_;
 いいわけ
  • ローカル変数の再宣言に関するものは全て forループで初期化される変数が原因。(i と node)
  • 「var array = new Array(length);」を「var array = []; array.length = length;」や「var array = Array.prototype.slice.call({length:length}, 0);」 と書き換えることは拒否する。

    (new Array(length)が一番簡潔で自然な書き方)

    (JavaScript 1.7の配列内包に書き換えるのには吝かでない)

  • != 0 の代わりに !== 0 と書け? 書き換えましたm( __ __ )m

(一つを除いて) 無視できる警告*ばかりで良かった。

* 無視したら警告の意味がない。forループの変数なんて(古い VC使い以外には)スコープの誤解を招きやすいという理由で、避けなければいけないものの筆頭ともいえる。


2008年02月08日 (金) 寝ているすきに腕まくらをされに来ていたにゃんこ。これだから冬は(≧∀≦)

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (1)

Under Translation of ECMA-262 3rd Edition を読んでいて見つけた「故意にに汎用的である」と書かれたメソッド群。Arrayと Stringのほとんどのメソッドが該当する。

Firefoxが Array.prototype.methodなどを Array.methodからも参照できるようにする(している)のも 仕様が汎用的で再利用が可能になっているからだろう。

それら汎用的なメソッドを使い回してやるために、その動作を javascriptのコードで表してみる。

 Array.prototype.concat([item1[, item2[, ...]]])

function() {
  var array = [];
  var array_index = 0;
  var item = this;
  var arg_index = 0;
  do {
    if(item instanceof Array) { // (1)
      var item_index = 0;
      while(item_index !== item.length) {
        if(item_index in item) {
          array[array_index++] = item[item_index++];
        } else {
          ++array_index; ++item_index; // (2)
        }
      }
    } else {
      array[array_index++] = item;
    }
  } while(arg_index !== arguments.length &&
          (item = arguments[arg_index++] || true)
  );
  array.length = array_length;
  return array;
}
  • thisと引数は Arrayであるかどうかをチェックされ、扱いが変わる。(1)
  • 新しい配列にコピーするときに、(Arrayである) thisや引数の疎な部分が省略されることはない。(2)
  • thisが Arrayでない場合、単に戻り配列の "0" プロパティに thisオブジェクトが入っているというだけである。(余計な引数なしで呼び出して、Arrayでなければ配列化、という使い方ができる。うれしいか?)

 Array.prototype.join(separator)

function(separator) {
  var length = this.length>>>0; // (1)-1
  if(typeof(separator) === "undefined") {
    separator = ",";
  }
  separator = ToString(separator);
  if(length === 0) {
    return "";
  }
  var result;
  var index = 0;
  var item = this[index++];
  item = (item == null) ? "" : ToString(item);
  result = item;
  while(index !== length) { // (1)-2
    item = this[index++];
    item = (item == null) ? "" : ToString(item);
    result += separator + item;
  }
  return result;
}
// argが数値の場合は厳密には違うかも。
// 以後もたびたび登場する予定。
function ToString(arg) {
  return ""+arg.valueOf();
}
  • 0 から数値化された lengthプロパティの値までを処理する。(1)
  • 戻り値は必ず文字列であり、this.length-1 個のセパレータを必ず含む。
  • thisが Arrayでない場合、例えば Array.prototype.join.call({length:10+1}, " ") で長さが 10 のスペースを得ることが期待される。

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (2)

 Array.prototype.pop()

function() {
  var length = this.length>>>0;
  if(length === 0) {
    this.length = length; // (1)-1
    return; // undefined
  }
  length -= 1;
  var result = this[length];
  delete this[length];
  this.length = length; // (1)-2
  return result;
}
  • (数値化された) this.lengthを 1つデクリメントしたプロパティを削除し、その値を返す。
  • pop()を呼び出すと副作用で lengthプロパティが数値になる。(1)

 Array.prototype.push([item1[, item2[, ...]]])

function() {
  var length = this.length>>>0;
  for(var i = 0; i < arguments.length; ++i) {
    this[length++] = arguments[i];
  }
  this.length = length; // (1)
  return length;
}
  • (数値化された) this.lengthをインクリメントしながら引数をプロパティに設定していく。
  • push()を呼び出すと副作用で lengthプロパティが必ず数値になる。(1)

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (3)

 Array.prototype.reverse()

function() {
  var length = this.length>>>0;
  var mid = Math.floor(length/2);
  for(var p = 0; p !== mid; ++p) {
    var q = length-p-1;
    var P = this[p], Q = this[q];
    if(!(q in this)) {
      delete[p];
    } else {
      this[p] = Q;
    }
    if(!(p in this)) {
      delete[q];
    } else {
      this[q] = P;
    }
  }
  return this;
}
  • 0 から数値化された lengthプロパティの値までを処理する。
  • reverse呼び出し前後でプロパティ(疎な要素など)が増えたり減ったりはしない。

 Array.prototype.sort(comparefn)

sort()については、this.length>>>0 が用いられることと、thisを変更して thisを返すことだけを書いておいて、i, j要素を比較する手順について。

function compare(i, j) {
  // (A) 存在しない要素を最後尾へ。
  if(!(i in this)) {
    if(!(j in this)) {
      return +0; // (1)
    } else {
      return 1;
    }
  } else if(!(j in this)) {
    return -1;
  }
  var I = this[i], J = this[j];
  // (B) undefinedな要素を後ろへ。
  if(typeof(I) === "undefined") {
    if(typeof(J) === "undefined") {
      return +0; // (1)
    } else {
      return 1;
    }
  } else if(typeof(J) === "undefined") {
    return -1;
  }

  // (C) ユーザー比較関数
  if(typeof(comparefn) !== "undefined") {
    return comparefn(I, J);
  }
  // (D) デフォルト比較方法 (文字列化して昇順)
  I = ToString(I), J = ToSTring(J);
  if(I < J) {
    return -1;
  } else if(I > J) {
    return 1;
  }
  return +0;
}
  • デフォルトの比較関数はやっぱり要素を文字列化していた。(20080207p01。数値配列を期待通りにソートすることはできない)
  • +0 と書いてみたが、スクリプトで +0 と -0 を区別することはできない。(1)

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (4)

 Array.prototype.shift()

function() {
  var length = this.length>>>0;
  if(length === 0) {
    this.length = length; // (1)
    return; // undefined
  }

  var result = this[0];

  // ひとつずつ前へスライド
  for(var k = 1; k !== length; ++k) {
    if(k in this) {
      this[k-1] = this[k];
    } else {
      delete this[k-1];
    }
  }

  delete this[length-1];
  this.length = length-1;
  return result;
}
  • 0 から数値化された lengthプロパティの値までを処理する。
  • shift()を呼び出すと副作用で lengthプロパティが必ず数値になる。(1)

 Array.prototype.unshift([item1[, item2[, ...]]])

function() {
  var length = this.length>>>0;
  // 要素を引数の数だけ後ろへシフト。
  for(var k = length; k !== 0; --k) {
    var j = k-1;
    if(j in this) {
      this[j + arguments.length] = this[j];
    } else {
      delete this[j + arguments.length];
    }
  }
  // 先頭に引数をコピー
  for(var k = 0; k !== arguments.length; ++k) {
    this[k] = arguments[k];
  }

  this.length = length + arguments.length; // (1)
  return this.length;
}
  • 0 から数値化された lengthプロパティの値までを処理する。
  • unshift()を呼び出すと副作用で lengthプロパティが必ず数値になる。(1)

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (5)

 Array.prototype.slice(start, end)

function(start, end) {
  var array = [];
  var array_index = 0;
  var length = this.length>>>0;
  start = ToInteger(start);
  start = (start < 0) ? Math.max(length+start, 0) : Math.min(start, length);
  end = (typeof(end) === "undefined") ? length : ToInteger(end);
  end = (end < 0) ? Math.max(length+end, 0) : Math.min(end, length);
  for(var k = start; k < end; ++k) {
    if(k in this) {
      array[array_index++] = this[k];
    } else {
      ++array_index; // (1)
    }
  }
  array.length = array_index;
  return array;
}
function ToInteger(x) {
  x = +x;
  if(isNaN(x)) {
    return +0; // (2)
  }
  if(x === 0 || !isFinite(x)) {
    return x;
  }
  // 0 に近づける。(小数点以下を切り捨てる)
  return (0 <= x) ? Math.floor(x) : Math.ceil(x);
}
  • 数値化された this.lengthと引数 start, end を元に範囲を決定し処理する。
  • startと endには負数も使用可。
  • thisの存在しない要素がコピーされることはないが、無視されるわけではない。(1)
  • +0 と書いたがスクリプトが +0 と -0 を区別することはできない。(2)

 Array.prototype.splice(start, deleteCount[, item1[, item2[, ...]]])

function(start, deleteCount) {
  var array = [];
  var length = this.length>>>0;
  start = ToInteger(start);
  start = (start < 0) ? Math.max(length+start, 0) : Math.min(start, le  deleteCount = ToInteger(deleteCount);
  deleteCount = Math.min(Math.max(deleteCount, 0), length-start);
  // 取り出す要素をコピー。(まだ削除はしない)
  for(var k = 0; k !== deleteCount; ++k) {
    var l = start+k;
    if(l in this) {
      array[k] = this[l];
    }
  }
  // (A) 要素を後ろへずらして空きを作る
  //     (後で上書きされてしまう要素までずらしてない?)
  if(deleteCount < arguments.length) {
    for(var k = length-deleteCount; k !== start; --k) {
      var l = k + deleteCount -1;
      var m = k + arguments.length -1;
      if(l in this) {
        this[m] = this[l];
      } else {
        delete this[m];
      }
    }
  // (B) 要素を前へ詰めて、空きを新要素と同じ数にする
  } else if(arguments.length < deleteCount) {
    for(var k = start; k !== length-deleteCount; ++k) {
      var l = k + arguments.length;
      var m = k + deleteCount;
      if(m in this) {
        this[l] = this[m];
      } else {
        delete this[l];
      }
    }
    // (なぜ逆順で削除する?)
    for(var k = length; k !== length-deleteCount+arguments.length; --k) {
      delete this[k-1];
    }
  }
  // 追加する要素で thisを上書き
  if(arguments.length === deleteCount) {
    for(var k = 0; k !== arguments.length; ++k) {
      this[start+k] = arguments[k];
    }
  }
  this.length = length - deleteCount + arguments.length;
  return array;
}
  • 数値化された this.lengthと引数 start を元に範囲を決定し処理する。
  • startには負数も使用可。
  • splice()を呼び出すと副作用で lengthプロパティが必ず数値になる。
  • splice()は仕事を抱え込みすぎ。

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (6)

パターンがわかってきた。

 Arrayのビルトインメソッドは concat()を除いて、this.lengthを 32-bit unsigned int(UINT32)に変換したものを処理範囲の上限として利用する。(下限はもちろん 0)

  • concat()は thisや引数が Arrayかどうかで処理を変えるから、(利用するときは) this.lengthを直接用いる。

 自身の長さを変更するメソッド(pop(), push(), shift(), unshift(), splice())を呼び出すと lengthプロパティは必ず UINT32に変更される。

  • 自身の長さを変更しない reverse(), sort()や、自身を変更しない concat(), join(), slice()を呼び出しても lengthは変化しない。

 あんまり使えない。

 Arrayの多くのメソッドは自己破壊的で、Stringに適用することができない。

Firefox(JavaScript1.5?)では適用すると Array.prototype.method.call(string) is read-onlyという警告がいくつも出る。

一度の呼び出しでいくつも出ることから read-onlyなのはメソッドではなく、自己破壊的な Array.prototype.method()を適用された stringだと思われる。

警告が出るだけで、戻り値は得られるので pop()で末尾の文字、shift()で先頭の文字が得られる。

unshift()、push()の返す数値は使えない。(その長さを持った文字列は存在しないので)

reverse(), sort()は全く役に立たない。役に立つ値を返さないし、並べ替えには失敗しているから。

slice()は String.prototypeに同名のメソッドがすでに存在する。

splice()がちょっと面白く、splice(startと deleteCount) の引数はちょうど String.prototype.substr(start, length)と対応するが、返ってくるのが substr()->"文字列" なのに対し、splice()->["文","字","列"] となる。

string.split("")の代わりに Array.prototype.splice.call(string, 0)としてみるのはいかが? (警告が出る上に IE7では使えませんが)

 そもそも IEの JScript5.7では文字列に添え字を使ってアクセスできないので Arrayのメソッドを適用しても意味がない。

Stringではなく、配列のようなオブジェクト(コレクションとか argumentsとか)に適用するのが正解か。

 concat()と join()は広く使える。

 配列化1 (びみょ〜)
// unknown_objectが配列なら unknown_objectのコピー、
// それ以外なら unknown_objectを唯一の要素とする配列が返る。
var array = Array.prototype.concat.call(unknown_object);
 配列化2 (IEでは使えない)
// lengthプロパティと [] での要素アクセスが可能な、
// 配列っぽいオブジェクト(NodeListとか argumentsとか)を配列化。
var array = Array.prototype.slice.call(document.getElementsByTagName("pre"), 0);
 繰り返し文字列 (けっこう使える)
// "        " が得られます。
var softtab = Array.prototype.join.call({length:8+1}, " ");
// softtabを使ったレベル3のインデントが得られます。
var indent = new Array(3+1).join(softtab); // prototypeいらねー

二番目の書き方を使うなら Arrayのビルトインメソッドを他のオブジェクトに使い回すという趣旨が……。

[javascript] Arrayと Stringの故意に汎用的なメソッドたち (7)

今回から String.prototypeのメソッド。Stringは自己破壊的なメソッドを持っていないから、Arrayのビルトインメソッドと違って適用範囲が広いことを期待している。

……と思ったが、Stringのビルトインメソッドはどれも事前に ToString(this)を呼んでしまう。(ToString()については (1))

indexOfで配列の探索ができるんじゃないかと期待していたが無理だった。がっかり。

これにて終了。

split()に渡す正規表現のサブマッチに特別な意味があるとか、split()はグローバルフラグを無視するとか、グローバルフラグの立った正規表現で exec(string)を呼んで空文字列にマッチしたときはこちらで lastIndexプロパティを 1増やさないといけないとか、もやもやしてたことが Under Translation of ECMA-262 3rd Editionにはいろいろ書いてありました。訳者に感謝。


2008年02月07日 (木)

[javascript] 普通に使ってて驚いた JavaScriptのヘンなところ

switch-caseのラベル部分に何でも書ける*という変態的な部分は置いておいて、1/2が 0.5になるのと同じ類の話。

 [1, 5, 10].sort()

返るのは [1, 10, 5]。どうも文字列としてソートされている。数字だけの配列だ(と自分が知っている)からって無駄な期待をしてはいけない。

 for(var i in [1, 5, 10]) { alert(typeof i) }

stringと表示される。i に入るのが配列の要素 1, 5, 10 ではなく添え字(key)だというだけで意外だが、さらに意表をついてそれが数字でもなく、"0", "1", "2" 。i+1 としても隣の要素にはアクセスできない。array[1] = 5; と数字の添え字で代入した後でも同じだった。

for-in ループは遅いと評判だったので使っておらず、最近まで知らなかった。

ついでにいうと for-inとは別の in 演算子の存在も、つい最近まで知らなかった。(SHJSのソースを読んで発見した)

グローバル変数の存在確認方法も SHJSを読んで知った。今までは typeof(global_var) == "undefined" とやっていた。これは存在確認とは微妙に意味が違う。

deleteできるグローバル変数とできないグローバル変数については amachangのブログを読んで知った。

 オブジェクトのプロパティ名、連想配列のキー、配列の添え字、ぜんぶ同じぜんぶ文字列。

  • プロパティ名はすべて文字列化されてからオブジェクトに渡されること
  • 配列は特殊な lengthプロパティを持った Object

ということを知っていれば for-inループで文字列が渡されても驚くことはない。(じゃあなぜ驚いた)

var object = {};
alert(""+ (1==="1")); // false
o[1] = 1;
o["1"] = "1";
alert(""+ (o[1]===o["1"])); // true

var a = [1,2,3];
var b = [1,2,3];
alert(""+ (a===b)); // false
o[a] = a;
o[b] = b;
alert(""+ (o[a]===o[b])); // true

var s = "1,2,3";
alert(""+ (a===s)); // false
o[s] = s;
alert(""+ (o[a]===o[s])); // true

* http://arton.no-ip.info/diary/20061224.html#p02


2008年02月06日 (水) パスワードを間違えたときに Caps Lockがオンと教えてくれる Vista。

[javascript] 継承。prototypeへの代入

こんなやりとりがあった。

  1. 404 Blog Not Found:javascript - 勝手に添削 - JavaScript入門
  2. 「勝手に添削 - JavaScript 入門」を勝手に添削 - IT戦記

dankogaiの指摘の一つ、「MyObject.prototype = { /* ... */ }は避けるべし」について。

MyBox.prototype = Box.prototype; // ここで継承しているのに
MyBox.prototype = {              // ここで台無し
  speed:8
};

指摘には同意するが、上の例だと親の Boxにも MyBoxと共通のクラス変数 speedが追加されている。

amachangの反論にあるように、継承するなら次の書き方の方が優れている

MyBox.prototype = new Box; // new Box()と同じ

ところで、amachangはこう続けている。これがプロトタイプ継承の正しい形式であり

MyBox.prototype = {};
MyBox.prototype = new Object;

指摘を受けた上段の書き方は下段のシンタックスシュガーなので両方とも Objectからの正しい継承方法である。このことがわかっていれば、他から継承しておきながら Objectからも継承して台無しにするような間違いは犯さない。(だから必要に応じて違う書き方をすれば良いし、必要がなければ .prototype={}と書いても良い)。

でも違う理由で MyObject.prototype = {}; という書き方は避けている。 理由は一つで

MyObject = function(){};
MyObject.prototype = {/* MyObjectの定義 */};
alert(new MyObject().constructor); // function Object(){ [natiive code] } と表示

constructorプロパティが MyObjectではなくなってしまっている。これは MyObject.prototype = new MyParent; としたときも同じで constructorは MyParentになる。

constructorプロパティだけを気にするなら

MyObject.prototype = { constructor:MyObject.prototype.constructor };

というように明示的にコピーすればごまかせるが、一時しのぎ感がぷんぷんしている。(constructor以外のプロパティが追加されたらその都度書き換えるの?)

だから、継承の仕方で逆につっこまれてしまったが、dankogaiが提示した 3つの方法に自分は同意していて、そのうちの一つ目を自分は使っている。

北極の氷。融けるとどうなる?

http://blog.mf-davinci.com/mori_log/archives/2005/12/index.php

勘違いしていた。水面より上にある氷が融ければ水面が上がると思っていた。

重さで考えてみる。

氷の全体の重さは、水面下に沈んでいる氷の体積と同じ量の水の重さと等しい。(浮力)

氷が融けてすべて水になるとどうなるか。水面下にあった氷と同じだけの体積を占めることになる。(同じ重さの水は同じ体積)

水面は上昇しない。

ところで、氷の水面に浮かんでいる部分は、水が凝固したときに増加した体積分だという見方はしたことがなかった。(凡人以下)

いやになるなあ。


2008年02月05日 (火)

day と date

すごい勢いで釣られてしまった。

  • getDay() … 日にちを求める
  • getDate() … 年月日を求める

だと思っていたら、

  • getDay() … 曜日を求める
  • getDate() … 日にちを求める

だった。違うだろそれ。(笑)

きっと一部の人間が勝手に決めちゃったんだろうなぁ。

http://blog.xe.bz/archives/50286628.html

いやいやいやいや、英語を知ってる人が決めちゃったんだよ。

http://en.wikipedia.org/wiki/Days_of_the_week

でも釣られてしまうのは、day(日)、date(年月日)もありだと思っているから。


2008年02月04日 (月)

[SHJS][javascript] sh_main.jsの高速化

SHJSのブラウザでの実行時間を削るには sh_main.js(SHJSのメインスクリプト)を速くするか、正規表現を効率的なものにする方法がある。(>遅い正規表現(20080116p01))。

正規表現に関してできることは限られるうえ、知識も少ない(『詳説 正規表現 第三版』待ち)ので、可能な限り文字クラスや文字集合といわれるものを使うように気を付けただけにとどまる。(sh_ruby.js, sh_javascript.js)

メインスクリプトの sh_main.jsに対してできることは多い。この日記の現在?の最新ページ(2008年1月12日から7日間)を表示して、sh_highlightDocument()前後での経過時間を表示したところこのようになった。

Firefox2IE7(64-bit)IE7(32-bit)Opera9.25
sh_main.js (0.4.2)935ms1050ms1270ms1260±150ms
改変版600ms680ms865ms1200±150ms
削減率36%35%32%5%

ハイライト対象が少なくて数ミリ秒で処理が終わるような場合はオーバーヘッドのために改変版の方が 1-2ミリ秒遅くなるが、それよりもスクリプトがブラウザをロックする時間が長くなるような場合にこそ速度改善が必要なので OK。

代償としてファイルサイズが sh_main.jsで 10.5KiBから 12.7KiBへ +2.2KiB。jsmin圧縮後の sh_main.min.jsで 6.22KiBから 7.82KiBへ +1.60KiB。Apacheによる gzip圧縮やブラウザのキャッシュに期待します。

 (補足) Operaについて

普段は全く Operaを使わないし、詳しくもない。むしろ Operaではキーボードを使ったブラウジングもままならない。そんな人間が Firefox+Firebugを頼りに sh_main.jsの修正を行ったので Operaの速度が改善しないのは仕方のない部分がある。(IEは改善したが)。(あんだけいじってトータルで変わらない方がすごい。どこが足を引っぱっているのだろう)。リテラル文字列と Stringオブジェクトの差が他のブラウザより大きいらしいが、それが原因?

EfficientJavaScript - Dev.Opera - 効率的な JavaScript (www.hyuki.com)

Operaでの JavaScriptの実行時間が他のブラウザに比べて長いのははっきりした理由があって、Operaはスクリプトが全力疾走中であってもユーザーの操作に対する反応を後回しにしたりしない。これは偉い。ユーザーを待たせない代わりにスクリプトが遅れるのは当然の代償で仕方がない。

あ、スクリプトでなく再描画が律速してるから改善しないということ?

 (かんせいしたへびのえにあしをかきくわえるこうい、とまでは言わないが) この日記で使用中のファイルへのリンク

(常に最新版だが一時的にバグが混入していることがあるかも)

 追記@今日:sh_main.jsをちょっと修正。

すぐ上のリンク先はすでに変更が反映されている。

これら二つの記事を参考に escapeHTML()を変更した。測定に使ったページでは 9000回ちかく呼び出されるメソッドなので影響はバカにならない。といっても 600msだったのが 590msを切るようになった、というレベル。むしろ下請けfunctionを隠蔽できたことの方が嬉しい。

escapeHTML()自体、sh_builderのインターフェイスではないので、外部から呼び出せないようにすべきかもしれないが、functionをかぶせるたびに呼び出しのオーバーヘッドが増える気がしてそうはしていない。

 追記@今日

SHJSの patternStackオブジェクトは外部と完全に独立して動作するのに、sh_highlightString()が呼ばれるたびに無名クラスとそのインスタンスを作成するような方法がとられている。コンストラクタと prototypeを書こう。(sh_highlightString()は HTML文書内の <pre class="sh_XXX">の数だけしか呼ばれないから影響は小さいが。件のページでは 58回)。

sh_highlightString()からしか使われないのにスタックの可視範囲が広がるのが気になるなら、さっき覚えた無名functionで二つをくるんでしまえば良い。

var sh_highlightString = (function(){
  var Stack = function(){
    this.stack_ = [];
  };
  Stack.prototype.getLength = function(){/* ... */};
  // ……
  return function(){
    var patternStack = new Stack();
    /* sh_highlightStringの中身がつづく…… */
  };
})();

まあ、速度が改善するわけではないので、書き直さないんだけど。

 追記@2008-02-25

innerHTMLや textContent、innerTextの使用は堕落だという気もするが、冗長な上に呼び出しを重ねることで遅くなる DOMメソッドがいけない。


2008年02月03日 (日) Thunderbirdが全角文字を URLに含めてしまって困る。「http://www.amazon.co.jp/your-account)ページ」なんてページはない。アマゾンも他の URLのように半角かっこで囲ってくれればいいのに。

読み方いろいろ

 warning

口をあまり開けないで「ア」と発音したらワーニン(グ)ウォーニン(グ)に聞こえる。どっちでもいい。どっちゃでもいい。これこそどちらでもよい。オーストラリアの人の発音が知りたいこの頃。

 GUI (Graphical User Interface)

ジーユーアイと読んで悪いことはないと思うが、Wikipedia(ja)にも載っているように、グイと読むと知ったときはひっくり返った(比喩)。

 ECMAScript (これの 3といえば標準化された JavaScript。そろそろ 4も)

イーシーエムエースクリプトと読んでいた。YouTubeで Googleの人がエクマスクリプトと喋っていて、なにそれ??? もうエクマにも慣れたが。

 IEEE (アメリカの電気関係の学会という認識。いろんな規格に名前が出てくる)

けっこう前からアイトリプルイーだと知っている。最初はアイイーイーイーと読んでいた。トリプルって言いにくいよね。どうしても四音節になる。

 W3C (World Wide Web Consortium, WWWの標準化団体)

今も頭の中でダブルさんスィーと読んでいる。ス・リーより さんの方が早い。w3mはもちろんダブルさんエムと読んでいる。

 WSH (Windows Scripting Host 改め Windows Script Host 改め Windows Script とか)

ウッシュと読むような気がする。こう読むんだよって(おそらく) Microsoftのコラムで読んだのだけど、英語だったので、その英語はなんと読むんですか? PostgresQLのように mp3ファイルを置いておいてくれると助かります。

読めないから WSHは(頭の中でも)読んでいない。Wと Sと Hが並んだもの、から、JScript、VBScript、ActivePerl、ActiveScriptRubyといった各種スクリプトエンジンをホストして Windowsや IEで実行可能にするアレ、という認識に直結している。

 locale

ロカールと読む必要を感じない。カタカナだとだいたいロケールと書いてある。あれっ、ロケールが正解?

XSS Challenges

6問目がわからない。UTF-7とか出てくる前にもう。


2008年01月18日 (金) Thunderbirdにスパムマークと一緒に確度も表示してほしい。それでソートすれば「うーん?どっちだろ」から「絶対スパム!」なメールに振り向ける注意力に差を付けられるのだが。


2008年01月17日 (木) 直木賞きまったね。まだ読んでない。たぶん次のハードカバーが出る頃に読む。

第3回 全国一斉!日本語テスト "口説き力"判定 | ATOK.com

今年も期待してました。(去年の結果一昨年の結果)。口説き力はおいといて今年の日本語テストの結果は

第3回 全国一斉!日本語テスト結果 (70点)

間違えたのは

第8問 部長(上司)に対する敬語で不適切なものは、どれ?

  1. 京都のおみやげです。部長、どうぞ頂いてください。
  2. 京都のおみやげです。部長、どうぞ召し上がってください。
  3. 京都のおみやげです。部長、どうぞお食べください。

第9問 優位な立場を利用して高圧的な態度をとる意の「かさに着る」の「かさ」。正しい書き方は、どれ?

  1. 暈(かさ)に着る
  2. 傘(かさ)に着る
  3. 笠(かさ)に着る
  4. 嵩(かさ)に着る

第10問「部下は酒が弱いため、ちびりちびりと{飲む・呑む}」、正しいのは、どっち?

  1. 飲む
  2. 呑む

8問目は単なるケアレスミス。


2008年01月16日 (水) Pythonかわいいよ、Python

[正規表現] 遅い正規表現

/\/(?:\\.|[^\n\\\/])+\/[gim]*(?!\w)/g
/\/(?:\\.|[^\n\\\/]+)+\/[gim]*(?!\w)/g // 死ぬほど遅い

どちらも同じく /regexp/i みたいな正規表現リテラルにマッチするのだが、下の方がブラウザが固まるほど遅い*。バックトラックの影響だろうか、これまで気にしてこなかったが……。原因が推測できない(無能)のがイタイ。(この日記、SHJSによるソースコードハイライトを多用していると読み込み完了直前にブラウザの反応がいっとき消えている。正規表現を最適化する余地があるならしたい)

昨日 rubyco(るびこ)の日記 (2007-06-20 正規表現の選択) 経由でウィッシュリストに入れた『詳説 正規表現 第2版』がタイムリーすぎる。

 いけないバックトラックの例

 reg = /.*x|a/
 s = "a" * 11_000_000
 m = reg.match(s)

http://d.hatena.ne.jp/kkos/20060801#1154438784

* 一度に多くの文字をつかんで繰り返しを減らそうという目論見がまんまと外れてしまった。

 原著は第3版が既に出ている。ちょっと(1、2年?)待ってみよう。<追記:2008-04-26に第三版(日本語)が発売*予定*。未だ Amazonに入荷せず(@2008-04-28)。22日にオライリーの通販で届いたって人もいるのに。>

[SHJS][SakuraEditor][javascript] SHJSと SakuraEditor用のハイライトルールファイル

SHJSの javascript定義ファイル(lang/sh_javascript.js)の元になったファイル(javascript.lang)の中身がこれ。

include "java.lang"

subst keyword = "abstract|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|final|finally|for|function|goto|if|implements|in|instanceof|interface|native|new|null|private|protected|prototype|public|return|static|super|switch|synchronized|throw|throws|this|transient|true|try|typeof|var|volatile|while|with"

javaて……。キーワードにしても使ったことのないものがいっぱい。

あまりにあんまりなんで一から書いた。(sh_javascript.js, sh_javascript.min.js)。 参照したのは JScript5.5の HTMLHelpなので JScript.NETや ECMAScript4には対応していない。古典的な JavaScript。

ついでに同じものを SakuraEditorにも。(javascript_keywords.zip)


2008年01月15日 (火)

[javascript] ラベル付きブロックと break - JScript

オブジェクトリテラルの存在も JScript5.5の HTMLHelpでの扱いが一行しかなかった*ために気付くのが遅れたが、これはその上をいく。古典的な javascriptで何年ぶりの新発見。しかもこれもちゃんと書いてあった。(但し一行)

block:
{
  break block;
}

ループにラベルを付けて、ネストしたループから外側のループを continueしたり breakしたりできるのは知っていた(が忘れていた)が、ラベル付きブロックと breakのこの使い方は全く知らなかった。

labelのスコープはローカル変数と同じみたいで、functionをまたいで goto代わりには使えなかった。

 余談

 function(){ alert(1) }();
 void function(){ alert(2) }();
 (function(){ alert(3) })();

alertが表示されるのは 2と 3。二行目ので初めて voidの使い途を見つけたと思ったのだったが三番目の書き方もあるらしく、またそれが一般的っぽい。まあ、なんとなくわかる。あれでしょ。

 余談の追記@2008-02-25

1行目の functionだけ functionExpressionではなく functionDeclarationだと解釈されて、続く () は関数呼び出しのかっこではなく、式をグルーピングするかっこになるのだとか。(functionDeclarationでは関数名を省略できないのだから、(省略できる)functionExpressionだと解釈してくれても良さそうなものだが)

そういう理由なのでこんなのもあり。

+function(){ alert(4) }();
(function(){ alert(5) }());

* JScript>ユーザーズ ガイド>JScriptの基本>JScriptコードの記述

 JScript>ランゲージ リファレンス>ステートメント>break ステートメント

 ローカル変数なら自分より外側のものが見える。ラベルは見えないのでそれより厳しい。


2008年01月14日 (月)

[Hiki] Hiki-0.8.7をインストール

絶望した! Hikiに同梱されてる HikiDocのバージョンが tDiaryのより低いのに絶望した! (tDiaryのもたいがい古いのに>20080112p01) (もちろん最新の hikidoc.rbに入れ替えました)