Hatena::Groupcside

Cside::StudyMemo このページをアンテナに追加 RSSフィード

メインブログに書くまでもない自分用メモを垂れ流す。日々是勉強也。

カテゴリー

2012-10-14

[][]capybara-webkit と polterguiest の違い

どちらも Capybara用のドライバで、かつ内部でWebkitを使ってヘッドレスにブラウジングするものということで、違いがよく分からない。

ちょっと調べて分かったのは、c-wはQt *1 経由でwebkitを使ってるのに対して、poltergueistはPantomJSでwebkitを使ってる、ということか。

polterguiestがc-wの代替として使われてきているっぽい。

Some people at RubyConfBR recommended checking out poltergeist, which uses PhantomJS, as an alternative to capybara-webkit.

(略)

It doesn't require QT or xvfb, is faster, more stable, and is being actively developed. I don't see a reason not to use it in place of capybara-webkit everywhere

Switch from capybara-webkit to poltergeist by bkeepers ? Pull Request #32 ? github/swordfish ? GitHub

*1:C++言語で書かれたアプリケーション・ユーザインタフェース(UI)フレームワーク http://ja.wikipedia.org/wiki/Qt

2012-08-26

[]submitボタンの連打を防ぐには

やり方はいろいろあるけど、いっぺんクリックされたらdisabledにする方式がシンプル。

<input type="submit" onclick="$(this)attr('disabled', true); $('#form').submit()" />

divで画面を覆うことで防ぐ方法もあるらしい。 ttp://d.hatena.ne.jp/ku__ra__ge/20100223/p3

[]onloadとonDOMContentLoadedの違い

load -> 画像も読み込み終わるまで待つ

DOMContentLoaded -> DOM構築時に発火


なのでDOMをいじりたいだけならDOMContentLoadedでいいというわけ

2012-07-15

[]全部分マッチを得る

覚えらんねぇ。 ttp://d.hatena.ne.jp/koseki2/20090530/JsIdiom

たとえば「#foo #bar #baz」から bar baz を抜きたいとき

var str = '#foo #bar #baz';
var re = /#(ba.)/g;
var match = [];
var _match;
while ((_match = re.exec(str)) != null) {
    match.push(_match[1]);
}
// match = ['bar', 'baz'];

Perlだとこう

my $str = '#foo #bar #baz';
my (@match) = $str =~ /#(ba.)/g;
# @match = ('bar baz')

2012-05-03

[]jQueryの .find と .filter の違い

findサーチ対象にその要素自身は含めない
filterサーチ対象にその要素自身も含める

こんな知識覚えたところで他で潰しがきかんしマジ意味ないんだけどな。。。

2012-04-03

[]data-validator.js - JSで引数をバリデーションする

https://github.com/Cside/data-validator.js

#!/usr/bin/env node
var DV = require('./data-validator');

method({foo: 'Foo', bar: 50});

function method() {
    var args = DV.validate({
        foo: 'Str',
        bar: {isa: 'Num', optional: 1},
        baz: {isa: 'Int', default: 10},
    }, arguments);

    console.log( args.foo ); //=> 'Foo'
    console.log( args.bar ); //=> 50
    console.log( args.baz ); //=> 10
}

Perlでいう Data::ValidatorSmart::Args にあたるものがJSでほしくなったので書いた。こういうの、まずい引数を渡されたらその場で処理を中止できる(デバッグで面倒なのは誤った引数が渡されてもなまじ途中まで動くケース)とともに、どういう引数の受け付け方をするのかがソースをパッと見て分かるから、時間を置いてメンテするときにも楽だ。

テストは全部通ったので、あとはドキュメント。

これ、どこまで需要あるだろ…。

2012-03-22

[][]Titaniumのrequireにおける相対パスの起点がおかしい件とその対策

Titaniumで外部のJSを読み込むとき、Ti.include の代わりに require を使うと、CommonJSで定義されたJSのモジュールを読み込むことができる*1。しかしこの方法には1つ大きな問題がある。

require('./path/to/module') の相対パス起点は、CommonJSではrequireを呼んだファイルが起点とされている対して、Tiではすべて Project/Resources/ が起点になる

もう少し具体的にいうと、こういうディレクトリ構成だったとして、

Resources/
├── app.js
└── lib1
    ├── module1.js
    └── lib2
        └── module2.js

module1.js 内で

require('./lib2/module2');

とmodule2を呼びだそうとしても、Titaniumは Resources/lib2/module2.js を読み込もうとしてしまうのでFile Not Foundでエラーになるということだ。この例でいうとパスを './lib1/lib2/module2' と書きなおさなければ動かない。


これの何がまずいかというと、CommonJSで書かれたJSのモジュールをTitaniumに移植してもこの問題のせいで動かないということだ。頑張ってパスを Resource/ を起点としたものに書き換えれば当然動くが、馬鹿馬鹿しいし、元のソースには極力手を入れたくないというものだ。

そこで、元のソースに手を入れずに require の挙動を変更する。app.js の最初でこのような定義をすればよい。

var origRequire = require;
var PathResolver = function (cwd) { 
    this.cwd = cwd || '.';
    var self = this;
    require = function (path) {
        return (new PathResolver(self.cwd)).require(path);
    };
};
PathResolver.init = function (cwd) { new PathResolver(cwd) };
PathResolver.prototype.require = function (path) {
    var newPath = this.cwd + '/' + path;
    this.cwd  = newPath.substr(0, newPath.lastIndexOf('/'));
    return origRequire(newPath);
};
PathResolver.init();

// Write your code below...

これで、require の相対パスの起点が呼び出したファイルになる。


TitaniumにはNode.jsでいう __filename のような、そのソースファイルの場所を取れる予約変数が無かったので、若干ややこしい実装になったし時間も食ってしまったのだけ残念。


Note

自分用メモ。

Tiに依存しない部分をNodeで動作チェックしつつCommonJSなモジュール化し、それをTiに移植する、というのを今回試みている。いくつかの罠(上で挙げたのも当然)を意識すれば、Tiで毎回10秒近くかかるエミュレータ再起動くりかえしな開発よりもだいぶ効率上がる感じなので、これについてはアプリが完成した段階でまとめたい。


てか最近、メインブログよりすっかりこっちが元気になってるな…。

[][]CommonJSの仕様 ⊂ Node.jsの仕様?

Nodeでは動いていたコードが一部Tiで動かなかった。たとえばこれ。

var module = require('module.js'); // error

CommonJSの仕様を見たところ .js を外さないと駄目なようだった。

var module = require('module'); //=> ok

ほかにも結構、NodeにはCommonJSから外れた仕様がある。(詳しくはここ ttp://d.hatena.ne.jp/t_43z/20110626/1309082158 。)


僕はTiで動かしたいモジュールを、Nodeで動作チェックしながら書いていた。何故でTiで実行しないかというと、エミュレータの再起動に毎回10秒近くかかるからだ。それはだるい。

しかしNodeにはCommonJSにない独自仕様があるから、Nodeで動いてたのにTiで動かないコードをデバッグするのに時間を取られる。本末転倒感ある。

CommonJSの仕様を脱してないかどうかcheckできるJSLintのようなものは無いのだろうか。ちょっと困っている。

そういうのが無いなら、diff Node.js CommonJS の主なところを知りたいが、探してもいまいち見つからない…。

2012-03-21

[](書きかけ)Strict Modeでグローバルオブジェクトが取れない理由とその対処法

結論:strict modeでglobal objectとりたいときは

new Function('return this;');

で取れる。


= = = =

JSDeferredの Deferred.define() みたく関数をグローバルにエクスポートしたくて書いたコードが動かなくて超はまった。

"use strict";
Deferred.define = function () {
    var obj = (function getGlobal () { return this })();
    // obj は グローバルオブジェクトが取れるはず…なのに、
    console.log(obj); //=> undefined
}

いろいろ試行錯誤していて、"use strict"; を外したらなぜか動いた。なんでか全然分からなかった。…とtwitterで助けを乞うたところ、教えていただいた。



ということらしい。調べてみると

【書きかけ】


http://nmi.jp/archives/268

http://javascriptweblog.wordpress.com/2011/05/03/javascript-strict-mode/

ttp://d.hatena.ne.jp/think49/20110528/1306586637

ttp://d.hatena.ne.jp/vividcode/20110528/1306597105

[]QUnitに正規表現にマッチするかをテストする関数を追加する(好きなテスト用関数を定義する)


というわけで、こけたときにBAIL_OUTしてくれるものを作るのを試みる。


QUnitで関数が定義されてる部分のソースを見ると

	equal: function(actual, expected, message) {
		QUnit.push(expected == actual, actual, expected, message);
	},
	strictEqual: function(actual, expected, message) {
		QUnit.push(expected === actual, actual, expected, message);
	},

な感じで、つまりめちゃめちゃシンプルに定義できることが分かる。


これらを真似する感じで、

var QUnit = require('./qunit'),

QUnit.like = function(actual, expected, message) {
    QUnit.push(expected.test(actual), actual, expected.toString(), message);
};

使ってみる。

QUnit.like('<p>hello</p>', /<html/i);
not ok 1 - expected: '/<html/i' got: '<p>hello</p>'

うむ。


ところでJSのテストフレームワークは割といろいろあるっぽいけど今はどれが主流なんだろう。

2012-03-20

[]JSでのマッチ変数

var str = '400x300';

str.replace(/(\d+)x(\d+)/,                   "$1 $2 $&");  //=> "400 300 400x300"
str.replace(/(\d+)x(\d+)/, function(){return "$1 $2 $&"}); //=> "$1 $2 $&"


str.replace(/(\d+)x(\d+)/, function(){
    return [
        arguments[0], //=> 400x300。$& にあたる
        arguments[1], //=> 400。    $1   〃
        arguments[2], //=> 300。    $2   〃
    ];
});

[]呼び出し元のglobal objectを取る

Perlでいうscalar caller(0)と近い。

JSDeferredの Deferred.define(); を読んだら分かった。

Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];

Deferred.define = function (obj, list) {
	if (!list) list = Deferred.methods;
	if (!obj)  obj  = (function getGlobal () { return this })(); // ここがミソ
	for (var i = 0; i < list.length; i++) {
		var n = list[i];
		obj[n] = Deferred[n];
	}
	return Deferred;
};

2012-03-19

[][]Perlライクにテストが書けるJSモジュールのスケルトン

Perlな人が学習コストなくJSのテストを導入する - Cside::StudyMemo - Csideグループ を自動化した。

https://github.com/Cside/starter.pl からcloneされたし。

スケルトン生成

$ ./starter.pl my-module # ハイフンつなぎ注意
my-module/
├── Makefile
├── my-module.js
└── t
    ├── 001_basic.js
    └── test/

make か make test でテストが走る。

あとは my-module.js に実装を、 t/001_basic.js にテストを書きたしていけばいい。

参考

クライアントサイドJSでもサーバーサイドJSでもうごくテストを書く - Perl Advent Calendar Japan 2011 Amon2 Track

2012-03-18

[][]Perlな人が学習コストなくJSのテストを導入する

https://github.com/tokuhirom/yearmonth-js

みたいなのをベースにいじればいい。

QUnitのメソッドをTest::More風にwrapしてくれる。こんな感じに書ける。

// widthという関数をテストする
subtest('width', function () {
    is(width('あいうえお'), 10);
    is(width('...'), 3);
    is(width('Shinjuku'), 8);
    is(width('…'), 2);
    is(width("\u2026"), 2); // ambiguous char should be full width
});
$ make
# test: width
ok 1
ok 2
ok 3
ok 4
ok 5

すごい。テンプレート作ったら便利そう。

yuku_tyuku_t2012/03/18 21:26次回の記事が「readモードで起動したあとでwriteをしたくなった時簡単にvimrc.writeを読み込む設定にキーバインド振った」的なのになると勝手に予想

CsideCside2012/03/18 21:30実はもう command! W :source ~/dotfiles/.vimrc.write を用意してたりします…

yuku_tyuku_t2012/03/31 23:11ずっと気になってたんだけど、なんでこれで起動が早くなるんだろうね。
だって、.vimrc.writeを読みこまなくたってpluginは勝手に全部読み込まれるわけで、.vimrcが数十行増えた所でpluginの読み込みのほうが支配的な気がするんだけど。
pluginの読み込みより、設定の読み込みの方が重たいってのは、何か設定のしかたが間違ってるんじゃないかという気がしてくる。

CsideCside2012/04/01 19:59お、てっきり Bundle "hoge" したものだけ読み込まれるものだとばかり。
であれば、起動時にネオコンをauto onにする設定が.vimrc.readにあるせいですね。あれがぶっちぎりで時間食ってるので。

2012-03-08

[]選択範囲のテキストを取得する

Share on Tumblrのソースを見たら

var selection
   = window.getSelection   ? window.getSelection()
   : document.getSelection ? document.getSelection()
   : document.selection    ? document.selection.createRange().text
   :                         0
;

みたいなだいぶ壮大なことしてたけど、これはクロスブラウザ対応のためであって、モダンなブラウザなら以下だけで取れる。

var selectedText = window.getSelection().toString();

2012-03-07

[]strict modeだとarguments.calleeとかが使えない

今日、Titaniumでarguents.calleeが使えなかったんですよ、と話したら、「strict modeだと使えないから、ひょっとしたらTiだとデフォルトでstrict modeなんじゃないかな?」と言われ、なるほどと思った。(strict modeのせいかどうかは見検証。)


今度からstrict modeで書くようにしてみる。恥ずかしながらまだ使ったことがない。

参考:

http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/

http://utatane-constellation.tumblr.com/post/3711268645

2012-01-15

[]CoffeeScript文法メモ(2)

CoffeeScript 言語リファレンス - sappari wiki

or -> perlでいう||、? -> perlでいう//

options or= defaults # 偽のとき
options  ?= defaults # nullやundefinedのとき

footprints = yeti or "bear"
footprints = yeti  ? "bear"

ただし ? に限っては、変数やプリミティブの後に付けて存在チェックに使える(存在演算子)。

if foo?
    ...
else
    ...

プロパティの値がnullやundefinedかもしれないとき、プロパティチェーンをドット「.」でつなぐ代わりに「?」を使える。

もし全てのプロパティが存在すれば普通に結果が返ってくるし、チェーンが途中で途切れればTypeErrorが発生する代わりにundefinedが返る。

zip = lottery.drawWinner?().address?.zipcode

2012-01-12

[]CoffeeScript文法メモ

Perl & JS脳なので、RubyとPythonのいいとこ取り文法ってのが全然馴染みない…。

CoffeeScript基礎文法最速マスター | 株式会社インフィニットループ技術ブログ

基礎

複数行コメント
###
Comments
###

文字

変数展開
name = "Cside"
str = "I am #{name}" 
ヒアドキュメント
html = '''
    <li>a</li>
    <li>b</li>
    <li>c</li>
'''

配列

Range
ary = [1..10]
mapのようなもの
ary = (i * i for i in [0...10])
ary = for i in [0...10] then i * i

とかあるけど

ary = [0..10].map (s) -> x * x

でよさそう。

オブジェクト

YAMLのように書けるけど気持ち悪い…。

obj =
     name:"tek_koc"
     age:22
     blother:
          name:"himitsu"
          age:6

制御文

ifとかが値を返す。

value = 3
if value is 3
    console.log "3."
else
    console.log "not 3."

result =
    unless value isnt 4
        "4."
    else
        "not 4."
条件演算子
JavaScriptCoffeeScript
===is
!==isnt
!not
&&and
││or
truetrue,yes,on
falsefalse,no,off
一行if式(前置、後置)

Coffeeでは三項演算子が使えないので以下のような書き方をする必要あり

console.log if flg then "正" else "否"

後置もできる。

flg = off
console.log "正" if flg
while, until

これも値を返す。これで配列が作れるってきもいなw

i = 0
v =
    while i < 10
        i++
# [0..9]
for文

これも(ry

kuku = for i in [1..9]
    for j in [1..9]
        i * j

ハッシュ(オブジェクト)のループにはinではなくofを使う。

for key of {foo:1, bar: 2}
    console.log key
forの前置と後置

大きく意味が異なる。

ary = x for x in [1..10]
ary = for x in [1..10] then x

これは以下のjsにコンパイルされる。

var ary, x;
for (x = 1; x <= 10; x++) {
  ary = x;
}
ary = (function() {
  var _results;
  _results = [];
  for (x = 1; x <= 10; x++) {
    _results.push(x);
  }
  return _results;
})();

関数

可変引数
func = (args...) -> console.log args
func 1,2,3

配列を引数に渡せばいいじゃん。ありがたみ分からぬ。

デフォルト引数
func = (str = "hoge") -> "Hello #{str}!"
引数に無名関数を渡す
setTimeout (-> console.log "ok" ), 1000

setTimeout ->
     console.log "ok"
, 1000
存在演算子

undefined以外真を返す。

console.log "defined" if var?

プロパティチェーンで使えるのに重宝しそう。

obj = one: {two: tree : true}

console.log obj.null.two  #=> error!!
console.log obj.null?.two #=> undefined

Class

今のところOOPでコード書く予定ないので省略。

そのときになったら今日から始めるCoffeeScript - KAYAC engineers' blogでも眺めよう。

きもいっていっぱい書いたけど

単に僕がrubyやpythonの文法の雰囲気に慣れていないだけという説もある

[][]VimでCoffeeScriptをリアルタイムプレビュー

GitHub - kchmck/vim-coffee-script: CoffeeScript support for vimをBundleなどで入れる。

シンタックスハイライト

vim-coffee-scriptを入れたら自動でされるのだけど、なぜか自分の環境では色がつかなかったので以下を設定。

autocmd BufNewFile,BufRead *.coffee set filetype=coffee

リアルタイムプレビュー

これヤバい。

au BufWritePost *.coffee :CoffeeCompile watch vert

.js ファイルに自動コンパイル

au BufWritePost *.coffee silent CoffeeMake! -b | cwindow | redraw!

あとvim-coffeescriptには :CoffeeRun っていうその名のごとくCoffeeを実行できるコマンドがあるけど、$ coffee [filename] でも実行できるっぽくて普通にQuickRunできたので今回は使わず。

Coffee使うのに便利そうな機能いっぱいあったのでまたvim-coffeescriptのリファレンスを折に触れて読み返してみる。

2011-12-15

[]JS正規表現メモ

String.prototype.replace() - JavaScript | MDN

ほんと慣れない…。

正規表現に変数を使う

そういうときはRegExpオブジェクトを使う。

var query = 'some string'
var re = new RegExp(query, 'ig');

マッチした文字の置換

str.replace(re, function(match) { '<tag>' + match + '</tag>' });

//こう書ける
str.replace(re, '<tag>$&</tag>');

ちなみに非破壊的。

2011-04-30

[]jQueryメモ(ver 1.5 とかも)

詳細

jQueryの配列操作用関数について

  • $.each, $.grep, $.map, $.merge, $.inArray などなど。
    • これらは生JSの forEach, filter, map, concat, indexOf と対応する。
  • これらは基本的に、NodeListなどのように配列ではないものでも使える。
    • これはありがたいように思えるけど、NodeListは $(NodeList) としてしまえばDOM操作用の諸メソッドが使えてしまうので、あまりNodeListで使う機会はない気がする。
  • これを使う機会があるとすれば、クロスブラウザを意識する必要のあるときかな?
    • ブラウザ拡張みたいに特定のブラウザ決め打ちのときは出番ないなあ。
  • $.unique は便利だね。
  • あと個人的には、配列の中にある要素が存在するかを「真偽値で」返すやつが欲しかったなあ。array.indexOf('foo') >= 0 はあんま好きくないので。
    • 仕方ないので自前で実装。
function exists(val, array) {
    return !! (array.indexOf(val) >= 0);
}
exists("foo", ["foo", "bar", "baz"]);  //=> true
  • sliceはまぎらわしいが配列操作用の関数ではない。
    • $('li').slice(2) のように使う

deferredオブジェクトを返す諸メソッドについて

  • when( .... ).then( .... ) など
  • JSDeferredを使ったほうが綺麗にかける
    • 終了

ajaxについて

  • $.ajax({...}), $.get(), $('div#foo').load(), $.post()
  • get('/foo.php') とか書けるのは楽だし、これは積極的に使っていきたい
  • $.ajax() にはオプションがいっぱい
    • cache, beforeSend, complete, contentType, dataFIlter, dataType, error, global, ifModified, timeout, ...
    • 一回ごとの通信ではなく、すべての通信で共通のオプションも定められる 
      • ajaxSetup
        • Ajax Global Eventと呼ばれる
    • よく使うもの: cache(ログイン処理とかでoffにする), timeout(通信にもよるけど1.5secくらい)
  • 通信の面倒を見てくれるだけでなく、取得後の面倒を見てくれるものもある
    • getJSON、getScript
  • 特定の要素にオプションを仕掛けることもできる
    • $.ajax*という名前の関数

まぎらわわしいtoggleメソッド

いっぱいあってたいへんにまぎらわしい。

// クリックで次の関数にトグル
$('li').toggle(func, func, ..., func);

// 式が真なら表示、偽ならhide
$('li').toggle(式);

// 表示中ならhide、hide中なら表示
$('li').toggle();

ほか、トグル系のメソッドは hover(func, func); など

bindとtrigger

bindでイベント発火時のコールバックを割り当て、triggerで発火。

bindはaddEventListenerと違い、独自イベントも割り当てられる。

$("p").click( function (event, a, b) {
  // 普通にp要素がクリックされた場合、aとbには何も渡されないのでundefinedになります。
  // aには"foo"、bには"bar"が入ってくる。
} ).trigger("click", ["foo", "bar"]);

bindの類似でliveなるものもある。

// すべての a要素
('a').bind(....);
// すべての a要素 + 今後新たに追加されるすべての a要素
('a').live(....);

以下のようにして、イベント管理用オブジェクトを作るのはよく見る手な気がする。

// 発火用関数
function receiveData(evName, data) {
    EventManager.trigger(evName, data);
}

// イベント管理オブジェクト
var EventManager = $({});
EventManager.bind('eventFoo', function(data){
    ....
});
EventManager.bind('eventBar', function(data){
    ....
});
....

parse系関数

parseXMLは普通に便利。速度を気にしないなら、これでもうXMLをゴリゴリDOM操作でパースすることから解放される。

// JSON.parse使えないブラウザのとき有用
$.parseJSON(jsonString);

// これは便利
$.parseXML(xmlString);

・・・っていっても、確かjQのajax系メソッドは、xml文字列を自動でDocument Objectに自動変換した上で返してくれた気がするけど。

位置取得メソッド

  • offset(), position(), scrollTop(), scroll,
  • position: 親要素からの位置
  • offset ドキュメント上の位置

その他

// (new Date()).getTime() のようなもの
$.now();

まとめ(?)

  • クロスブラウザなら絶対使うべき(jQでなくていいけど、何らかのライブラリは)
  • では、ブラウザ拡張など、特定のブラウザ決め打ちの開発時はどうか
    • けっこう使いどころある。
      • 記述を大幅に短縮できるメソッドがたくさんあるから楽
        • 特にajaxでは。
          • ajax()などは記述を短縮化してくれるだけでなく、デフォルトでキャッシュの面倒も見てくれたりするし
      • DOM操作は生JSだと特に量が多くなってしんどいし
        • parseXML()とかもあるし
    • ただし、配列操作用関数はほとんど要らない($.unique()は除く)
    • HTMLでもXMLでも、DOM操作するときは導入して決してオーバーではないと思う

2011-04-27

[]はてブ拡張のファイル構成

  • 便利関数はグローバルにエクスポートか、Utils.jsに入れるか
    • 便利系関数は(どこで使うものだろうが)だいたいUtilsオブジェクトに押し込んでる感じ
    • しょっちゅう使うようなもの(デバッグ用関数)とか、他の言語にもあるようなもの(sprintfとか)はグローバルにエクスポートしてる感じ
  • popup.htmlとかpopup.jsとか
    • Utils.jsはpopup.htmlでもbackground.htmlでも読み込まれていた
      • backgroundにほとんどのメソッドを集約する設計ではなかった
      • 必要なときだけbgにアクセスしてる感じ
        • bgじゃないと使えないchrome api使うときとか、xhr通信するときとか
    • 普通にfunction foo { ... }で関数べた書きしてる感じだった
      • 一番最後に読み込まれるものだからあんまり気にする必要ないよね、そもそも。
  • String.prototype.* とか Object.prototype.* は全く見つからなかった
    • "foo".method() とかチェインしていけるのは便利だけど、prototype汚染しちゃうしなあ
  • content script
    • connect & postMessageで通信してるっぽい
    • contentでposeMessage({message: "eventA"}) -> background.jsでbind("eventA", ...); みたいな感じ
      • bindはjQueryのメソッド

[]JSの開発効率を上げる便利関数メモ

はてブ公式拡張のソースを覗いてたら便利そうなのが転がってたのでメモ。

(関数の定義は拡張のソースからそのまま取ってきたものです。)

デバッグ用関数 p

プリミティブでもオブジェクトでもstringigyしてconsole.logしてくれる

var p = function() {
    console.log(JSON.stringify(Array.prototype.slice.call(arguments, 0, arguments.length)));
}
p("foo", "bar");  //=> ["foo", "bar"]
p({foo: "bar"});  //=> [{"foo":"var"}]

sprintf

var sprintf = function (str) {
    var args = Array.prototype.slice.call(arguments, 1);
    return str.replace(/%0(\d+)d/g, function(m, num) {
        var r = String(args.shift());
        var c = '';
        num = parseInt(num) - r.length;
        while (--num >= 0) c += '0';
        return c + r;
    }).replace(/%[sdf]/g, function(m) { return sprintf._SPRINTF_HASH[m](args.shift()) });
};
sprintf._SPRINTF_HASH = {
    '%s': String,
    '%d': parseInt,
    '%f': parseFloat
};
console.log(sprintf("%04d年%02d月%02d日", 2011, 1, 1));  //=> 2011年01月01日

createElementSimply、createElementFromString

シンプルな要素メーカー。これだけのためにjQを使いたくないときに。

createElementSimply("div", {id: "foo", className:"bar"});
    //=> <div id="foo" class="bar"><div>

createElementFromString(
    "<div><p>#{hello}</p><p>#{world}</p></div>", {
    data:{
        hello: "Hello",
        world: "World"
    }
});
    //=> <div><p>Hello</p><p>World</p></div>
    createElementSimply: function(name, attr) {
        var children = Array.prototype.slice.call(arguments, 2);
        var e = document.createElement(name);
        if (attr)
            for (var key in attr)
                e[key] = attr[key];

        children.map(function(el) { return el.nodeType > 0 ? el : document.createTextNode(el) }).
            forEach(function(el) { return e.appendChild(el) });
        return e;
    },
    createElementFromString: function (str, opts) {
        // original code by cho45 - http://gist.github.com/3239
        if (!opts) opts = { data: {} };
        var document = opts.document || window.document;
        if (!opts.data) opts.data = { };
        var t, cur = opts.parent || document.createDocumentFragment(), root, stack = [cur];
        while (str.length) {
            if (str.indexOf("<") == 0) {
                if ((t = str.match(/^\s*<(\/?[^\s>\/]+)([^>]+?)?(\/)?>/))) {
                    var tag = t[1], attrs = t[2], isempty = !!t[3];
                    if (tag.indexOf("/") == -1) {
                        child = document.createElement(tag);
                        if (attrs) attrs.replace(/([a-z]+)=(?:'([^']+)'|"([^"]+)")/gi,
                            function (m, name, v1, v2) {
                                var v = text(v1 || v2);
                                if (name == "class") root && (root[v] = child), child.className = v;
                                child.setAttribute(name, v);
                            }
                        );
                        cur.appendChild(root ? child : (root = child));
                        if (!isempty) {
                            stack.push(cur);
                            cur = child;
                        }
                    } else cur = stack.pop();
                } else throw("Parse Error: " + str);
            } else {
                if ((t = str.match(/^([^<]+)/))) cur.appendChild(document.createTextNode(text(t[0])));
            }
            str = str.substring(t[0].length);
        }
        function text (str) {
            return str
                .replace(/&(#(x)?)?([^;]+);/g, function (_, isNumRef, isHex, ref) {
                    return isNumRef ? String.fromCharCode(parseInt(ref, isHex ? 16 : 10)):
                                      {"lt":"<","gt":"<","amp":"&"}[ref];
                })
                .replace(/#\{([^}]+)\}/g, function (_, name) {
                    return (typeof(opts.data[name]) == "undefined") ? _ : opts.data[name];
                });
        }
        return root;
    }

他にもたくさん汎用性のある便利関数がたくさんありそうなので、もうちょっとソース覗いてみる。

2011-04-25

[]JS開発で意識してると良いかもしれないこと メモ

関数の中での関数定義

(function() {
    var func = function() {
    };
    func();
})()

としてしまいがちだが、

(function() {
    function func() {
    };
    func();
})()

でよい。

速くするための工夫

ttp://subtech.g.hatena.ne.jp/cho45/20091113/1258047997

  • DOMContentLoaded ですら遅いので、HTML 生成時に script 要素を各所に埋めこんで処理を先走りさせる
  • 初期表示に必ず XHR による通信が必要なコンテンツを localStorage にキャッシュして次回はとりあえずそれを表示させてから通信を走らせる
    • ユーザを一瞬騙すけど気持ちはよくなる
  • エフェクトの実行時間自体を半分にする、あるいはなくす
    • というかエフェクトは基本的に通信が同時に走るとき以外はしない、というルールにしたほうがよい
  • とりあえずでもいいのでユーザのアクジョンの結果は先にだす
    • アクション → 未確定 → 確定とちゃんと見た目が遷移するように

要素の見た目を変える処理は外部化する

  • ハードコーディングしてしまうとメンテナンス性が下がる
  • 専用オブジェクトを用意してもよい

引数が多くなる場合は、引数をハッシュ化する

  • 順番で混乱したり、抜けたりするのを防ぎやすくなる

 

例外を使う

try {
    throw new Error("error!");
} catch(e) {
    alert(e.message)
}

設定値は一元化しておく

  • てか、データを小さなオブジェクトに格納する習慣つけとくとコードが見やすくなりそう

ほか

Foo = {
    methodA: function() {
    },
    methodB: function() {
    }
};

if (env == 'bar') {
    Foo.methodA();
} else {
    Foo.methodB();
}

より

Foo = {
    // 実行用メソッド
    execute: funcction(env) {
        if (env == 'bar') {
            this.methodA();
        } else {
            this.methodB();
        }
    },
    methodA: function() {
    },
    methodB: function() {
    }
};

Foo.execute(env);

みたいに、実行用のメソッドもオブジェクトに定義しといたほうがいい(ときもあるかも)

参考

  • ttp://subtech.g.hatena.ne.jp/cho45/20110212/1297518804
  • ttp://subtech.g.hatena.ne.jp/cho45/20091113/1258047997
  • ttp://blog.xole.net/article.php?id=612
  • ttp://blog.xole.net/article.php?id=613

2011-03-15

[][]作りながらnode.jsを復習

ttp://d.hatena.ne.jp/sugyan/20110313/1300025780 をみながらnode.jsを復習。

var access_token = 'XXXXXXXXXXXXXXXX';
var tag          = 'prayforjapan';

var http     = require('http');
var https    = require('https');
var socketIo = require('socket.io');

var flg = false,
var recent;

// node.jsでサーバー立てるときは http.createServer
// app.onrequest = function() { ... }; と同じ
var app = http.createServer( function(req, res) {
    // Realtime StreamはAPI提供側からPOSTで通知される
    if (req.method == 'POST') {  // 本当はヘッダの検証とかする必要あり
        if (! flg) {
            flg = true;
            https.get({  // (1)
                host: 'api.instagram.con',
                path: '/v1/tags/' + tag + '/media/recent?access_token=' + access_token
            }, function(res) {
                var body = '';
                res.on('data', function() {
                    body += data.toString();
                });
                res.on('end', function() {
                    recent = JSON.parse(body);
                    socket.broadcast(recent);  // undefined socket
                    flg = false;
                });
            });
        }
        res.end();
    }
    else {
        if (req.url != '/' + tag) {
            res.statusCode = 404;
            res.end;
            return;
        }
        // Webページの用意
        res.writeHead(200, {'Content-Type': 'text/html'});  // (2)
        res.write(''); // 表示はクライアントでやるので内容はどうでもいい
        res.end();
    }
});
app.listen(80):

var socket = socketIo.listen(app);
socket.on('connection', function(client) {
    if (recent) {
        client.send(recent);
        // clientからのmsgは受けないパターン。
     // 接続を確立してからずっと監視し続けるというよりは、同期的な例。
    }
})

client側の実装はこう

// WSのアクセスはsocket単体でOK
var socket = new io.Socket('64.30.139.104');  // (5)
socket.on("message", function(msg) {
    ....
});
socket.connect();

(1) node.jsにおけるHTTP Request処理

一番簡単なのはこれ。$ua->get($url) みたいなもん。

var http = require('http');

http.get({
    host: 'localhost',
    path: '/',
}, function(res) {
    var body = ""
    res.on('data', function(data) {
        body += data;
    });
    res.on('end', function() {
        console.log(body);
    });
});

$ua->request(GET => $url) にあたるのがこれ。こちらは明示的にend()する必要あり。

var req = http.request({
    ....
}, function(res) {
    ....
});
req.end();

// equals
var req = http.request({
    ....
});
req.end();

req.on('response', function(res) {
    res.on('data', function (data) { });
    res.on('end', function() { });
});

POSTだとこう。reqにwriteする。

var req = http.request({
    ....
}, function(res) {
    ....
});
req.write('data\n');
req.end();

ドキュメントによると、http.requestはClientRequestクラスのインスタンスを返すらしい。このインスタンスは別の方法によっても生成できて、以下のように書く:

var client = http.createClient(port, host);
var request = client.request(
    'GET',
    path,
    { host: u.hostname }
);

(2) 基本的なレスポンス

Hello Worldを思い出す。

var server = http.createServer(
    function (request, response) {

        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.write('Hello World!!\n');
        response.end();
    }
).listen(8124);

(3) ファイルを読み込む・書きこむ

ReadStream、WriteStreamという概念。

ttp://d.hatena.ne.jp/yamamucho/20110220/1298191467

(4) Expressを使うか否かの境目

サーバー側ではclientにデータをダラ流しにするだけ、みたいなのならフレームワークなしで全然いける。

pathに応じたViewがいくつも、みたいなアプリならExpress使ったほうがいい。(plackとMojo::Liteの使い分けと似てる。)

(5) socket.io

ttps://github.com/LearnBoost/Socket.IO-node まんま。

// On the server:

var http = require('http'), 
        io = require('./path/to/socket.io'),

server = http.createServer(function(req, res){
    // your normal server code
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end('<h1>Hello world</h1>');
});

server.listen(80);

var socket = io.listen(server);

socket.on('connection', function(client){
  // new client is here!
  client.on('message', function(){})
  client.on('disconnect', function(){})
});

//On the client:
// <script src="/socket.io/socket.io.js"></script>

var socket = new io.Socket();
socket.connect();
socket.on('connect', function(){})
socket.on('message', function(){})
socket.on('disconnect', function(){})

データをサーバ・クライアント間でやりとりするメソッドは以下。

serverclient.send(message)Sends a message to the client.
serverclient.broadcast(message)Sends a message to all other clients.
clientsocket.send(data)

参考:ttp://d.hatena.ne.jp/Jxck/20100831/1283253824

2011-03-13

[][]node.js入門メモ(2)

http://dl.dropbox.com/u/219436/node.js/handson/build/html/index.html

非同期io

var util = require('util');
var url  = require('url');
var http = require('http');

function download(urlStr) {
    var u = url.parse(urlStr);
    var client = http.createClient(u.port || 80, u.hostname);
    var req = client.request('GET', u.pathname, { host: u.hostname });
    req.end();
    request.on('response', function(res){
        console.log(res.statusCode);
        for (var i in res.headers) {
            console.log(i + ": " + res.headers[i]);
        }
        res.setEncoding('utf8');
        res.on('data', function(chunk) {  // req has many data
            util.print(chunk);
        });
        res.on('end', function(){
            console.log*(;
        });
    })
}

download(process.argv[2]);

2つのプログラミングモデル

コールバック関数を渡すコールバックモデルの他に、「イベントを専用オブジェクトに定義し、関数の返り値として返す」イベント駆動モデルがある。

受け取る(発火させる)側

var EventEmitter = require('event').EventEmitter;
function download(url){
   var ev = new EventEmitter();

   request.on('response', function(response){
      // ... (snip) ...
      response.on('end', function(){
         ev.emit('notify', response, body);  // 発火
      });
   });

   return ev;
}

呼び出す側

var ev = download(url);
ev.on('notify', function(response, body){
   ...
});

メリット:

  • 異なるイベント名をつけることで,それぞれの状況に応じた引数を組み立てることができる
  • イベント受信側では複数のイベントハンドラを登録することができる

ライブラリを作る

メソッドをエクスポートする

exports.add = function() { .... };

node.jsでのクラス継承

var util = require('util');
function ClassB(){ .... }
// util.inheritsで継承
util.inherits(ClassB, ClassA);

[][]node.js入門メモ

node.jsとは何のためにあるものか

イベントループモデルと聞いて、最初はnginxとかと何が違うのか分からなかったけど、こういうことらしい。

apache はご存じのとおりスレッド、対して nginx はイベントループ。で、その性能の差は使用メモリ量などに端的に表れていたんだ。スレッドモデルの場合ね、実行スタックをコピーする必要があるからスレッドが増えるほどメモリ使用率が上昇する。対してイベントループモデルは1プロセスのままだからメモリはそれほど食わない。この点ではイベントループが有利っていうコンテクストだ。

ただね、イベントループモデルにも弱点がある。それはコードのどこかでブロックする処理が発生するとプロセス全体がストップしちゃう、つまりイベントループ自体の処理もストップしてしまい、パフォーマンスに大きく影響してしまうってことなんだ。

ryan はこの、イベントループにおけるブロックをなくそうと考えた。それが node.js のそもそもの始まりってわけ。

http://d.hatena.ne.jp/badatmath/20101020/1287587240

hello world

var sys = require('sys');
var http = require('http');

var server = http.createServer(
    function (request, response) {

        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.write('Hello World!!\n');
        response.end();
    }
).listen(8124);

sys.log('Server running at http://127.0.0.1:8124/');

TCPサーバ

http://www.atmarkit.co.jp/fcoding/articles/websocket/01/websocket01b.html

var net = require('net');
var sys = require('sys');
var server = net.createServer(function (stream) {
  var array = [];
  stream.setEncoding('utf8');
  stream.on('connect', function () {
    stream.write('connected\r\n');
  });
  stream.on('data', function (data) {
    array.push(data.slice(0,-2));
    stream.write('res: ' + array.join(',') + '\n');
  });
  stream.on('end', function () {
    sys.log('disconnected')
    stream.end();
  });
});
server.listen(8127, 'localhost');

WebSocketを使う

node.jsとWebSocketは相性が良いらしい。

WebSocketはステートフル(一度クライアントとサーバとの間で接続が確立されると、その後のやり取りで状態を共有することができる)。必要なときにサーバサイドから情報を送ることができれば、ムダなトラフィックを減らせる。

var ws = new WebSocket("ws://example.com/service");
ws.onopen = function() {
  // Web Socket is connected. You can send data by send() method.
  ws.send("message to send"); ....
};
ws.onmessage = function (evt) { var received_msg = evt.data; ... };
ws.onclose = function() { // websocket is closed. };

naoyaさんのブログで、サーバ側をperl(Mojolicious::Liteに全部やってもらう感じ)、クライアント側をjs、っていうサンプルコードがあった。

http://d.hatena.ne.jp/naoya/20101011/1286778922

  var ws = new WebSocket('ws://localhost:3000/echo');
  ws.onopen = function () {
    log('Connection opened');
  };
  
  ws.onmessage = function (msg) {
    var res = JSON.parse(msg.data);
    log('[' + res.hms + '] ' + res.text); 
  };

  $('#msg').keydown(function (e) {
    if (e.keyCode == 13 && $('#msg').val()) {
        ws.send($('#msg').val());
        $('#msg').val('');
    }
  });

node.jsでWebアプリを作るチュートリアル

http://dl.dropbox.com/u/219436/node.js/handson/build/html/index.html