Javascriptのオブジェクト指向

Javascriptオブジェクト指向は気持ち悪い!」
みたいな意見を聞くことがあるけれど、
そういうのはたいてい慣れの問題だと思っています。


「オレオレオブジェクト指向」を作って使っちゃうのも、
自分ひとりだけならいいけど多人数が見るコードでやるのは迷惑かなと。
だいたい「オレオレクラス」を分かりやすいと思っているのは作った人だけだったり。。。


でもJavascriptオブジェクト指向に問題があるのは事実だし
対策は必要なので自分がベストだと思っている方法を書いておきます。


newをつけなくても呼び出せちゃう問題

function Point(x, y){
    this.x = x;
    this.y = y;
}

var o = new Point(0, 0); // 問題なし
var p1 = Point(1, 1); // p1はundefinedだし、グローバルオブジェクトが汚染される!

この場合、Pointコンストラクタの一行目に

    console.assert(this instanceof Point, 'newをつけずに呼び出された可能性があります');

みたいに書いておくのがいいと思います。
Node.jsではエラーになるし、ブラウザでもコンソールを見ればわかります。

この問題への対策として

    if(!(this instanceof Point)) return new Point(x, y);

みたいなコードも見るけれどあまりよくないです。
根本的な解決(newを付けてコールさせるべき)になっていないうえに、
可変長の引数を取る場合Function.prototype.bindを使う必要があって面倒臭さいです。


prototype継承とインスタンスの問題

問題のないprototypeチェーンを作るにはこのようにする必要があります。
毎回こんなことをやるのは面倒です。
だからといってオレオレクラスを作るのはよくないので、
コンストラクタに何もさせないことにします。
コンストラクタが何もしないのならば周りくどいことをする必要はありません。

function Point(){
    console.assert(this instanceof Point, 'newをつけずに呼び出された可能性があります');
}

Point.prototype.init = function(x, y){
    // 初期化はこの関数で
    this.x = x;
    this.y = y;
}

Point2.prototype = new Point();
function Point2(){
    Point.call(this);
}

var p1 = new Point();
p1.init(1, 2); // 初期化

var p2 = new Point2();
p2.init(1, 2); // 初期化

これはこれでデメリットがないとは言い切れませんが、大きな問題はないと思います。
DOMの操作で似たようなことをしますし。おすし。

var event = document.createEvent('Events');
event.initEvent('eventtype', false, false); // 初期化

ぶっちゃけprototypeって打つのメンドクサイ問題

Object.definePropertiesを使えるならば使いましょう。
prototypeって打つのは1回だけだし、for( in ) で列挙されないようにできます。
書き換え不能にもできます。
一石三鳥です。

Object.defineProperties(Point.prototype, {
    init: {
        value: function(x, y){
            this.x = x;
            this.y = y;
        }
    },
    distance: {
        value: function(otherPoint){
            var x = otherPoint.x - this.x, y = otherPoint.y - this.y;
            return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        }
    }
});

でも、そもそもfunctionって打つのも面倒なので、
根本的に解決したいなら補完してくれるエディタを使うべきだったり。(Aptanaとか)


まとめ

「オレオレオブジェクト指向」で作ったコードを読めるのは「今」の自分だけかもしれません。
すみやかにやめたらいいと思います。
どうしても素のJavaScriptが嫌なら「traceur」みたいなものがあるんだから、
そっちを使えばいいんじゃないでしょうか。

IE9RCを試してみた

IE9がRCになったようなので開発者ツールとかを軽く試してみた。

Javascript

>> Object.defineProperty 
 function defineProperty() {     [native code] }  
ウォッチ対象に追加
>> Object.defineProperties 
 function defineProperties() {     [native code] }  
ウォッチ対象に追加
>> [] 
{...} 
ウォッチ対象に追加
>> [].map 
 function map() {     [native code] }  
ウォッチ対象に追加
>> [].filter 
 function filter() {     [native code] }  
ウォッチ対象に追加
>> [].forEach 
 function forEach() {     [native code] }  
ウォッチ対象に追加
>> window.addEventListener 
 function addEventListener() {     [native code] }  
ウォッチ対象に追加
>> document.addEventListener('click', function(){console.log('clicked')}, false) 
ログ: clicked 
ログ: clicked 
ログ: clicked 

consoleとか、よく使う関数とかが定義されていて嬉しい。
__defineGetter__とかは使えないみたいだけど
Object.defineProperty()があるから問題ない。

css

border-radiusとか使えた。
-ms-transformはあったっけどtransitionはないっぽい。

まとめ

HTMLを書く側としてはいろいろ楽になるんじゃないかと思う。
どうなるかは、まだわからんけど。
Vista SP2以降じゃないと使えないから、XPを使っている人はIE8を使い続ける事になるし。
XP邪魔。。。


あと、バレンタインチョコ欲しい!
欲しいのはiPod touch。(一番欲しいのはMacbook Airだけど)
はてなさんじゃなくていいから誰かください。

Gitのコマンドにエイリアスを設定した

gitのコマンドってやたらと長い物が多い。
(checkoutとかbranchとかcherry-pickとか)


長いと打ち間違えも多くなるし、どうにかならないかと思って調べたら、
エイリアスを設定できることがわかった。
もしかして知らなかったのは自分だけ?

git config --global alias.co checkout

ってやっておくと

git co master

でmasterブランチにチェックアウトできる。
タイプ数が6文字減っている。
1日1回checkoutする人なら、なんと1年で2190文字もタイプ数が減る。


とりあえず、よく使うものを設定しておいた。

git config --global alias.co checkout
git config --global alias.lo "log --oneline --reverse"
git config --global alias.pick cherry-pick

でもあんまり短すぎても、よくわかんなくなるし程々にしたほうがよさそう。

Javascriptでいい感じにプロトタイプを継承する

Javascriptでプロトタイプを継承するには

function newClass(){
  superClass.apply(this, arguments);
}
newClass.prototype = new superClass(); // superClassを継承

var newObject = new newClass();
console.log(newObject instanceof superClass); // true

こんなコードを書けばいいわけだけど、
これだとsuperClassオブジェクトを作っているところがスマートじゃない。

newClass.prototype = superClass.prototype; // superClassを継承

これだと余計なオブジェクトはつくらないけど、
newClassのprototypeを拡張するとsuperClassのprototypeも拡張されてしまう。


というわけで

newClass.prototype = (function(C){
  C.prototype = superClass.prototype; // superClassを継承
  return new C();
})(function(){});

こんな書き方をする必要がある。


さらに今はやり(?)のObject.definePropertiesを使って

newClass.prototype = Object.defineProperties((function(C){
  C.prototype = superClass.prototype; // superClassを継承
  return new C();
})(function(){}), {
  methodA: {
    value: function(){
      // methodA
    }
  }
});

とするとfor(〜 in 〜)したときに余計なプロパティを列挙しなくなる。
Chromeで、すでに使用可能。
拡張を作るときに活用できる。


ただしArrayの拡張はこれでもうまくいかない。

myArray = new MyArray();
myArray[100] = 100;

と、代入したときにlengthが101にならない等の問題がある。
たぶん完全にArrayを拡張する方法は(いまのところ)ない。

擬似要素でアニメーションが効かない

:beforeや:afterなどの擬似要素に
-webkit-transitionとか-webkit-animationが効かない。
仕様だと効くはずなのでそのうち出来るようになるんだろうけど少し困った。


あとMacBook Air 11インチ欲しい!

Python + goo.gl でURLを短縮する

この記事が元ネタ。なぜか全然動かないコードだったので勝手に修正。
動作確認はWindows Vista + Python 2.5。
simplejsonがないとImportErrorって言われて怒られると思います。

googl.py
#!/usr/bin/env python

def shorten(url):
  from urllib2 import urlopen, Request, HTTPError
  from urllib import quote
  from simplejson import loads
  try:
    req = Request('http://goo.gl/api/url', 'url=%s' % quote(url))
    try: urlopen(req)
    except HTTPError, e:
      if e.code == 201: res = e.read()
      else: raise
    j = loads(res)
    return j['short_url']
  except: raise Exception('Unknown eror forming short URL.')

if __name__ == '__main__':
  from sys import argv
  print shorten(argv[1])

使いかた

python googl.py 」っていうコマンドを実行する。
もしくはPythonスクリプトからshorten関数をインポートして「shorten(url)」。