Filed Under (article, document) by on 29-10-2008
こんにちは、ひろきのだいちです。
JavaScriptの再発見からさらに2年がたち、モダンなWeb開発では欠かせない存在となったJavaScript。さまざまなライブラリが乱立し、言語としての基礎を得ないままでもある程度のアプリケーション開発ができるようになった現在。
再入門JavaScriptと題して、中上級者を目指すために入門書を超えてじっくりとJavaScriptを学びなおすことが重要だと考え、ここを中心にまとめていきたいと思います。
ここで掲載するトピックは本当の基礎の基礎は他の媒体にゆだねた上で、「仕事でつかえる」レベルのコードを記述するために重要で比較的高度なものを取り扱うつもりです。
また、ここでいう仕事で使えるとは”多人数で開発を進める”ことを前提とした開発環境を想定しており、他サイトのAPIの使い方やライブラリの使用方法といった枝葉のリファレンスではありません。
また、ここに書かれていることはたいてい僕のメモなので、責任はもちませんw
再入門JavaScript -フロントエンドエンジニア資料集
再入門JavaScript -JavaScriptでの中大規模開発
- 再入門JavaScript - コード成長過程
- 再入門JavaScript - MarkupエンジニアとScriptエンジニア、サーバサイドエンジニアとの協調
- 再入門JavaScript - グレースフルデグラデーション
- 再入門JavaScript - MVCモデル
- 再入門JavaScript - クライアントサイドMVC
- 再入門JavaScript - デザインパターン
再入門JavaScript -オブジェクト指向と関数オブジェクト
- 再入門JavaScript - オブジェクトとプロトタイプチェーン
- 再入門JavaScript - 継承・ダックタイピング
- 再入門JavaScript - 引数アサーションと動的ヴァリデーション
- 再入門JavaScript - 動的バインディング
- 再入門JavaScript - ビルドインオブジェクトのプロトタイプ拡張
再入門JavaScript -DOM
- 再入門JavaScript - イベントとバブリング
- 再入門JavaScript - XHTML拡張
- 再入門JavaScript - CSSの扱い
再入門JavaScript -DOM上のDSL
- 再入門JavaScript - CSS Selector
- 再入門JavaScript - XPath
- 再入門JavaScript - E4X
再入門JavaScript -テストとドキュメンテーション
- 再入門JavaScript - UnitTest
- 再入門JavaScript - Seleniumによる結合テスト
- 再入門JavaScript - JsDocによるドキュメンテーション
再入門JavaScript -パフォーマンス/高速化
- 再入門JavaScript - プロファイリング
- 再入門JavaScript - DOM描画フェーズ/タイマー
- 再入門JavaScript - 事前コンパイルを利用した高速化
- 再入門JavaScript - VMレベルでの動作を意識する
- 再入門JavaScript - メモリリークとガーベジコレクタ
- 再入門JavaScript - defer属性/script後置とminifier
- 再入門JavaScript - 先読みと
- 再入門JavaScript - Google Gears
再入門JavaScript -通信
- 再入門JavaScript - クロスドメイン制約
- 再入門JavaScript - 同一ドメイン通信
- 再入門JavaScript - location.hashによるヒストリ管理
- 再入門JavaScript - 通信の効率化とサーバサイド連携
- 再入門JavaScript - クロスドメイン通信方法と注意点
Filed Under (Uncategorized) by on 29-10-2008
おこんばんわ。ひろきのだいちです。
今日は、再入門JavaScriptと題して、Perlなどのサーバサイドの言語になれている方に向けて、
JavaScriptにおける大規模開発の勘所として重要な名前空間の実現方法の解説と、
それらを簡単に提供するライブラリJS.Namespaceを作成しましたので、そのドキュメントを織り交ぜてご紹介します。
JavaScriptを仕事で利用される方や、ライブラリのコピペのような書き方から一歩進みたい方に向けて記述しています。
Read the rest of this entry »
Filed Under (Uncategorized) by on 29-10-2008
動的ロード
サーバサイドにおいては、フレームワークなどの汎用的に使われるライブラリやプラグイン機構などでしか目にすることが
あまりないモジュールの動的ロードですが、クライアントサイドのようにユーザインタフェースと直結したプログラミング環境においては
動的ローディングは重要な技術です。
Perlにおいてコンパイルフェーズ以外でモジュールのロードを行いたい場合、UNIVERSAL requireを用いることが多いです。
PERL:
-
-
sub get_class{
-
-
$package->require;
-
}
JavaScriptにおいて動的ローディングを行うためには2つの方法があります。
ひとつはDOM操作によってscriptタグを埋め込む方法で
もうひとつはXML.HTTPRequestによって取得したTextをEvalする方法です。
DOM操作による動的ロード
JAVASCRIPT:
-
$$('head')[0].appendChild(new Element('script',{src:'/static/js/dynamic/load.js'}));
XHRによる動的ロード
JAVASCRIPT:
-
new Ajax.Request('/static/js/dynamic/load.js',{
-
evalScript:true,
-
onSuccess:function(e){
-
//
-
},
-
onException:function(e){
-
//
-
}
-
});
ライブラリによる動的ロード
動的ロードの縛りとして、
* 確実に終了と例外を補足できるXHRのみでのダイナミックロードに限定する
* urlでなく完全限定名を引数としてそれをファイルパスに自動変換した場所のファイルを取得する
* 内部でそのモジュールが作成されている
をもうけることで安全な動的ロードを提供する。
JAVASCRIPT:
-
JS.Namespace.dynamic('XXX.YYY.ZZZ',function(aaa){
-
aaa();
-
});
Filed Under (Uncategorized) by on 29-10-2008
名前空間上の関数を利用する
Perlの場合、Exporterなどの仕組みを使えば
PERL:
-
use XXX::
YYY::
ZZZ qw(aaa bbb
);
-
-
aaa();
のように記述するだけで、Current Packageにそれぞれの関数を貼り付けることができます。
あるいは
PERL:
-
use XXX::YYY::ZZZ;
-
-
&XXX::YYY::ZZZ::aaa();
のように、完全限定名(Full Qualified Name)でアクセスすることでもその名前空間上のメソッドを利用することができます。
jsの場合では完全限定名を利用すれば同じように利用することができます。
一方、Exporterのように現在のスコープに貼り付けるにはwithブロックを使います。
JAVASCRIPT:
-
(function(){with(XXX.YYY.ZZZ){
-
aaa();
-
}})();
しかし、withの動作をしっかり把握できない場合にwithを多用することはjavascriptのプログラム作法からは
推奨されません。
そのかわりに一時的にその名前空間をテンポラリな変数に別名をつけて利用することもできます。
JAVASCRIPT:
-
(function(){
-
var ns = XXX.YYY.ZZZ;
-
ns.aaa();
-
})();
-
-
(function(){
-
this.aaa();
-
}).apply(XXX.YYY.ZZZ);
どちらも、問題なく動作します。
JS.Namespaceライブラリではこれらの利便性を考えて
JAVASCRIPT:
-
JS.Namespace.using('XXX.YYY.ZZZ',function(){
-
this.aaa();
-
});
-
-
var ns = JS.Namespace.using('XXX.YYY.ZZZ');
-
ns.aaa();
などの記述を可能にします。
またネームスペース自体に追記がなく、use XXX qw(hoge)のようにただ関数やオブジェクトを利用するだけの場合
JAVASCRIPT:
-
var ns = JS.Namespace.using('XXX.YYY.ZZZ',$w('aaa bbb'));
-
ns.aaa();
-
ns.test = "test"; // XXX.YYY.ZZZは汚染されない
-
-
//クロージャの引数として宣言したオブジェクトを引き渡す
-
JS.Namespace.using('XXX.YYY.ZZZ',function(aaa,bbb){
-
aaa();
-
});
このように記述することができます。
jsには予約語としてnamespaceが定義されているけどブラウザじゃ使えませんし、Rhinoなどの一部の処理系を除いて、意味を成さないんですが、var namespace = hogehoge;みたいな記述は避けてください。
Filed Under (Uncategorized) by on 29-10-2008
ネームスペース
ネームスペースとはwikipedia先生いわくこんな感じです。
名前の集合を分割することで衝突の可能性を低減しつつ参照を容易にする概念である。
Perlであれば
とすれば、aaa,bbbはシンボリックテーブルXXX::YYY::ZZZ::において
XXX::YYY::ZZZ::aaa,XXX::YYY::ZZZ::bbbとして記録されグローバルに参照することができます。
なので仮に
というモジュールをロードしても、名前は衝突することがありません。
JavaScriptの場合、シンボリックテーブルのようにstrictプラグマで保護されているようなグローバルハッシュは存在しないので
window以下のグローバルオブジェクトを使い名前空間を構築するほかありません。
JAVASCRIPT:
-
(function(){
-
window.XXX = XXX || {};
-
window.XXX.YYY = XXX.YYY || {};
-
window.XXX.YYY.ZZZ = XXX.YYY.ZZZ || {};
-
window.XXX.YYY.ZZZ.aaa = function(){};
-
window.XXX.YYY.ZZZ.bbb = function(){};
-
})();
しかし、このように記述していては冗長すぎるので、
JAVASCRIPT:
-
var XXX = XXX || {};
-
XXX.YYY = XXX.YYY || {};
-
XXX.YYY.ZZZ = XXX.YYY.ZZZ || {};
-
-
(function(){
-
this.aaa = function(){};
-
this.bbb = function(){};
-
}).apply(XXX.YYY.ZZZ);
のように書くことで、クロージャ内のスコープにおいて、thisがXXX.YYY.ZZZとなり、冗長な記述を避けることができます。
また、Prototype.jsのObject.extendを利用すれば
JAVASCRIPT:
-
(function(){
-
Object.extend(this,{
-
aaa:export_aaa,
-
bbb:export_bbb
-
})
-
function export_aaa(){};
-
function export_bbb(){};
-
-
}).apply(XXX.YYY.ZZZ);
明示的にレキシカルスコープ中のどのメソッドを名前空間オブジェクトにバインドするかを記述することができます。
一方、
JAVASCRIPT:
-
var XXX = XXX || {};
-
XXX.YYY = XXX.YYY || {};
-
XXX.YYY.ZZZ = XXX.YYY.ZZZ || {};
この部分の記述の冗長さは付きまとってしまうので、
これら名前空間に関する処理をひとつにまとめるメソッドをJS.Namespace
JAVASCRIPT:
-
var ns =JS.Namespace.createNamespace('XXX.YYY.ZZZ');
-
ns.aaa=function(){};
-
ns.bbb=function(){};
-
-
or
-
-
JS.Namespace.createNamespace('XXX.YYY.ZZZ',function(){
-
Object.extend(this,{
-
aaa:export_aaa,
-
bbb:export_bbb
-
})
-
function export_aaa(){};
-
function export_bbb(){};
-
});
のように記述することができます。
createNamespaceは新しいネームスペースを定義することを明示している関数です。
なので、すでにそのネームスペース/オブジェクトが存在している場合エラーを出力します。
これはファイル名とパッケージの結びつきの小さいjsにおいて、
* 名前空間を定義する場面
* その空間に関数/クラスを追加する場面
* その名前空間上の関数を利用する場面
というのをある程度明示しておくことで、開発上の混乱を避けるためです。
では次に名前空間に定義された関数を利用する場面での記述を考えて見ましょう。
Filed Under (Uncategorized) by on 28-10-2008
依存関係のチェック
AAAというモジュールを利用して、BBBというモジュールを作成したいケースを考えてみると
Perlでは上記のように書けば、動的にincludeパスを探索し、モジュールのロードと依存関係の解決を
してくれますが、Jsではそんな便利な仕組みは存在しません。
また、HTTPリクエスト数の問題などからファイル名とモジュール名とを完全に同期させることも
非常に難しい課題です。
だからといって、jsのモジュールに
と書いたところで、このコメントはプログラムから解釈されないのですぐにエラーを出してくれませんし、
コメント自体の正しさを保障してくれません。
なので、
JAVASCRIPT:
-
if( !BBB )throw('ERROR!');
のようにコード中に依存関係を明示的に書くようにすれば、そのモジュールがロードされて無い場合すぐに気付くことができます。
JS.Namespaceライブラリでは
JAVASCRIPT:
-
JS.Namespace.depends('BBB','AAA.WWW');
このように記述することで、依存関係のチェックをコード中に埋め込むことができます。
Filed Under (Uncategorized) by on 28-10-2008
スコープ
javascriptにはPerlのようにレキシカルブロックが明示的に提供されていません。
なので、
JAVASCRIPT:
-
var i = 0;
-
-
{
-
var i = i;
-
i++;
-
i++;
-
console.log(i); // 2であってほしい
-
}
-
console.log(i); // 0であってほしい
このようにはかけません。
もしレキシカルにスコープを生成したい場合にはクロージャを使います。
JAVASCRIPT:
-
var i = 0;
-
-
(function(){
-
var i = window.i;
-
i++;i++;
-
console.log(i);
-
})();
-
console.log(i);
上記のプログラムはfunction(){}として、無名のクロージャを生成してそのまま実行しています。
ちなみにPerlのレキシカルスコープと少し異なるのは関数の宣言に関してです。
PERL:
-
# in perl
-
-
-
{
-
-
sub get_val{
-
-
}
-
}
この場合、get_valはCurrent Packageである、XXXのシンボリックテーブルに記憶されますが
JAVASCRIPT:
-
(function(){
-
var val = undefined;
-
function getValue(){
-
return val
-
}
-
-
})();
-
-
getValue(); // 関数が見つからない。
javascriptの場合、packageの概念は無いので、レキシカルスコープにはりつきます。
JAVASCRIPT:
-
var XXX = {};
-
(function(){
-
var val = 11;
-
function getValue(){
-
return val
-
}
-
XXX.getValue = getValue;
-
-
})();
-
-
XXX.getValue();
レキシカルスコープ上の関数を外部から参照可能にするためにはグローバルから参照可能な
オブジェクトに対して明示的に貼り付けてやる必要があります。
また、
JavaScriptの場合、ファイルベースのレキシカルスコープは存在しないので、
複数のJSの読み込まれているページの場合、それぞれのJSは明示的に
レキシカルスコープを作成する必要があります。
Filed Under (Uncategorized) by on 15-10-2008
どうも。ひろきのです。
Prototype.jsといえばクロスブラウザでDOMの足回りを提供してくれているすばらしいフレームワークですが、ちょっとばかり不満があります。
Event.observeで、あたえたeventHandlerの戻り値を完全に無視しているところです。
具体的には
JAVASCRIPT:
-
function createWrapper(element, eventName, handler) {
-
var id = getEventID(element);
-
var c = getWrappersForEventName(id, eventName);
-
if (c.pluck("handler").include(handler)) return false;
-
-
var wrapper = function(event) {
-
if (!Event || !Event.extend ||
-
(event.eventName && event.eventName != eventName))
-
return false;
-
Event.extend(event);
-
handler.call(element, event) //ここでハンドラの戻り値を返してない。
-
};
-
-
wrapper.handler = handler;
-
c.push(wrapper);
-
return wrapper;
-
}
このぐらい書き換えろとか思うかもしれませんがこれらの重要なライブラリについて元自体をいじってしまうのが難しい環境もあるかもしれません。
そもそもEventObjectのevt.returnValueを使えばいいじゃんとかありますが、WebKitのevt.returnValueの挙動が他のブラウザとちがい、実際にハンドラからreturnしないと受け付けないというものがあるため、コレ自体を完全にクロスブラウザにするのは難しかったりします。(evtオブジェクトのreturnValueを上書きできないのかも。。)
そもそもOperaにはonbeforeunloadが存在しませんし・・・
このハンドラからの戻り値をダイレクトに使用しなければならない場面はそう多くはありませんが、最近のWebアプリケーションでは一般的になっている編集中にページ遷移しようとするとでるメッセージ(Gmailなどで書きかけで移動するとでてくるやつ)を実現するためにはこの機能が必須になります。
ちなみにOperaの場合はステートが保存されているので、
戻るボタンを押せば編集中の内容が復帰します。
JAVASCRIPT:
-
Event.observe(window,'beforeunload',function(e){
-
return e.returnValue = 'このままページを移動すると編集中の内容は保存されませんがよろしいですか?';
-
});
のようにしたい場合、現行のPrototype.jsでは上手くいきません。
そこで、Webkitでbeforeunloadの場合に限り、Event.observeをフックすることを考えました。
以下コードです。
実用上は、Webkitの場合にはaddEventListenerを用いればよいのですが、透過的に行えたほうが自然といえば自然なので。
同様のテクニックで、Prototype.jsのカスタムイベントをよりリッチにすることができますが、それについてはまた今度。
JAVASCRIPT:
-
if(Prototype.Browser.WebKit)(function(){
-
-
Event._observe = Event.observe;
-
Event.observe=function(element,name,func){
-
if(/beforeunload/.test(name)){
-
element.addEventListener('beforeunload',function(evt){
-
return func.bind(element)(evt);
-
});
-
}else{
-
Event._observe(element,name,func);
-
}
-
}
-
Object.extend(window, {
-
observe: Event.observe.methodize()
-
});
-
})();
Filed Under (article) by on 13-10-2008
こんにちは。ひろきのだいちです。
今日はブラウザ界のやんちゃボーイ Internet ExplorerのEvent実行順序を調整する方法を考えたんでつらつら書きます。
ただ、すべてのDOMイベントを他のブラウザと互換を取るのはむずかしいので、prototype.js(1.6 or later)のdom:loadedイベントとwindow のloadイベントの実行順序を整列させます。
その後、単一のイベント間の実行順序がattach順にならないというIEのバグをDOM準拠のAPIを追加することで修正します。
Read the rest of this entry »