Hatena::ブログ(Diary)

文殊堂 このページをアンテナに追加 RSSフィード Twitter

2012-08-31

BPStudy#60 ECMAScript5時代のJavaScriptライブラリ

JavaScript: 世界で最も誤解されたプログラミング言語(2001年)

JavaScript: The World’s Most Misunderstood Programming Language

oct inaodu

2012年未だ誤解されているプログラミング言語JavaScript

嫌われている"JavaScript"とは何か?
  • ECMAScript3
    • 13年前・前世紀(1999年)の言語仕様
  • IE6
嫌われている"JavaScript"と同世代の各言語のバージョンは?
  • Python2.1
  • Ruby1.6
  • Perl5.6
  • J2SE1.3

現在のJavaScriptはECMAScript5

どの環境で使える?

ECMAScript 5 compatibility table

ES5 features on iOS/Android's default browser - 愛と勇気と缶ビール

  • IE9+(Strict modeを除く)
  • Google Chrome13+
    • 変数名にゼロ幅スペース使用可能」を除く
  • Firefox6+
  • Safari5.1.4+
    • 変数名にゼロ幅スペース使用可能」を除く
      • 込みなら6+
  • MobileSafari iOS6+
  • Android 標準ブラウザ 4.0+
    • 変数名にゼロ幅スペース使用可能」を除く
    • strict modeを除く
      • 込みなら4.1+
  • Opera12+
    • 変数名にゼロ幅スペース使用可能」を除く

shimライブラリを使えば対応環境はもっと増える

kriskowal/es5-shim ? GitHub

  • IE9+(Strict modeを除く)
  • Google Chrome13+
    • 変数名にゼロ幅スペース使用可能」を除く-Firefox6+
  • Safari5.1+
    • 変数名にゼロ幅スペース使用可能」を除く
      • 込みなら6+
  • MobileSafari iOS5.0+
    • 変数名にゼロ幅スペース使用可能」を除く
    • 変数名にゼロ幅スペース使用可能」を除く
    • strict modeを除く
      • 込みなら4.1+
  • Opera12+
    • 変数名にゼロ幅スペース使用可能」を除く
何が出来る?
  • strict mode
  • Array#map等のarrayの反復メソッド
  • その他builtin objectの便利メソッドが若干
  • native JSON
  • Function#bind
  • Object.create
  • object保護関数
    • Object.preventExtensions
      • propertyの追加定義禁止
    • Object.seal
      • propertyの追加定義・再定義禁止
    • Object.freeze
      • propertyの追加定義・再定義・値の変更禁止
  • Object.Object.defineProperty
    • writable
    • enumerable
    • configurable
    • value
    • get
    • set

ECMAScript5の豊かさを活かしていきたい

ECMAScript5をターゲットにしたライブラリ、BeautifulProperties.js

v0.1.0を先ほどリリースしました

monjudoh/BeautifulProperties.js at v0.1.0 ? GitHub

2012-07-26

インスタンスに依存した初期値を持つ書き換え可能propertyの定義

インスタンスに依存してなければこれで済むから簡単ですよねー

function A(){}
var proto = A.prototype;
Object.defineProperty(proto,'key',{
  value : 'default',
  writable : true
});

インスタンスに依存している場合はprototype定義時にそのインスタンスが存在しないのでvalueで初期値を定義できません。

単純なコードだと、実際の値を保持する別propertyとget/setを定義して、まだ保持していなかったら設定するとかそうなるでしょう。

function foo(val) {
  // valの内容によって戻り値が変わると思いねえ
  return 'default';
}
function A(){}
var proto = A.prototype;
Object.defineProperty(proto,'key',{
  get : function () {
    var self = this;
    if (self._key === undefined) {
      self._key = foo(self);
    }
    return self._key;
  },
  set : function (val) {
    var self = this;
    self._key = val;
  }
});

しかし、無駄にpropertyを増やしたくありません。

こうします。

prototypeに対してconfigurable:trueにしてget/setありのpropertyを定義、

その中でインスタンスの(!=prototypeの)propertyを上書き定義します。

function foo(val) {
  // valの内容によって戻り値が変わると思いねえ
  return 'default';
}

function A(){}
var proto = A.prototype;
Object.defineProperty(proto,'key',{
  get : function () {
    var self = this;
    var val = foo(self);
    Object.defineProperty(self,'key',{
      value:val,
      writable:true
    });
    return val;
  },
  set : function (val) {
    var self = this;
    Object.defineProperty(self,'key',{
      value:val,
      writable:true
    });
  },
  configurable : true
});

ばっちり動きますね

a= new A;
b= new A;
a.key; // 'default'
a.key= 1;
a.key; // 1
b.key; // 'default'

参考ES5, Property Descriptor解説 - 枕を欹てて聴く

2012-07-25

JavaScriptでの非同期関数合成

Unserscore.jsや互換ライブラリLo-Dashを使うと関数合成が出来ます。

複数個の関数があって、関数を呼び出した結果を使って関数を呼び出して…っていうのを1個の関数にします。

ドキュメントの例を見れば分かるかと。


簡略化のために関数合成の対象になる関数を1引数戻り値ありの関数とします。

これを非同期処理をする関数に当てはめるとcallbackを含む2引数戻り値なしの関数が当てはまるでしょう。

この場合のcallbackは1引数関数とします。

まず、logを出力するcallback関数を定義しましょう。

function log(result){
  console.log(result);
}

次にcallbackを含む2引数戻り値なしの関数を定義します。別に非同期処理はやっていないです。

// 1を足す
function add1(callback,arg){
  callback(arg+1);
}

// 2をかける
function mul2(callback,arg){
  callback(arg*2);
}

実行してみましょう

add1(log,4); // 5
mul2(log,5); // 10

結果を引き回す形で呼んでみましょう。

add1(function(result){
  mul2(log,result);
},4); // 10

呼び出しが深くなるとcallbackのネストが段々鬱陶しくなります。

add1(function(result){
  mul2(function(result){
    add1(log,result);
  },result);
},4);

Function#bindによる部分適用に置き換えます。

add1.bind(null,mul2.bind(null,log))(4); // 10

べた書き部分を汎用にしてみます。

(function(){
  var slice = Array.prototype.slice;
  var funcs = slice.call(arguments);
  return funcs.reduceRight(function(prevResult,current){
    return current.bind(null,prevResult);
  })
})(add1,mul2,log)(4);

今回やりたいことは、callbackを含む2引数戻り値なしの関数をn個合成してcallbackを含む2引数戻り値なしの関数にすることなのでこれは違います。

で、こうします。

function noop(){}
function composeAsync(){
  var slice = Array.prototype.slice;
  var funcsOrig = slice.call(arguments);
  return function (callback,arg) {
    var funcs = funcsOrig.slice();
    funcs.push(callback || noop);
    funcs.reduceRight(function(prevResult,current){
      return current.bind(null,prevResult);
    })(arg);
  };
}
実行してみましょう
composeAsync(add1,mul2)(log,4); // 10
composeAsync(add1,mul2,add1)(log,4) //11

「callbackを含む2引数戻り値なしの関数をn個合成してcallbackを含む2引数戻り値なしの関数にする」ので合成した関数も合成対象にできます。

var composed = composeAsync(add1,mul2);
composeAsync(composed,composed)(log,4);

この縛りでは合成対象の関数の汎用性に問題があるという場合は、

汎用性のある多引数関数を定義して部分適用したものを合成すればいいと思います。

2012-04-17

packerで圧縮されたJavaScriptのdebug方法

minifyされたJavaScriptは各種開発者ツールのdeminifierを使えば整形された状態でdebug出来ますが、packerで圧縮されたJavaScript(以下packed JS)はどうかという話。

Google Chromeの開発者ツール前提で話をするので他のブラウザについては誰か調べてみてください。

packed JSをdebugする際の壁はそれがevalで実行されることですが、Google Chromeの開発者ツールではevalで実行されたJSもdebug実行出来ます。

問題はどうやってdebug実行に持っていくかですが、globalから辿れる何らかのAPIがあるなら呼び出し元でブレークポイントを貼ってstep intoすれば良いです。

その場実行されるようなJSの場合はそれもできないので工夫が必要です。

以下は古いjQueryを使った例でそのような工夫は本来不要ですが、その場実行されるJSに対して使えるテクニックです。

その場実行されるpacked JSをdebug実行する(Google Chrome開発者ツール)

ターゲットとなるpacked JSが読み込まれる前に何らかの手段でJSを読み込めるようにしましょう。

CocProxyあたりで差し替えられるようにすれば良いと思います。

そこで、ターゲットの中で使われているであろうbuiltin methodを差し替えるJSを読み込ませ実行します。

失敗したら使われていそうなmethodを変えてやっていきましょう。

元と同じ動作でdebugger statementが仕込まれたmethodに差し替えます。

(function() {
  var proto = String.prototype;
  var x = proto.replace;
  proto. replace = function() {
    debugger;
    return x.apply(this, arguments);
  };
})();

packed版jQueryを読み込ませてみましょう。

s=document.createElement('script');s.src='http://jqueryjs.googlecode.com/files/jquery-1.2.6.pack.js';document.body.appendChild(s);

早速debugger statementでbreakされますが、

f:id:monjudoh:20120417203148p:image

step outするとpacked JS自体の実行だと分かるのでどんどん実行してしまいましょう。

f:id:monjudoh:20120417203147p:image

さて、ここからが本番です。scriptタブの下部の「{}」をクリックして有効(青)にしましょう。これが標準で使えるdeminifierです。

適当に以下のようなコードを実行して、packed版で読み込んだjQueryのコードを動かしましょう。

$('<div>');

f:id:monjudoh:20120417203938p:image

step outすると整形されたターゲット(jQuery)のコードが見れます。

f:id:monjudoh:20120417203937p:image

以降はscriptタブ上部のソース選択で(program)というものがあるのでその中でconsoleから実行したものではない方を選べばいつでも整形されたターゲットのコードが見れますし、以下のように好きなところにブレークポイントを貼ってdebug実行することも出来ます。

f:id:monjudoh:20120417203936p:image

2011-12-12

MacHGでのrebase onto Mercurial Advent Calendar 2011

Mercurial Advent Calendar 2011 - PARTAKEの12日目は文殊堂がお送りします。

深夜35時などという遅くに申し訳ありません。

今日はSourceTree無料キャンペーンによって存在意義が危うくなった感のあるMacHGも得意な箇所は凄いんだよ、ということで

MacHGでrebase ontoをやるお話です。

Mercurialのrebase

rebaseというとGitのコマンドという印象が強いですが、

Mercurialでもビルトインのextensionを有効化するだけで使用できます。

話の都合上ついでにいくつか有効化しておきましょう。

[extensions]
rebase =
mq =
graphlog =

rebase onto

さて、今日お話しするのはgit rebase --onto相当のことです。

まず以下のglogを見てください。

このmami branchから分岐しているmado branchをhomu branchから分岐するように改変します。

$ hg glog
@  changeset:   3:09876c6c806e
|  branch:      homu
|  tag:         tip
|  parent:      0:663f5d36f850
|  user:        monjudoh <monjudoh@gmail.com>
|  date:        Tue Dec 13 09:51:41 2011 +0900
|  summary:     branch homu
|
| o  changeset:   2:917591f587ee
| |  branch:      mado
| |  user:        monjudoh <monjudoh@gmail.com>
| |  date:        Tue Dec 13 09:44:03 2011 +0900
| |  summary:     branch mado
| |
| o  changeset:   1:eaa1adaa875b
|/   branch:      mami
|    user:        monjudoh <monjudoh@gmail.com>
|    date:        Tue Dec 13 09:42:29 2011 +0900
|    summary:     branch mami
|
o  changeset:   0:663f5d36f850
   user:        monjudoh <monjudoh@gmail.com>
   date:        Tue Dec 13 09:41:46 2011 +0900
   summary:     first commit

とりあえず--sourceと--destを指定してみるとただのmergeになってしまいます。

$ hg rebase --source mado --dest homu
saved backup bundle to /Users/monjudoh/Dropbox/example/.hg/strip-backup/917591f587ee-backup.hg
$ hg glog
@    changeset:   3:4f7d2a838aa9
|\   branch:      homu
| |  tag:         tip
| |  parent:      2:09876c6c806e
| |  parent:      1:eaa1adaa875b
| |  user:        monjudoh <monjudoh@gmail.com>
| |  date:        Tue Dec 13 09:44:03 2011 +0900
| |  summary:     branch mado
| |
| o  changeset:   2:09876c6c806e
| |  branch:      homu
| |  parent:      0:663f5d36f850
| |  user:        monjudoh <monjudoh@gmail.com>
| |  date:        Tue Dec 13 09:51:41 2011 +0900
| |  summary:     branch homu
| |
o |  changeset:   1:eaa1adaa875b
|/   branch:      mami
|    user:        monjudoh <monjudoh@gmail.com>
|    date:        Tue Dec 13 09:42:29 2011 +0900
|    summary:     branch mami
|
o  changeset:   0:663f5d36f850
   user:        monjudoh <monjudoh@gmail.com>
   date:        Tue Dec 13 09:41:46 2011 +0900
   summary:     first commit

Gitのrebaseのようにやるには--detachを付けます。

しかし、mado branchが消えてしまいました。あんまりだよ、こんなのってないよ。

$ hg rebase -s mado -d homu --detach
saved backup bundle to /Users/monjudoh/Dropbox/example/.hg/strip-backup/917591f587ee-backup.hg

$ hg glog
@  changeset:   3:15acace3840a
|  branch:      homu
|  tag:         tip
|  user:        monjudoh <monjudoh@gmail.com>
|  date:        Tue Dec 13 09:44:03 2011 +0900
|  summary:     branch mado
|
o  changeset:   2:09876c6c806e
|  branch:      homu
|  parent:      0:663f5d36f850
|  user:        monjudoh <monjudoh@gmail.com>
|  date:        Tue Dec 13 09:51:41 2011 +0900
|  summary:     branch homu
|
| o  changeset:   1:eaa1adaa875b
|/   branch:      mami
|    user:        monjudoh <monjudoh@gmail.com>
|    date:        Tue Dec 13 09:42:29 2011 +0900
|    summary:     branch mami
|
o  changeset:   0:663f5d36f850
   user:        monjudoh <monjudoh@gmail.com>
   date:        Tue Dec 13 09:41:46 2011 +0900
   summary:     first commit

branchを保持してrebaseしたいのであれば--keepbranchesを付けましょう。そろそろoptionの長さにイライラしてきていると思いますが、ひとまずちゃんとrebase ontoに成功しました。

$ hg rebase -s mado -d homu --detach --keepbranches
saved backup bundle to /Users/monjudoh/Dropbox/example/.hg/strip-backup/917591f587ee-backup.hg

$ hg glog
@  changeset:   3:1548007f5eaa
|  branch:      mado
|  tag:         tip
|  user:        monjudoh <monjudoh@gmail.com>
|  date:        Tue Dec 13 09:44:03 2011 +0900
|  summary:     branch mado
|
o  changeset:   2:09876c6c806e
|  branch:      homu
|  parent:      0:663f5d36f850
|  user:        monjudoh <monjudoh@gmail.com>
|  date:        Tue Dec 13 09:51:41 2011 +0900
|  summary:     branch homu
|
| o  changeset:   1:eaa1adaa875b
|/   branch:      mami
|    user:        monjudoh <monjudoh@gmail.com>
|    date:        Tue Dec 13 09:42:29 2011 +0900
|    summary:     branch mami
|
o  changeset:   0:663f5d36f850
   user:        monjudoh <monjudoh@gmail.com>
   date:        Tue Dec 13 09:41:46 2011 +0900
   summary:     first commit

ただ、id:monjudoh:20110801:1312179612 で書きましたがMercurialで歴史改変を行う場合は、

元のchangesetsを保持するoptionを付けて行い、正常に出来たことを確認した上で古い方の歴史をstripで削除する

のをお勧めします。


今回の場合だと、-keep optionを付けて実行し、確認後に古いほうをstripすることになります。

$ hg rebase -s mado -d homu --detach --keepbranches --keep

正直かったるいですね。

思うにrebase ontoという操作はコマンドラインで行うには複雑すぎる操作なのではないかと思います。

本家のgit rebase --ontoはoptionこそ少ないですが、3引数の順番とか覚えてられません。

MacHGの強力な歴史改変サポート

MacHGではrebase,mq(stripのみ),collapse,histeditといった歴史改変系のextensionを強力にサポートしています。

このような履歴になっている状態で、右クリックからRebase changesets…を選択し、

f:id:monjudoh:20111213104618p:image

移動元と移動先をグラフからそれぞれ選択し、必要に応じて--keepbranches --keep相当のチェックボックスを選択しRebaseボタンを押すだけです。

f:id:monjudoh:20111213104617p:image

無事元branchを保持したrebase ontoが出来ました。

f:id:monjudoh:20111213104616p:image

stripもこのUIならまず誤爆しないでしょう。

f:id:monjudoh:20111213104615p:image

まとめ

MacHGの歴史改変操作のUIはとても素敵です。

皆様も活用してみてはどうでしょうか。

後、よくわかってない奴は歴史改変とかすんな。

では13日目の @ponkotuy さんよろしくお願いします。