第2回関西アンカンファレンスで zsh の記号プログラミングについて発表した

2010年1月8日に神戸で第2回関西アンカンファレンスが開催された(告知ページ)。僕は zsh が大好きなので「zsh で記号プログラミング」というタイトルで発表した。

発表内容

内容は zshHello world をアルファベット、数字を使わずに記号だけで書くというもの。資料は slidesahre で公開してる

動画もとってもらった。
http://youtube.com/watch?v=BBLFVkTCq6Q
動画の撮影、編集をしてくれた @ さん、ありがとうございました。

ソースコード

ソースコードはこんな感じ。

:
__=$?
((___=!__))
((____=___+___))
((_____=____+___))
((______=_____+___))
((_______=______+___))
((________=____*_____))
((_________=________+___))
((__________=____*______))
((___________=_____*_____))
____________=$(% |& >& $___)
$____________[-$______]$____________[-$____$____]$____________[-$_______]$____________[-$___$_________] "\\$__$___$___$__\\$__$___$______$_______\\$__$___$_______$______\\$__$___$_______$______\\$__$___$_______$_________, \\$__$___$________$_________\\$__$___$_______$_________\\$__$___$________$____\\$__$___$_______$______\\$__$___$______$______!"

こいつを実行すると ...

% zsh hello.zsh
Hello, world!

無事 Hello world が出力された。

コメントを入れて読みやすくするとこんな感じ。

# 0から9までの数字を作る
:
__=$?                       # 0
((___=!__))                 # 1
((____=___+___))            # 2
((_____=____+___))          # 3
((______=_____+___))        # 4
((_______=______+___))      # 5
((________=____*_____))     # 6
((_________=________+___))  # 7
((__________=____*______))  # 8
((___________=_____*_____)) # 9


# 以下のエラーメッセージを変数に代入する
#   kigou.zsh:fg:1: no job control in this shell.
____________=$(% |& >& $___)
# 省略せずに書くと以下の通り
# ____________=$(% 2>&1 | cat >& 1)

# 末尾から 4, 22, 5, 17 文字目をつなげると echo になる
# $____________[-4]$____________[-22]$____________[-5]$____________[-17]

# "Hello, world!" の文字列は
# \0NNN 形式の8進数 ascii コードで表現する
# echo "\\0110\\0145\\0154\\0154\\0157, \\0167\\0157\\0162\\0154\\0144!"
# , スペース ! は記号なのでそのまま書く

# 数字を $__ など最初に作った変数に置き換える
$____________[-$______]$____________[-$____$____]$____________[-$_______]$____________[-$___$_________] "\\$__$___$___$__\\$__$___$______$_______\\$__$___$_______$______\\$__$___$_______$______\\$__$___$_______$_________, \\$__$___$________$_________\\$__$___$_______$_________\\$__$___$________$____\\$__$___$_______$______\\$__$___$______$______!"

zsh らしいスクリプトになったと思う。

感想

僕以外の発表一覧は 第2回関西アンカンファレンス セッション一覧 にまとめられてる。

おもしろい発表がいっぱいあったけど、特によかったのは以下。

非デザイナーがデザイナーっぽい思考をするためのツボ
デザインの話大好き
Googleで始めた風評調査
生々しい
Gmailはショートカットでサクサク使おう
快適そう
美味しそうな写真の撮り方
おいしそう

JavaScript とかのプログラミングの話ももちろんいいんだけど、デザインとか写真とか他の勉強会ではなかなかない話が聞けるところがアンカンファレンスの魅力だと思ってる。

僕も好き勝手に話できるので発表するのを楽しんでます。

最後に

今回の関西アンカンファレンスはいろんな人の協力のもと成り立ってる。

  • 会場を提供してくれた兵庫県立大学神戸キャンパスの皆様
  • 僕のマシントラブルの際に Mac を貸してくれた @msng さん
  • スタッフの皆様
  • 発表してくれた皆様
  • 参加者の皆様
  • 今回の発表のきっかけを作ってくれた 記号プログラミング Advent Calendar の皆様

楽しいイベントになりました。ありがとうございました。

zsh の戦闘力を計測する

vim 使いには戦闘力という概念があって、vimrc の行数で強さを比べる。

せっかくなので zsh でもまねしてみた。

function scouter() {
  sed -e '/^\s*$/d' -e '/^\s*#/d' ${ZDOTDIR:-$HOME}/.zshrc | wc -l
}

zshrc の行数を計れる。.zshrc に書いて実行してみよう。

% scouter
263

もっと強くなりたい。

zsh の zmv を使って簡単に複数ファイルを一括リネームする

連番のファイルがずらーっとあったとき、複数のファイル名を一気にスマートに変えたいことがある。一個ずつちまちまリネームなんてやってられない。そんなときは zsh の zmv を使うと便利なので紹介する。

zmv で何ができるか

例えばこんな感じで 1.txt から 6.txt までファイルがあったとする。

% ls
1.txt 3.txt 5.txt
2.txt 4.txt 6.txt

でも、ファイル名短すぎてわかりにくいなー、file-1.txt みたいに頭に file ってつけたいなー、って思ったとしよう。

そんなときのために zsh には zmv ってコマンドがあって、この手の一括リネームがスマートにできる。お手軽な使い方はこんな感じ。

あらかじめ ~/.zshrc にこう書いておいてから、

autoload -Uz zmv
alias zmv='noglob zmv -W'

一回のコマンドで複数ファイルをリネームできる。

% zmv *.txt file-*.txt

実行してみると ...

% ls
file-1.txt file-3.txt file-5.txt
file-2.txt file-4.txt file-6.txt

1.txt が file-1.txt, 2.txt が file-2.txt ... という風にリネームできた。便利。

使い方の解説

もうちょっと詳しく解説してみる。

オプションなしの使い方

さっきは alias でいろいろオプションつけてたんだけど、まずはオプションなしの場合で説明する。

% zmv '(*).txt' 'file-$1.txt'

これが一番基本的な使い方。左側(第一引数)のパターンにマッチしたファイルを右側(第二引数)のフォーマットで作った名前に mv できる。

カッコの指定と $1 ってのがポイントで、リネームするときに右側の $1 の部分は左側のカッコで囲んだ部分に置き換えられる。カッコの中にワイルドカードを入れておくと実際にマッチした部分が $1 として使われるので、元のファイル名の一部を使ってそれぞれの mv 先のファイル名が作れる。

例えば 005.txt ってファイルは 005 の部分が * にマッチするので $1 が 005 に置き換わって、結果として file-005.txt に mv される。

さらに $1 っていうぐらいなので、$2, $3 ... といくらでも続けれる。例えばこんなの。

% zmv 'hoge-(*).(*).(*).tar.gz' 'hoge-$1--$2--$3.tar.gz'

先に出てきたカッコから順に $1, $2, $3 と対応するので、この場合、hoge-1.2.3.tar.gz が hoge-1--2--3.tar.gz、hoge-2.10.8.tar.gz が hoge-2--10--8.tar.gz にリネームされる。

-w オプション

でもなんかカッコで囲むのめんどくさいな、って思った人もいるかもしれない。僕もそう思う。そんな人のために zmv には -w というオプションがある。

-w オプションをつけると左側のワイルドカード部分を勝手にカッコで囲んだものとして扱ってくれる。なので、さっきの tar.gz の例で言うと全部カッコを省略して書ける。

% zmv -w 'hoge-*.*.*.tar.gz' 'hoge-$1--$2--$3.tar.gz'
-W オプション

こうなってくると人間横着になるもので、右側の $1 ってのもめんどくさくなってくる。せっかく zmv を使うんだから極限まで楽をしたい。zmv を作った人もそう考えたのか、 -W というオプションを作ってくれた(大文字の W ね)。

-W オプションを使うと、 -w (小文字)に加えて右側の * も順番に $1, $2 ... と置き換えてくれる。なので最終的にはこれで OK。

% zmv -W 'hoge-*.*.*.tar.gz' 'hoge-*--*--*.tar.gz'

シンプル。

でも zmv '(*)-(*).txt' '$2-$1.txt' みたいに順番を入れ替えたい場合には使えない。そういうときは -W オプションなしでやろう。

noglob

さっきから引数を '...' で囲んでるけど、これは * をファイル名展開せずに zmv に渡すため。'...' で囲まなかった場合は zsh 自体が zmv に引数を渡す前に * を展開してしまうのでうまく動かない。zmv を使うときはこのファイル名展開は必要ないので無効にしておくといい。

% noglob zmv -W hoge-*.*.*.tar.gz hoge-*--*--*.tar.gz

こんな風に先頭に noglob を付けると zsh が * とかのワイルドカードを展開しなくなるので '...' で囲まなくてもよくなる。

便利な alias

最後の noglob を付けたパターンが典型的な使い方になるので、こんな風に ~/.zshrc に書いておくと便利。

autoload -Uz zmv
alias zmv='noglob zmv -W'

これで Windows のコマンドプロンプトみたいなのができるようになる。

% zmv *.txt *.c

進んだ使い方

ここまでが基本編。ここからもっと凝った使い方を紹介するので何かの時に使ってみよう。

ここから先はさっき紹介した alias とかの設定は無いものとして説明する。

確認する

-n オプションを使うと、実際にリネームせずに行おうとしてるコマンドの内容を出力してくれる。

% zmv -n -W '*.txt' '*.c'
mv -- file1.txt file1.c
mv -- file2.txt file2.c

難しいことやる前には一回これで確認しておくと安全。

-C, -L オプション

-C オプションを付けると mv の代わりに cp を実行するようになる。

# *.c ファイルを *.c.bak にコピーしてバックアップをとる
% zmv -C '(*.c)' '$1.bak'

-L オプションを付けると mv の代わりに ln を実行するようになる。

例えば、~/dotfiles ディレクトリの下に dot.zshrc, dot.vimrc とか dot.* という形式で設定ファイルを置いていたとしよう。ホームディレクトリに .zshrc とか本来の名前でリンクを作る場合はこう。

% cd  # ホームへ移動
% zmv -L -s 'dotfiles/dot.(*)' '.$1'

頭の dot を外してリンクが張れる。

% ls -al
.vimrc -> dotfiles/dot.vimrc
.zshrc -> dotfiles/dot.zshrc

-L を指定した場合はデフォルトでハードリンクが張られるんだけど、 -s を付けるとシンボリックリンクになる。普通は -s を付けておいた方がいいだろう。

あと、-M オプションを付けるとデフォルト動作に戻って mv コマンドを実行するようになる。

変数展開フラグ

右側のリネーム先ファイル名の部分で '${(U)1}.txt' みたいな感じで変数展開フラグが使える。といってもよく意味が分からないかもしれないので例を見てもらおう。

# *.txt の * 部分を大文字にしたい
% ls
readme.txt  todo.txt

# U フラグを使うと ...
% zmv '(*).txt' '${(U)1}.txt'

# カッコで囲んだところが大文字になった
% ls
README.txt  TODO.txt

# 小文字にしたいときは L フラグを使う
% zmv '(*).txt' '${(L)1}.txt'

# 戻った
% ls
readme.txt  todo.txt

変数展開フラグはいっぱいあるんだけど、便利なものは以下。

U
大文字にする(Upper)
L
小文字にする(Lower)
C
キャピタルケース。つまり readme を Readme にする

その他もっとマニアックなことがやりたい人は man zshexpn の Parameter Expansion Flags を参照。

変数名の置換

右側のリネーム先ファイル名の部分で、元の名前を置換できる。これも例を見た方がわかりやすい。

# なんかファイル名にスペースが入ってる。なんとかしたい
% ls -1
hoge 1 x.txt
hoge 2 y.txt

# ${1//pattern/repl} という形式で ...
% zmv '(*)' '${1// /_}'

# スペースが _ に置き換わった
% ls
hoge_1_x.txt  hoge_2_y.txt

ちなみに、スペースがいっぱいあるときは ## が便利。

# 誰だよ、こんなファイル作ったのは ...
% ls -1
hoge    1   x.txt
hoge   2     y.txt

# 直してやるか
% zmv '(*)' '${1// ##/_}'

% ls -1
hoge_1_x.txt
hoge_2_y.txt
# よしよし

## は1回以上の繰り返しを表すので、この場合は連続したスペースが1つの _ になる。

ディレクトリを降りて再帰的に変換する

zsher にはおなじみの **/* も使える。例えば今いるディレクトリ以下にある makefile を全部 Makefile にするのはこんな感じ。

% zmv '(**/)makefile' '$1Makefile'

今いるディレクトリ直下だけでなく、src/project1/makefile とか奥深くにあるファイルもリネームできる。

参考資料

その他参考になる資料一覧を挙げておく。さらに進んだ使い方をするときに読むといいだろう。

zmv の man

何はともあれ zsh の man を見るのがいい。zmv は man zshcontrib に書いてある(だいぶ下の方)。

zsh の man は Web でも読める。
man zshcontrib の OTHER FUNCTIONS

zsh-lovers

zsh-lovers をインストールすると man zsh-lovers で zsh の使い方、Tips とかが読めるようになる。その中に ZMV-Examples ってのがあって、zmv の例がいっぱい書いてある。

こいつも Web で読める。
ZSH-LOVERS(1) - ZMV-Examples

ソースコード

あと、zmv のソースコードの最初の方にコメントで詳しい説明が書いてあるので参考になる。僕の環境(Ubuntu 10.10)では /usr/share/zsh/functions/Misc/zmv にインストールされてた。

ソースは sourceforge にもある。
SourceForge - zsh/zsh/blob - Functions/Misc/zmv

その他の man

変数展開、グロブ展開なんかの zsh 自体の機能が分かってくると色々凝ったリネームができるようになる。参考になる man は以下。

man zshexpn の FILENAME GENERATION
*, x## とかファイル名生成全般についての説明
man zshexpn の Globbing Flags
(*).(#i)jpg の #i とかファイル名生成のフラグについての説明 (#i は大文字小文字を無視するフラグ)
man zshexpn の Parameter Expansion
${1// /_} など $1, $2 ... を置換する方法
man zshexpn の Parameter Expansion Flags
${(U)1} (大文字にする) など

色々やり過ぎると「結局手で一個ずつ直した方が早かった」なんてことになるかもしれないけど、便利なときもあるのでうまく使おう。

はてなブックマークのエントリーページで個別のブックマークを非表示にするテスト

はてなブックマークのブックマーク一覧ページで、特定のブックマークを非表示にできるようになった。
エントリーページ上の個別のブックマークを非表示にする機能を追加しました - はてなブックマーク日記 - 機能変更、お知らせなど

はてなダイアリーを書いてる人は、自分の日記につけられたブックマークを個別に指定して非表示にできる。

実験

というわけで、早速このエントリをはてなブックマークに追加して非表示にしてみることにした。
この「表示制御画面へ」というリンク先に移動して、

非表示にしたいブックマークにチェックを入れてボタンを押せば OK。

反映されるまでにちょっと時間がかかるけど(5分ぐらい?)、ちゃんとこのエントリのブックマーク一覧ページには出てこなくなった。ただし、ブックマークした本人(この場合は僕)と、ブックマークした人をお気に入りに追加してる人には見えるみたい。

あと、メタブでも同じように非表示にできる。自分でメタブしてメタブの一覧ページで非表示にしてみた。

感想

この機能があるってことは、せっかくブックマークが共有できるようになってるのにそれが見えなくなることがあるわけで、僕としてはあんまりうれしくない。

はてなダイアリーとかのブログ、サイト作成者がいい感じの基準でこの機能を使ってくれることを期待してます。

oh-my-zsh を使って zsh の便利な設定をまとめて取り入れる

最近 oh-my-zsh ってのがあるって聞いた。インストールするだけで zsh が便利になるみたいなので、早速試してみた。

oh-my-zsh って何?

oh-my-zshzsh の設定を管理するフレームワークで、いろんな設定を有効にして管理、更新できるようになる。プロジェクトは GitHub で管理されてる。

http://github.com/robbyrussell/oh-my-zsh

特徴としてこんなことが挙げられてる。

  • 40以上のプロンプト テーマ
  • 便利なプラグイン
  • 自動アップデート機能
  • コミュニティ主体の開発(みんなの設定を取り込んでいく)

インストールすると、だいたいこんなことが設定される。

  • プロンプトがかっこよく(?)なる
  • alias がいっぱい設定される
  • よく使う option が設定される

インストール直後はこんな感じ。

Git のブランチ名も表示される。

必要な環境なんだけど、zsh のバージョンは 4.3.9 以降を推奨してる。Mac の Snow Leopard の人はデフォルトの zsh で OK。あと、全体的に Git 使ってること前提っぽい。

インストール

インストール用に自動スクリプトがある。

% wget http://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

こいつを実行すれば oh-my-zsy がローカル環境にインストールされる。でも、勝手に chsh でログインシェルを変えたりして色々やりすぎなので、あんまりおすすめしない。

手動でインストールする方法は以下。

% git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
# 自分の ~/.zshrc がすでにある場合はリネームしておく
% mv ~/.zshrc ~/.zshrc.orig
% cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

どっちの方法も Git が必要になる。Git 使えない人は GitHub のプロジェクトページから圧縮ファイルをダウンロードしてインストールしよう。

# ダウンロードファイルを展開して ~/.oh-my-zsh にコピーする
% unzip robbyrussell-oh-my-zsh-541da0c.zip
% mv robbyrussell-oh-my-zsh-541da0c ~/.oh-my-zsh
% cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

インストールした後適当に zsh を再起動すると oh-my-zsh が有効になる。

あと、インストール先の ~/.oh-my-zsh というディレクトリ名は固定で、変えたら動かない

プロンプトの設定

oh-my-zsh にプロンプトのテーマがいっぱい入ってる。~/.zshrc で ZSH_THEME を設定するとテーマを変更できる。

# ~/.zshrc 6行目
# デフォルトは robbyrussell
export ZSH_THEME="robbyrussell"

~/.oh-my-zsh/themes の中を見て、好きなものに変更しよう。

export ZSH_THEME="cypher"

zsh を再起動すると新しいテーマになる。

プラグイン

~/.oh-my-zsh/plugins/ 以下に独自のプラグインが入ってる。

% ls ~/.oh-my-zsh/plugins/
brew/  dirpersist/  git/         macports/  osx/  rails/  ssh-agent/  vi-mode/
cap/   gem/         lighthouse/  mysql/     pip/  ruby/   textmate/

このうち使用するものを ~/.zshrc で指定する。デフォルトはこんな感じ。

# ~/.zshrc 19行目
plugins=(git)

使用したいものを指定しよう。

plugins=(git ruby gem)

プラグインと言ってもそんな凝ったことしてるわけじゃなくて、 だいたい alias、便利な関数、 補完ファイルとかが読み込まれるだけ。

カスタム設定

自分の独自の設定は ~/.oh-my-zsh/custom に適当な名前(最後は .zsh)でファイルを作ってそこに書く。

# 例
% cat ~/.oh-my-zsh/custom/custom-aliases.zsh
alias v='vim'
alias vr='vim -R'
alias c='cd'

好きなように書いておこう。

自動アップデート

1週間に1回のタイミングで、zsh 起動時に自動的アップデートされる。アップデートといっても単純に GitHub から pull してくるだけ。なので Git 使わずにインストールした人はこの機能が使えない。

勝手にアップデートされるのがいやなら ~/.zhsrc で無効にできる。

# ~/.zshrc 12行目をアンコメント
export DISABLE_AUTO_UPDATE="true"

使ってみた感じ

使ってみた感想なんだけど、なんか全体的に作った人の「個人的な便利設定の集まり」みたいになってる。一般におすすめできるかというと、ちょっと疑問が残る。

作者の theunraveler さんの環境はたぶんこんな感じ。

典型的な(?) Rubyist だと思うので、そういう人意外にはありがたみがないかもしれない。

例えば textmate プラグインにこんな設定が入ってる。

alias ett='mate app config lib db public spec test Rakefile Capfile Todo &'

Textmate(Mac 用のエディタ)の設定のはずが、明らかに Rails 使ってること前提になってる。なので Rails やってない人はこのへん意味ない。(だからプラグインの形になってるわけね)

というか、そもそも alias って手癖みたいなもんで、めちゃ人に依存するとこだと思う。なので誰かの設定をそのまま持ってきても便利になるとは限らない。すでに zsh カスタマイズしまくりの人が oh-my-zsh 使ってもかえって混乱するだけだと思う。

なので、oh-my-zsh をおすすめするのは以下の人。

  • これから zsh を初めて使う人
  • 設定とかカスタマイズとか自分でやりたくない人
  • Mac で RubyRails な人

でもインストールするだけで zsh の補完とかがすぐ使えるようになるのは良いと思う。というわけで、「zsh ってよくわかんないし、設定難しいし、簡単にパワフルな機能使ってウヒョーってなりたい」ってひとは使ってみるといいと思うよ。

Kanasan.JS jQuery コードリーディング #2 に参加したので復習とコードの解説をする

2010/09/26(日) に京都で Kanasan.JS jQuery コードリーディング #2 が行われた(告知ページ)。みんなで jQueryソースコードを読む勉強会で、僕は初めての参加。事前に前回分も読んでおいたので準備万端、気合いを入れて参加した。

で、今回の内容なんだけど、642行目から1205行目まで読み終えた。全部で6240行なので全体の5分の1ぐらい終わったかな。今回は普段外部から使うものじゃなくて、jQuery 内部で使う関数のところが多かった。あとは jQuery マニアしか使わないようなメソッドとか。そんなわけで使い方がイメージしにくいところもあったので、その中でわかりにくかったとこを復習して挙げておく。

あと、今回読んだ jQuery のバージョンは 1.4.2。

前提

こんな感じでメソッドを追加してるところがある。

jQuery.extend({
  queue: // ... queue の実装
});
jQuery.fn.extend({
  queue: // ... queue の実装
});

1つ目の jQuery.extend の方は jQuery のグローバルな関数というか jQuery のクラスメソッドを定義してる。そいつは $.queue() みたいな感じで呼び出せる。

2つ目の jQuery.fn.extend の方は jQuery オブジェクトのインスタンスメソッドを定義してて、$("div").queue() みたいな感じで呼び出す。インスタンスメソッドの方はたいてい一つ目の方法で定義した関数を使って実装されてる。なので、jQuery.extend の方の実装を理解するのが大事。

こうやってメソッドを追加してるとこは今回に限らずいろんなとこで出てくるので、基本ルールとして押さえておく。

DOM に関係ない関数

361行目から719行目までで jQuery.extend({ ... }); 形式で jQuery のクラスメソッドを定義してる。気になったものは以下。

map

よくある map 関数なんだけど、最後がちょっとわかりにくい。

return ret.concat.apply([], ret);

これは何をしているかというと、jQuery の map は map 用の callback が配列を返したとき、その配列を展開した各要素が結果に追加される、という仕様になってる。で、 ret は map した結果がどんどん追加される配列なんだけど、最後の return するところで配列を一段階取り除いてる。

// concat で配列をなだらかにする例
[].concat(1, [2, 3], [4, 5]); //=> [1, 2, 3, 4, 5]

つまり Ruby でいうところの flatten。

proxy

そもそも $.proxy って何ですかってことで調べてみたら、わかりやすい解説があった。
jQuery1.4から追加されたjQuery.proxy()を試してみる | THE HAM MEDIA BLOG
$.proxy(fn, thisObject) みたいな感じで呼び出すと、「その中の this が thisObject になった新しい fn 関数」を返す。

proxy 関数の実装は、余計なところを取り除くとこうなる。

proxy: function(fn, thisObject) {
  return function() {
    return fn.apply(thisObject, arguments);
  };
}

つまり、proxy を使うと this をすり替えて実行してくれる新しい関数がもらえるので、bind する時にわざわざ function { thisObject.fn(); } とかしなくてもよくて便利だよ、ってことらしい。うん、まあ半年に一回くらいしか使わないだろう。

IE の DOMContentLoaded

759行目、IE で DOMContentLoaded イベントが発生しないので、その代わりのテクニックを使ってる。

function doScrollCheck() {
  try {
    // If IE is used, use the trick by Diego Perini
    // http://javascript.nwbox.com/IEContentLoaded/
    document.documentElement.doScroll("left");
  } catch( error ) {
    setTimeout( doScrollCheck, 1 );
    return;
  }

  // and execute any waiting functions
  jQuery.ready();
}

IE の「DOM ツリーができあがる前に doScroll を呼び出すと例外が発生する」という仕様なのかバグなのかよくわかんない動作を利用して、doScroll しまくって例外がでなくなったら DOM ツリー完成、という風にしてる。

このテクニックはその筋の人には有名みたいで、コメントにもある通り IEContentLoaded - An alternative for DOMContenloaded on Internet Explorer に詳細が書いてある。

この場合 doScroll でほんとにスクロールしてしまったら困るので "left" を指定して画面が移動しないようにしてる。だれかが「"up" でもいいんじゃないの?」って言ってたような気がするけど、"#main" みたいなページ内アンカーに飛んできた場合は最初の位置がページの一番上じゃないこともあるわけで、やっぱり "left" がいいと思う。

jQuery.support

みんな大好きバッドノウハウのコーナーということで、829行目からはブラウザが標準から外れた動作(というかバグ)をするかどうかをチェックしてる。

特に目を引いたのがこの1行。

div.innerHTML =
  "   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";

この HTML 1行で10種類のバグがあるかどうかチェックしてる。

  • leadingWhitespace
  • tbody
  • htmlSerialize
  • style
  • hrefNormalized
  • opacity
  • cssFloat
  • checkOn
  • optSelected
  • parentNode

「こんなにがんばってチェックしてるんだし、IE もバージョンがあがればよくなるはずだし、if (jQuery.browser.msie) { /* fu*k */ } とか書くのやめようぜ」ってことらしい。もう職人芸だね。

jQuery.support.hrefNormalized

jQuery.support には hrefNormalized ってのがあって、こんな感じの実装になってる。

hrefNormalized: a.getAttribute("href") === "/a",

getAttribute("href") したら普通は href 属性に書いてある値がそのままとれるんだけど、IE の場合は 'http://' から始まる絶対 URL 形式の値が返ってくる。

で、この hrefNormalized って名前逆じゃね? って発言があった。僕もそう思う。

これに限らず、jQuery のコードにはよくない名前がけっこうある(なので安易にまねしない方がいい)。

jQuery.support.deleteExpando

続きを読んでると jQuery.support.deleteExpando ってのが出てきた。「というか expando って何?」って話になったけど、オブジェクトに動的に追加したプロパティのことをそう呼ぶらしい(知らなかった)。Wiktionary にも expando のページがある。

この deleteExpando チェックの内容なんだけど、まず JavaScript の仕様としてはオブジェクトの存在しないプロパティを delete しても特に問題なくて、例外が発生したりしない。

var a = document.createElement("a");
delete a.hoge;  // true が返る

でもこの delete で例外が発生するよろしくないブラウザ(つまり IE)があって、そのときには jQuery.support.deleteExpando が false になるようだ。

jQuery.data 関数

1001行目から jQuery.data の実装が始まる。普通の jQuery プログラミングではあんまり使用しない関数なんだけど、jQuery の内部では結構使われているので大事だ。データ構造を押さえておくことにする。

jQuery.data(element, key, value) の基本的な機能は、emelent(DOM 要素)ごとに名前と値の組としてデータを格納する、というもの。

データを格納する時は DOM 要素ごとに一意的な ID を割り振って、その ID をキーとして jQuery.cache というグローバルなオブジェクトに値を格納する。

jQuery.cache のイメージはこんな感じ。

jQuery.cache = {
  id1 : {
    // element1 用のデータ
    key1_1 : value1
    key1_2 : value2
  },
  id2 : {
    // element2 用のデータ
    key2_1 : value3
  }
}

対象となった DOM 要素側でも ID を覚えてるので、あとからそれをキーとしてデータを取ってこれる。

jQuery.data の実装としては中でいろいろやってるんだけど、基本的なとこだけを残すとこうなる。

// グローバルな変数の初期化
var uuid = 0;
JQuery.expando = "jQuery" + (new Date).getTime();
JQuery.cache = {};

// jQuery.data の実装
jQuery.data = function(element, key, value) {
  // 要素ごとに一意的な ID を割り振る
  var id = ++uuid;
  element[jQuery.expando] = id;

  if (!jQuery.cache[id]) {
    jQuery.cache[id] = {};
  }
  jQuery.cache[id][key] = value;

  return value;
}

DOM 要素側で ID を覚えておく際に、現在時刻を取ってプロパティ名としてかぶらない名前を使うようになってる。

jQuery.queue 関数

早速1117行目からの jQuery.queue 関数で jQuery.data を使ってる。

jQuery.queue(element, queueName, value) は、指定された DOM 要素に queueName という名前のキューを作ってそこに値を追加する関数。

こいつも結局 jQuery.data を使って、queueName という名前で配列データを作ってキューとして使用してる。

// jQuery.queue のイメージ
jQuery.queue = function(element, queueName, value) {
  var queueName = (queueName || "fx") + "queue";
  // 現在のキュー(配列)を取得する
  var q = jQuery.data(element, queueName);
  q.push(value);

  return q;
}

キューにためるデータとしては何でもいいんだけど、関数というかイベントハンドラを登録することを想定しているようだ。「上に移動した後、色を変える」とか順番に行いたい処理をキューにためるといいんだろう。

最後に

読む上での注意なんだけど、tkyk さんもブログで指摘してたように、ソースを読む前にリファレンスマニュアルを読むのが大事。処理の概要がわかるし、引数を省略した場合の扱いとかも書いてある。つまり、リファレンスマニュアルは API の利用者だけでなく、実装を読んだり改造したりする人にとっても重要ってことだね。

なんか今回でコツがつかめたような気がするので、次回も楽しみにしてます。

jQuery ソースコードを読むための参考資料一覧

2010/09/26(日) に京都で Kanasan.JS jQuery コードリーディング #2 が行われる(告知ページ)。みんなで jQueryソースコードを読むイベントで、今回が2回目だ。参加者募集中です。

jQuery はファイル1つだけのライブラリなのですごい環境とかもってなくても大丈夫なんだけど、読むうえで参考になるサイトとかをまとめてみた。この辺の準備をしておくと理解が深まると思う。

ソースコード

http://code.jquery.com/jquery-1.4.2.js
http://github.com/jquery/jquery

まずは jQueryソースコードを取ってこないと始まらない。今回はバージョン 1.4.2 を使用する。jQuery の公式サイトから圧縮していないコードをダウンロードしよう。

それと、jQuery ソースコードは Git で管理されてる。Git が使える人はソースツリーを取ってくるといろいろできて便利。

% git clone git://github.com/jquery/jquery.git
# バージョン 1.4.2 に移動
% git checkout 1.4.2

src の下に分割されたソースコードがいっぱいはいってて、git log とか git blame で見れる。結合したいときは make, rake, ant のどれかで dist/jquery.js ってとこにソースコードができあがる。

あと、 test ディレクトリの下に test/unit/core.js とかテストコードがあって参考になる。

Git 使えない人でも GitHub にリポジトリがあるのでブラウザからログとか見れるよ。

リファレンスマニュアル

コードを読むときにリファレンスマニュアルもあわせて参照すると便利。その関数が何をするものなのか、とか呼び出し方の例とかが書いてあって参考になる。

公式のリファレンスマニュアル。何はともあれ、このページが一番詳しいし信用できる。

日本語リファレンスもある。これは 1.4.2 に対応してるのでいい感じ。

これは別の日本語リファレンス。うさぎがヌーヌー言ってるやつ。バージョン 1.3.2 対応のもようでちょっと古い。

チケット管理

http://dev.jquery.com/

jQueryソースコードを読んでると、こんな感じで #xxxx みたいな数字がコメントに書いてあることがある。

// 例えば 452 行目
// aren't supported. They return false on IE (#2968).

これはバグ管理システムのチケット ID で、「この部分はバグとして登録されてるよ」ってことを表してる。

登録されたチケットの詳細は jQuery の Bug Tracker ページから参照できる。チケット ID が分かってる場合は http://dev.jquery.com/ticket/2968 みたいな感じで「/ticket/チケットID」形式の URL を開くとそこが詳細ページになってる。あと、「Search Tickets」の欄に「#2968」とかチケット ID を入力すると詳細ページに移動できるので、そっちでも OK。

チケット詳細ページを見ると「どんな問題があったのか」、「どうやって修正するか」とかの議論が見れる。「IE の場合は XXX でムキー!」とか書いてあるので参考になる。

コーディング規約

http://docs.jquery.com/JQuery_Core_Style_Guidelines

jQuery にもコーディング規約がある。まあインデントスタイルとかは読む分には関係ないんだけど、大事そうなところを挙げておく。

'==', '!=' ではなくて '===', '!==' を使用する。

'==', '!=' はいろいろキモい動きをするので、代わりに '===', '!==' を使う。でも == null っていうのは === null または === undefined っていう意味で便利なので、この場合は '==' とか '!=' を使う。

for/in の hasOwnProperty チェックはしない

Object を for/in でぐるぐるループさせて連想配列として使うことがある。

// 素朴な書き方
for (name in obj) {
  // 何か処理
}

でも、もし Object.prototype を拡張してたらそいつまで for/in に出てきてしまってうまく動かなくなる。それを防ぐために hasOwnProperty でチェックするっていうテクニックがよく使われる。

// 行儀のいい書き方
for (name in obj) {
  if (obj.hasOwnProperty(name) {
    // 何か処理
  }
}

jQuery のコードでは一つ目の素朴な書き方で書いてる。Object.prototype を拡張してしまったらちゃんと動かなくなるけど、そんな邪悪な環境はサポートしないそうだ。

型チェック

型をチェックする方法も書いてある。
チェック方法は以下。

  • String, Number, Boolean, Object

typeof object === "string" など

  • Plain Object, Function, Array

それぞれ jQuery.isPlainObject(object), jQuery.isFunction(object), jQuery.isArray(object)

  • Element

object.nodeType

  • null

object === null

  • null または undefined

object == null

  • undefined
    • グローバル変数の場合 : typeof variable === "undefined"
    • ローカル変数の場合 variable === undefined
    • オブジェクトのプロパティの場合 : object.prop === undefined

Plain Object ってのは、何か継承したのじゃない単なるオブジェクトのこと。要するに {} 形式で書いたやつ。

正規表現

正規表現を使う場合、.test() と .exec() を使う。string.match() は使わない。理由は書いてないので分からない。単にどっちかに統一したかっただけなのかもしれない。

その他の参考記事

jQuery ソースコードを読む連載記事。バージョンは 1.2.2 で古いけど、詳しい解説があって参考になる。

ソースコードを読むための基礎知識。

jQueryソースコード ビューアー。メソッド名を入れると対応するコードが表示される。メソッドがどこで定義されてるのかわかんないときに見る。

この辺を押さえておくとだいぶコードが読みやすくなると思うので、是非コードリーディングに挑戦してみてください。