Kanasan.JS JavaScript 第 5 版読書会 #22008年01月21日 19時49分

Kanasan.JSJavaScript 第 5 版読書会 #2 へ行ってきました。今回は午前ライトニングトーク、午後読書会という二部構成。他の参加者の感想等は参加者のブログ記事一覧からどうぞ。参加人数が 50 人近くという大規模な読書会を企画し、無事成功に導いてくださった Kanasan さんはじめスタッフの方々、そして参加者の皆さん、本当にありがとうございました。

ライトニングトーク

Lightning Talk 一覧および Lightning Talk 発表資料一覧から各 LT に関する情報が見られます。

Mozilla Developer Center 翻訳事始

私も LT に参加したのですが、JavaScript に関することなら何でも OK とあるのを見落としていてほとんど関係ない話 (一応翻訳作業の実演は Core JavaScript 1.5 リファレンスの arugments の項を例にとって行いましたが)、しかも 5 分と申告しておきながら倍以上の時間を使ってしまいました。

内容は OmegaT を使った Mozilla Developer Center の翻訳作業の進め方ですが、これはあくまで個人で使っているだけであって、翻訳作業に OmegaT を使うと決まっているわけではありません。

JSDeferred による非同期処理

東京から駆けつけてくださった amachang さんが、cho45 さん作成の JSDeferred を紹介。JSDeferred は Python で書かれたネットワークアプリケーション用フレームワーク Twisted に影響を受けているそう (正確には JSDeferred は MochiKit Deferred に影響を受けていてMochiKit Deferred が Twisted に由来している?)。エラーハンドリングもできる。しかしついつい deffer と typo してしまいます。

単に自分の開発経験を (話せる範囲で) 話そう

AWAWA さんによる自身の JavaScript 歴の話。プロトタイプの存在を知らずに以下のように書いていたというのは私も同じです。

function A() {
  this.method = function () {};
}
var a = new A();

Script.aculo.us の UnitTest の使い方

ema さんによるプレゼン。script.aculo.us はスクリプトアークラスと読む?

Javascript で無限ループを実現する 5 つの方法

yhara さんによる非同期処理を同期処理のように書く話。最後の方法は自分で言語処理系を作ってしまうこと。YARV はヤーブと読む?

JavaScript から見た Haskell

氏久さんによる Haskell の話。Ruby は自然なコード、Haskell はシンプルなコードが書けることを目標にしている。Haskell は草案を書くのにお勧めとのこと。

Flex & de.popforge.audio ライブラリを使ってリアルタイムで音生成と再生

井上謙次 (deq) さんによる音声合成の実演。自然な音声を出すためには少なくとも 6000Hz (秒間 6000 回)、つまり 60fps (秒間 60 回) の動画と比べて 100 倍の頻度で処理をしなくてはいけないみたい。

読書会

50 人近くが机を四角に並べて向き合うという異例の読書会。「6 章 文」から「7.4 Object のプロパティとメソッド」まで読み進めました。以下読書会中に話題になった事柄を補足も交えて取り上げていきます。

式と文

if (x) { ... } としたときに、x に書けるものが式、書けないものが文。

文にも返り値が存在するが、普通のコード上からは見えない。文の返り値は JavaScript で通常扱える値以外に empty という値を取りうる。eval 関数を使ってプログラムを評価すると、そのプログラム中で最後に empty でない値を返した文の返り値を、eval 関数の返り値として見ることができる。

eval("1 + 2"); // => 3
// セミコロンの自動補完により式文 1 + 2; と解析される。
// 式文の返り値は式分を構成する式の返り値なので、
// 式 1 + 2 の返り値である 3 が eval 関数の返り値となる。

eval("42; var x = 12;"); // => 42
// var 文の返り値は empty なので、式文 42; の
// 返り値である 42 が eval 関数の返り値となる。

eval("var x = 42;"); // => undefined
// すべての文の返り値が empty である場合、
// eval 関数の返り値は undefined となる。

function キーワードを用いた構文

function キーワードが関数宣言の一部として使われているのか、関数式の一部として使われているのかは、その function キーワードが出現した時点で決まる (文が終了した直後、すなわち新たな文の開始位置に function キーワードが来れば、それは関数宣言のものとみなされる)。関数が名前つきか無名か、関数本体の後に何が続くかなどは関係ない。

eval("function f() {}"); // => undefined
// 関数宣言の文としての返り値は empty。

eval("function add(a, b) { return a + b; }(2, 3);");
// => 3
// function add... が関数宣言として扱われるので、
// (2, 3) の括弧は関数呼び出しの括弧ではなく
// 式をまとめるための括弧、カンマは引数リストの
// 区切りではなくカンマ演算子として扱われる。
// 式文 (2, 3); の返り値 3 が eval 関数の返り値となる。

eval("+function add(a, b) { return a + b; }(2, 3);");
// => 5
// 単項 + 演算子の後に文が来ることはできない
// (+if (...) とは書けない) ので、+ に続く
// function は名前付き関数式の始まりとみなされる。
// 関数式の関数本体の後に続く括弧は
// 関数呼び出しの括弧とみなされ、
// 関数呼び出しの結果 5 に単項 + 演算子を
// 適用した結果 5 が eval 関数の返り値となる。

eval("function () {}");
// ECMAScript 3 => 構文エラー
// ECMAScript では関数宣言には名前が必要。
//
// SpiderMonkey 1.8 => function () {
//                  => }
// SpiderMonkey (Firefox などで使われている
// JavaScript エンジン。バージョン 1.8 は現在
// 開発途中) のバージョン 1.6 以降では無名の
// 関数宣言が可能。この場合、文としての
// 返り値は Function オブジェクトとなる。 
//
// JScript 5.6 => undefined
// JScript でもエラーにはならないが、
// 文としての返り値は empty みたい。

eval("function (a, b) { return a + b; }(2, 3);");
// ECMAScript 3 => 構文エラー
//
// SpiderMonkey 1.8 => 3
// JScript 5.6 => 3
// 後に何が続こうと、関数宣言と解釈されたものが
// 関数式として解釈されなおすことはない。

eval("+function (a, b) { return a + b; }(2, 3);");
// => 5
// 関数式は無名でもよい。

eval("(function f() {});");
// => function f() {
// => }
// 開き括弧の後に文が来ることはできないので
// 関数式とみなされる。関数式の返り値は
// Function オブジェクトなので、式文
// (function f() {}); および eval 関数の
// 返り値もその Function オブジェクトになる。
//
// JScript 5.6 => undefined
// 謎の挙動。

ECMAScript では関数宣言はトップレベルまたは関数の直下にしか置けない (if 文内などには置けない) が、JavaScript や JScript ではブロック下の関数宣言も構文エラーにならない。このあたりは Function Expression Statements も参考に。

Primary Expressions

「文の直後の function f() {}(x) の最後の括弧は primary expression の括弧」と発言したことから primary expressions (基本式) って何という話題に。JavaScript の文法定義中に出てくる用語で、this キーワード、識別子、プリミティブ値のリテラル、配列リテラル、オブジェクトリテラル、括弧でくくった式の集合。

for-in 文

91 ページ、オブジェクトのプロパティ名を配列に抜き出すコードがすごい。応用すれば JavaScript 1.7 以上では 1 回の反復でプロパティの名前と値を抜き出せそう。

var o = { a: 10, b: 20, c: 30 };
var keys = [], values = [];
for ([keys[keys.length], values[values.length]] in new Iterator(o))
  ;
keys; // => a,b,c
values; // => 10,20,30

break 文

continue 文につけるラベルはループ文に対するものでなくてはいけないが、break 文につけるラベルはどんな文に対するものでもいい

eval 関数内での break 文

eval 関数を使って break 文の行き先を動的に変えることはできない。

outer: {
  inner: {
    var destination = "outer";
    eval("break " + destination);
    // => SyntaxError: label not found:
  }
}

これは eval 関数に渡された文字列がひとつのプログラムとして解析されるから。プログラムのトップレベルにラベル付き break 文は置けない。

空文

私は空文に一行とる派。

while (...)
  ;

var 文はなぜ文か

var は式でもいいのではという amachang さんの提起。実際 Perl の my は関数。var は互換性の観点から仕方ないとして、JavaScript 1.7 で let 宣言が導入されたときは私も同じことを言おうと思ったが、if (let x = foo()) { ... } else { ... } としたときに x のスコープはどうなるのかと考えたら面倒くさくなってやめてしまった。

ドットは演算子か

プロパティアクセスに使われる . は演算子かという話。79 ページにも演算子として扱うと書いてあるし、演算子の優先順位や結合規則を統一的に扱う上でも演算子として扱ったほうが説明が簡単になると個人的には思う。それから演算子であることとシンタックスシュガーであることは両立しうる気が。

. と [] はどちらが速いか。

o.xo["x"] で速度に差はあるのかという話。SpiderMonkey の場合、[] 内が文字列リテラルなら (そもそも [] 内が文字列リテラルでない場合、ドットによる書き換えはできない) 両者とも同じ中間コードになるので、実行時の速度を測っても差は出ないはず。ドットのほうが観測できないレベルでコンパイルが速いかもといった感じか。

配列リテラル中の空要素

配列リテラル中に空要素があった場合、プロパティ自体が作られない。

var a = [undefined, undefined,];
a.length;
// => 2
// JScript 5.6 => 3
// JScript では最後のカンマが無視されない。
a.hasOwnProperty("0");
// => true
// JavaScript ではプロパティ名は
// すべて文字列として扱われる。
// JScript 5.6 => false
// JScript では明示的に undefined を
// 指定していてもプロパティが作られない。

var b = [, ,];
b.length;
// => 2
// JScript 5.6 => 3
b.hasOwnProperty("0");
// => false
// SpiderMonkey 1.8 => true
// SpiderMonkey では空要素に対しても
// undefined を値とするプロパティを
// 作ってしまう (Mozilla Bug 260106)。

constructor プロパティ

自作オブジェクトの constructor プロパティの値は何になるかという話。まず前提として、Function オブジェクト A が作られたとき、同時に新しくオブジェクトが作られ、A の prototype プロパティに設定される。そしてそのオブジェクトは、基の Function オブジェクト A を指す constructor プロパティを持つ。

function A() {}
A.hasOwnProperty("prototype"); // => true
A.prototype; // => [object Object]
A.prototype.hasOwnProperty("constructor"); // => true
A.prototype.constructor == A; // => true

A をコンストラクタとして作られたオブジェクト a 自身は constructor プロパティを持たない。しかし、a の [[Prototype]] 内部プロパティ (__proto__ プロパティ) が指すオブジェクト A.prototype は constructor プロパティを持つ。プロパティの探索はプロトタイプチェーンをたどって行われるので、a.constructor の値は A.prototype.constructor の値である A となる。

var a = new A();
a.hasOwnProperty("constructor"); // => false
a.__proto__ == A.prototype; // => true
a.constructor == A; // => true

ここで、新しくオブジェクトoを作る。オブジェクトリテラルによるオブジェクトの作成は、Function オブジェクト Object をコンストラクタとして呼び出したのと同じことなので、o の [[Prototype]] 内部プロパティの値は Object.prototype が指すオブジェクトになる。o 自身は constructor プロパティを持たないが、Object.prototype が指すオブジェクトは constructor プロパティを持ち、その値は Object である。よって、o.constructor の値は Object となる。

var o = {}; // == new Object()
o.__proto__ == Object.prototype; // => true
o.hasOwnProperty("constructor"); // => false
Object.prototype.constructor == Object; // => true
o.constructor == Object; // => true

今、o を A.prototype に設定した上で A をコンストラクタとして新しくオブジェクト b を作る。b も b の [[Prototype]] 内部プロパティの値である o も constructor プロパティを持たないので、b.constructor の値はさらにプロトタイプチェーンをさかのぼって Object.prototype の constructor プロパティの値となる。

A.prototype = o;
var b = new A();
b.hasOwnProperty("constructor"); // => false
b.__proto__ == o; // => true
b.constructor == Object; // => true

こういった事態を防ぐためには、A.prototype に constructor プロパティを設定すればよい。

A.prototype.constructor = A;
var c = new A();
c.hasOwnProperty("constructor"); // => false
c.constructor == A; // => true

懇親会

周りからプロトタイプがどうのスコープがこうの聞こえてくるのを尻目に、まったりと皿をつつくグループに属していました。終盤では amachang さんの隣にお邪魔してお話を拝聴。自分はブログに書く速度が遅いということを言いましたが、さすがに DOM 2 Events の JavaScript による実装における、最初のプロトタイプ作成から 1 年以上というのは例外的な部類に入ります。それでもブログに書こうと思ってから実際に記事として公開できるまで 1 週間くらいかかるのは普通といった感じですが (^^;

コメント

_ kanasan ― 2008年01月22日 02時22分

YARVはヤルブと読みます。

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※投稿には管理者が設定した質問に答える必要があります。

名前:
メールアドレス:
URL:
次の質問に答えてください:
「ハイパーテキストマークアップ言語」をアルファベット4文字でいうと?

コメント:

トラックバック

このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2008/01/21/2571626/tb