プログラミング/CoffeeScript の変更点

更新


[[公開メモ]]

#contents

* CoffeeScript [#tb749906]

http://coffeescript.org/

もっと簡単に JavaScript を書きたい、という人向けに作られた言語だそうです。

最近の Ruby on Rails や [[Play Framework>ソフトウェア/PlayFramework/チュートリアル+α]] には自動的に JavaScript に変換してくれる機構が備わっているので
シームレスに使うことができます

** メリット [#sd53cdfc]

- JavaScript より簡潔に書ける
- JavaScript で気をつけなければならない点のいくつかを自動的にカバーしてくれる
- まずまず書いてて気持ちいい

** デメリット [#wac9e402]

- 最近はあまりはやっていないみたい???

*** @ はクラススコープではちゃんとクラス名になる [#tae013ba]

例えば、

 LANG:coffeescript
 class Outer
    class @Inner

という書式で、ネストクラス Outer.Inner を定義可能

このときの @ は Outer.Inner に変換される。かしこい。

恐るべきことに、

 LANG:coffeescript
 class Outer
    class this.Inner

と書いても、Outer.Inner に変換される!なんと。

** デメリット or 注意点 [#of3993bb]

- 普通の JavaScript が書きたくなくなる
- 普通の for( ; ; ) が書けないのが痛いときがある
- 最近はあまりはやっていないみたい???

*** for ... in ... と for ... of ... の見分けが付きにくい [#ae60a738]

JavaScript では for ... in ... は配列要素を列挙するための文法ではなく、
連想配列のキーを列挙するための文法なのですが、

http://d.hatena.ne.jp/amachang/20070202/1170386546

CoffeeScript では配列の列挙に使えるようになっています。

http://coffeescript.org/#loops

じゃ、連想配列のキーを列挙するにはどうするかというと、
for ... of ... なんだそうです。

わかりづらすぎる・・・

*** -> と => の違いも、気付きにくすぎ [#dade460a]

まあ、エラーが出たら => に直すというスタンスで書いておけば、
読むときは気にせず読めるから良いのかもしれないけれど???

*** 多重ループから脱出するためのラベル付き continue, break がない [#l67c096e]

https://groups.google.com/forum/#!msg/coffeescript/-LJ5OB2FNUw/UVrRdFwWdh8J

このあたりを読む限り、
CoffeeScript では JavaScript のラベルを実装する予定は無いみたいですね。

それじゃどうするかというと、~
http://stackoverflow.com/questions/7655786/breaking-continuing-nested-for-loops-in-coffeescript ~
で議論されているように、

 LANG:coffeescript
 list = ["a", "b"]
 
 `outer://`
 for i in list
     for j in [1..2]
         alert(i + j)
         `continue outer`

バッククオートでエスケープしたラベルや continue を渡してやるのが良いみたいです。

~`outer:` ではなく `outer://` となっている理由は、
上記コードを JavaScript に変換して

 LANG:javascript
 var i, j, list, _i, _j, _len;
 
 list = ["a", "b"];
 
 outer://;
 
 for (_i = 0, _len = list.length; _i < _len; _i++) {
   i = list[_i];
   for (j = _j = 1; _j <= 2; j = ++_j) {
     alert(i + j);
     continue outer;
   }
 }

となるのを見ると明らかだと思います。

outer: の後ろに ; が入るので、それをコメントアウトしているわけですね。

* 書いてみた [#wbd461e8]

** 日本の休日を求めるスクリプト(休日判定) [#p3b41c31]

まあ、車輪の再発明っぽいところもあるわけですが、
祝日名が取れて、なおかつメンテナンスしやすい形の
ライブラリが見付からなかったので書いてみました。

一応、https://gist.github.com/Songmu/703311 と突き合わせて
齟齬がないことを確認しましたが、運用は個々の責任でお願いします。

法律改正などで休日が変更になっても、よほどのことがない限り definition 
のところをいじるだけで対応できるはず???

*** 最新版はこちらです [#j372e199]

現在はこちらで公開しています。

[[ソフトウェア/javascript/japanese-holidays]]

*** ソース [#yac07e9e]

COLOR(red){SIZE(40){''以下のコードは古いです''}}

最新版はこちらで公開しています。

[[ソフトウェア/javascript/japanese-holidays]]

 LANG:coffeescript(linenumber)
 ###
 
     日本の休日を JavaScript で計算するためのライブラリ
                          Osamu Takeuchi <osamu@big.jp>
 
     ChangeLog
         2013.04.17 初出
 
     Date クラスに以下の関数を追加する
 
     **** Date::isHoliday(furikae = true)
 
     指定された日が休日かどうかを判定して、休日なら名前を返す
     休日でなければ null を返す
     furikae に false を指定すると振替休日を除く
     内部ではキャッシュした値を使って計算するため繰り返し呼ぶ
     際にはとても高速に動作する
     
     JavaScript:
 
       today = new Date();
       holiday = today.isHoliday();
       if(holiday) {
           alert("今日は " + holiday + " です<br/>");
       } else {
           alert("今日は祝日ではありません<br/>");
       }
 
 
     **** Date.getHolidaysOf(year, furikae = true)
     
     指定された年の休日を配列にして返す
     配列には {month:m, date:d, name:s} の形で表わされた休日が日付順に並ぶ
     furikae に false を指定すると、振替休日および国民の休日を除く
 
     JavaScript:
     
     today = new Date();
     holidays = Date.getHolidaysOf( today.getFullYear() );
     for(holiday in holidays) {
         document.write(
             holiday.month + "月" + holiday.date + "日は " +
             holiday.name + " です<br/>"
         );
     }
 
 
    **** Date::getShifted(year, mon, day, hour, min, sec, msec )
 
     元の時刻から指定時間だけずらした時刻を生成して返す
     負の数も指定できる
 
     d = new Date();
     d.getShifted(1);        # 1年後の時刻
     d.getShifted(0, -10);   # 10ヶ月前の時刻
     d.getShifted(0,0,0,1);  # 1時間後の時刻
 
 ###
 
 Date::getShifted = (year, mon, day, hour, min, sec, msec) ->
     # まずは日付以下の部分を msec に直して処理する
     res = new Date()
     res.setTime( @getTime() + 
         (((( day ? 0 ) * 24 + ( hour ? 0 )) * 60 + ( min ? 0 )) * 60 + 
                                 ( sec ? 0 )) * 1000 + ( msec ? 0 )
     )
     # 年と月はちょっと面倒な処理になる
     res.setFullYear res.getFullYear() + ( year ? 0 ) + 
        Math.floor( ( res.getMonth() + ( mon ? 0 ) ) / 12 )
     res.setMonth ( ( res.getMonth() + ( mon ? 0 ) ) % 12 + 12 ) % 12
     return res
 
 ###
     ヘルパ関数
 ###
 
 # 年を与えると指定の祝日を返す関数を作成
 simpleHoliday = (month, day) ->
     (year) -> new Date(year, month-1, day)
 
 
 # 年を与えると指定の月の nth 月曜を返す関数を作成
 happyMonday = (month, nth) ->
     (year) ->
         monday = 1
         first = new Date(year, month-1, 1)
         first.getShifted( 0, 0,
             ( 7 - ( first.getDay() - monday ) ) % 7 + ( nth - 1 ) * 7
         )
 
 
 # 年を与えると春分の日を返す
 shunbun = (year) ->
     date = new Date()
     date.setTime( -655910271894.040039 + 31556943676.430065 * (year-1949) + 24*3600*1000/2 )
     new Date(year, date.getMonth(), date.getDate())
 
 
 # 年を与えると秋分の日を返す
 shubun = (year) ->
     date = new Date()
     date.setTime( -671361740118.508301 + 31556929338.445450 * (year-1948) + 24.3*3600*1000/2 )
     new Date(year, date.getMonth(), date.getDate())
 
 
 ###
     休日データ
     https://ja.wikipedia.org/wiki/%E5%9B%BD%E6%B0%91%E3%81%AE%E7%A5%9D%E6%97%A5
 ###
 
 definition = [
     [ "元旦",               simpleHoliday( 1,  1), 1949       ],
     [ "成人の日",           simpleHoliday( 1, 15), 1949, 1999 ],
     [ "成人の日",           happyMonday(   1,  2), 2000       ],
     [ "建国記念の日",       simpleHoliday( 2, 11), 1967       ],
     [ "昭和天皇の大喪の礼", simpleHoliday( 2, 24), 1989, 1989 ],
     [ "春分の日",           shunbun,               1949       ],
     [ "明仁親王の結婚の儀", simpleHoliday( 4, 10), 1959, 1959 ],
     [ "天皇誕生日",         simpleHoliday( 4, 29), 1949, 1988 ],
     [ "みどりの日",         simpleHoliday( 4, 29), 1989, 2006 ],
     [ "昭和の日",           simpleHoliday( 4, 29), 2007       ],
     [ "憲法記念日",         simpleHoliday( 5,  3), 1949       ],
     [ "みどりの日",         simpleHoliday( 5,  4), 2007       ],
     [ "こどもの日",         simpleHoliday( 5,  5), 1949       ],
     [ "徳仁親王の結婚の儀", simpleHoliday( 6,  9), 1993, 1993 ],
     [ "海の日",             simpleHoliday( 7, 20), 1996, 2002 ],
     [ "海の日",             happyMonday(   7,  3), 2003       ],
     [ "敬老の日",           simpleHoliday( 9, 15), 1966, 2002 ],
     [ "敬老の日",           happyMonday(   9,  3), 2003       ],
     [ "秋分の日",           shubun,                1948       ],
     [ "体育の日",           simpleHoliday(10, 10), 1966, 1999 ],
     [ "体育の日",           happyMonday(  10,  2), 2000       ],
     [ "文化の日",           simpleHoliday(11,  3), 1948       ],
     [ "即位の礼正殿の儀",   simpleHoliday(11, 12), 1990, 1990 ],
     [ "勤労感謝の日",       simpleHoliday(11, 23), 1948       ],
     [ "天皇誕生日",         simpleHoliday(12, 23), 1989       ],
 ]
 
 
 # 休日を与えるとその振替休日を返す
 # 振り替え休日がなければ null を返す
 furikaeHoliday = (holiday) ->
     # 振替休日制度制定前 または 日曜日でない場合 振り替え無し
     sunday = 0
     if holiday < new Date(1973, 4-1, 30-1) or holiday.getDay() != sunday
         return null
     # 日曜日なので一日ずらす
     furikae = holiday.getShifted(0, 0, 1)
     # ずらした月曜日が休日でなければ振替休日
     if !furikae.isHoliday(false)
         return furikae
     # 旧振り替え制度では1日以上ずらさない
     if holiday < new Date(2007, 1-1,  1)
         return null # たぶんこれに該当する日はないはず?
     loop
         # 振り替えた結果が休日だったら1日ずつずらす
         furikae = furikae.getShifted(0, 0, 1)
         if !furikae.isHoliday(false)
             return furikae
 
 
 # 休日を与えると、翌日が国民の休日かどうかを判定して、
 # 国民の休日であればその日を返す
 kokuminHoliday = (holiday) ->
     if holiday.getFullYear() < 1988 # 制定前
         return null
     # 2日後が振り替え以外の祝日か
     if !holiday.getShifted(0, 0, 2).isHoliday(false)
         return null
     sunday = 0
     monday = 1
     kokumin = holiday.getShifted(0, 0, 1)
     if kokumin.isHoliday(false) or  # 次の日が祝日
        kokumin.getDay()==sunday or  # 次の日が日曜
        kokumin.getDay()==monday     # 次の日が月曜(振替休日になる)
         return null
     return kokumin
 
 
 #
 # holidays[furikae] = {
 #    1999:
 #      "1,1": "元旦"
 #      "1,15": "成人の日"
 #      ...
 # }
 #
 holidays = { true: {}, false: {} }
 
 getHolidaysOf = (y, furikae) ->
     # キャッシュされていればそれを返す
     furikae = if !furikae? or furikae then true else false
     cache = holidays[furikae][y]
     return cache if cache?
     # されてなければ計算してキャッシュ
     # 振替休日を計算するには振替休日以外の休日が計算されて
     # いないとダメなので、先に計算する
     wo_furikae = {}
     for entry in definition
         continue if entry[2]? && y < entry[2]   # 制定年以前
         continue if entry[3]? && entry[3] < y   # 廃止年以降
         holiday = entry[1](y)                   # 休日を計算
         continue unless holiday?                # 無効であれば無視
         m = holiday.getMonth()+1                # 結果を登録
         d = holiday.getDate()
         wo_furikae[ [m,d] ] = entry[0]
     holidays[false][y] = wo_furikae
     
     # 国民の休日を追加する
     kokuminHolidays = []
     for month_day of wo_furikae
         month_day = month_day.split(",")
         holiday = kokuminHoliday( new Date(y, month_day[0]-1, month_day[1] ) )
         if holiday?
             m = holiday.getMonth()+1            # 結果を登録
             d = holiday.getDate()
             kokuminHolidays.push([m,d])
     for holiday in kokuminHolidays
         wo_furikae[holiday] = "国民の休日"
     
     # 振替休日を追加する
     w_furikae = {}
     for month_day, name of wo_furikae
         w_furikae[month_day] = name
         month_day = month_day.split(",")
         holiday = furikaeHoliday( new Date(y, month_day[0]-1, month_day[1] ) )
         if holiday?
             m = holiday.getMonth()+1            # 結果を登録
             d = holiday.getDate()
             w_furikae[ [m,d] ] = "振替休日"
     holidays[true][y] = w_furikae               # 結果を登録
     return holidays[furikae][y]
 
 Date.getHolidaysOf = (y, furikae) ->
     # データを整形する
     result = []
     for month_day, name of getHolidaysOf(y, furikae)
         result.push(
             month : parseInt(month_day.split(",")[0])
             date  : parseInt(month_day.split(",")[1])
             name  : name
         )
     # 日付順に並べ直す
     result.sort( (a,b)-> (a.month-b.month) or (a.date-b.date) )
     result
 
 Date::isHoliday = (furikae) ->
     return getHolidaysOf(@getFullYear(), furikae)[ [@getMonth()+1, @getDate()] ]

JavaScript に直して minify したもの

 LANG:javascript_dom
 (function(){var definition;var furikaeHoliday;var getHolidaysOf;var happyMonday;var holidays;var kokuminHoliday;var shubun;var shunbun;var simpleHoliday;Date.prototype.getShifted=function(year,mon,day,hour,min,sec,msec){var res;res=new Date;res.setTime(this.getTime()+((((day!=null?day:0)*24+(hour!=null?hour:0))*60+(min!=null?min:0))*60+(sec!=null?sec:0))*1E3+(msec!=null?msec:0));res.setFullYear(res.getFullYear()+(year!=null?year:0)+Math.floor((res.getMonth()+(mon!=null?mon:0))/12));res.setMonth(((res.getMonth()+
 (mon!=null?mon:0))%12+12)%12);return res};simpleHoliday=function(month,day){return function(year){return new Date(year,month-1,day)}};happyMonday=function(month,nth){return function(year){var first;var monday;monday=1;first=new Date(year,month-1,1);return first.getShifted(0,0,(7-(first.getDay()-monday))%7+(nth-1)*7)}};shunbun=function(year){var date;date=new Date;date.setTime(-6.5591027189404E11+3.1556943676430065E10*(year-1949)+24*3600*1E3/2);return new Date(year,date.getMonth(),date.getDate())};
 shubun=function(year){var date;date=new Date;date.setTime(-6.713617401185083E11+3.155692933844545E10*(year-1948)+24.3*3600*1E3/2);return new Date(year,date.getMonth(),date.getDate())};definition=[["元旦",simpleHoliday(1,1),1949],["成人の日",simpleHoliday(1,15),1949,1999],["成人の日",happyMonday(1,2),2000],["建国記念の日",simpleHoliday(2,11),1967],["昭和天皇の大喪の礼",simpleHoliday(2,24),1989,1989],["春分の日",shunbun,1949],["明仁親王の結婚の儀",simpleHoliday(4,10),1959,1959],["天皇誕生日",simpleHoliday(4,29),1949,1988],
 ["みどりの日",simpleHoliday(4,29),1989,2006],["昭和の日",simpleHoliday(4,29),2007],["憲法記念日",simpleHoliday(5,3),1949],["みどりの日",simpleHoliday(5,4),2007],["こどもの日",simpleHoliday(5,5),1949],["徳仁親王の結婚の儀",simpleHoliday(6,9),1993,1993],["海の日",simpleHoliday(7,20),1996,2002],["海の日",happyMonday(7,3),2003],["敬老の日",simpleHoliday(9,15),1966,2002],["敬老の日",happyMonday(9,3),2003],["秋分の日",shubun,1948],["体育の日",simpleHoliday(10,10),1966,1999], ["体育の日",happyMonday(10,2),2000],["文化の日",simpleHoliday(11,3),
 1948],["即位の礼正殿の儀",simpleHoliday(11,12),1990,1990],["勤労感謝の日",simpleHoliday(11,23),1948],["天皇誕生日",simpleHoliday(12,23),1989],];furikaeHoliday=function(holiday){var furikae;var sunday;var _results;sunday=0;if(holiday<new Date(1973,4-1,30-1)||holiday.getDay()!==sunday)return null;furikae=holiday.getShifted(0,0,1);if(!furikae.isHoliday(false))return furikae;if(holiday<new Date(2007,1-1,1))return null;for(_results=[];true;){furikae=furikae.getShifted(0,0,1);if(!furikae.isHoliday(false))return furikae}return _results};
 kokuminHoliday=function(holiday){var kokumin;var monday;var sunday;if(holiday.getFullYear()<1988)return null;if(!holiday.getShifted(0,0,2).isHoliday(false))return null;sunday=0;monday=1;kokumin=holiday.getShifted(0,0,1);if(kokumin.isHoliday(false)||kokumin.getDay()===sunday||kokumin.getDay()===monday)return null;return kokumin};holidays={"true":{},"false":{}};getHolidaysOf=function(y,furikae){var cache;var d;var entry;var holiday;var kokuminHolidays;var m;var month_day;var name;var w_furikae;var wo_furikae;
 var _i;var _j;var _len;var _len2;furikae=!(furikae!=null)||furikae?true:false;cache=holidays[furikae][y];if(cache!=null)return cache;wo_furikae={};for(_i=0,_len=definition.length;_i<_len;_i++){entry=definition[_i];if(entry[2]!=null&&y<entry[2])continue;if(entry[3]!=null&&entry[3]<y)continue;holiday=entry[1](y);if(holiday==null)continue;m=holiday.getMonth()+1;d=holiday.getDate();wo_furikae[[m,d]]=entry[0]}holidays[false][y]=wo_furikae;kokuminHolidays=[];for(month_day in wo_furikae){month_day=month_day.split(",");
 holiday=kokuminHoliday(new Date(y,month_day[0]-1,month_day[1]));if(holiday!=null){m=holiday.getMonth()+1;d=holiday.getDate();kokuminHolidays.push([m,d])}}for(_j=0,_len2=kokuminHolidays.length;_j<_len2;_j++){holiday=kokuminHolidays[_j];wo_furikae[holiday]="国民の休日"}w_furikae={};for(month_day in wo_furikae){name=wo_furikae[month_day];w_furikae[month_day]=name;month_day=month_day.split(",");holiday=furikaeHoliday(new Date(y,month_day[0]-1,month_day[1]));if(holiday!=null){m=holiday.getMonth()+
 1;d=holiday.getDate();w_furikae[[m,d]]="振替休日"}}holidays[true][y]=w_furikae;return holidays[furikae][y]};Date.getHolidaysOf=function(y,furikae){var month_day;var name;var result;var _ref;result=[];_ref=getHolidaysOf(y,furikae);for(month_day in _ref){name=_ref[month_day];result.push({month:parseInt(month_day.split(",")[0]),date:parseInt(month_day.split(",")[1]),name:name})}result.sort(function(a,b){return a.month-b.month||a.date-b.date});return result};Date.prototype.isHoliday=function(furikae){return getHolidaysOf(this.getFullYear(),
 furikae)[[this.getMonth()+1,this.getDate()]]}}).call(this);

コメントやスペースをすべて除いたタイプ量を比べると
|Coffee Script| 3650 文字|
|Java Script|4751 文字|
|比率|0.768|

となって、この単純な例では Coffee Script を使うことによって
タイプ量が 3/4 程度で済んでいることになります。

*** これを使ってカレンダーを表示 [#c33aff1a]

&attachref(calendar.png);

休日セルにマウスを重ねると休日名がポップアップします。

使うときは、

 LANG:coffeescript
 target = document.getElementById("target_id");
 target.appendChild( createCalendar(2013, 3) );
 target.appendChild( createCalendar(2013, 4) );
 target.appendChild( createCalendar(2013, 5) );

などとします。

 LANG:coffeescript(linenumber)
 createElement = (tag, parent, innerHTML) ->
     child = document.createElement(tag)
     parent.appendChild(child) if parent?
     child.innerHTML = innerHTML if innerHTML?
     return child
 
 createCalendar = (year, month) ->
     calendar = createElement("div");
     calendar.className = "calendar"
     title = createElement("div", calendar);
     title.className = "title"
     a = createElement("a", title, year+"/"+month)
     a.name = "#"+year+"/"+month
     tab = createElement("table", calendar);
     d = new Date(year+"/"+month+"/1")
     i = 0
     while(i < d.getDay())
         tr = createElement("tr", tab) if (i==0)
         createElement("td", tr).className = "empty"
         i++
     while(month == d.getMonth() + 1) 
         tr = createElement("tr", tab) if (d.getDay()==0)
         td = createElement("td", tr, d.getDate())
         holiday = d.isHoliday()
         if holiday
             td.className = "holiday"
             td.setAttribute("holiday", holiday)
         d = d.getShifted(0,0,1)
     return calendar

土曜日と日曜日は css の nth-child で色を付ければ Script 
側で特別処理をしておく必要はありません。

 LANG:css
 div.calendar {
     float: left;
     padding: 5px;
 }
 
 div.calendar td {
     font-family: Arial, Helvetica, Sans-serif;
 }
 
 div.calendar div.title {
     text-align: center;
     font-size: large;
     font-weight: bold;
     background: #666;
     color: #eee;
 }
 
 div.calendar table {
     margin:auto;
 }
 
 div.calendar table tr td {
     text-align: center;
     color: grey;
 }
 
 div.calendar table tr td:nth-child(7):not(.empty):not(.holiday) {
     background: #cce;
 }
 
 div.calendar table tr td.holiday,
 div.calendar table tr td:first-child:not(.empty) {
     background: #ecc;
 }
 
 div.calendar table tr td.holiday:hover:after {
     content: attr(holiday);
     position: absolute;
     margin-top: -2em;
     background: #ecc;
     padding: 2px;
     box-shadow: 1px 1px 3px grey;
     -moz-box-shadow: 1px 1px 3px grey;
     -webkit-box-shadow: 1px 1px 3px grey;
 }

** Array::first(test=->true), Array::last(test=->true) [#t98476bd]

引数無しなら最初と最後を返すが、~
引数に (element)->Boolean を渡すと、テストが成功する物のうち、
始めと最後を返すので、検索に使える。

見付からなければ null を返す。

*** ソース [#l434f2f4]

 LANG:coffeescript
 # test を満たす最後の要素を返す
 Array::last = ( test = (e)->true ) ->
     return null if @length==0
     for i in [@length-1 .. 0]
         if test(@[i])
             return @[i]
     return null
 
 # test を満たす初めての要素を返す
 Array::first = ( test = (e)->true ) ->
     for elem in this
         if test(elem)
             return elem
     return null

** Object.is_a, Object::is_a [#l2369fea]

javascript の instanceof 演算子が壊れた仕様であることは有名ですが、

参照: http://minghai.github.io/library/coffeescript/07_the_bad_parts.html~
    の typeof の項

調べたいオブジェクトを Object() で包んでから渡してやりさえすれば、
ほぼ期待通りの動作をしてくれるのだそうです。

参照: http://webreflection.blogspot.jp/2011/10/better-isa-function-for-js.html

で、is_a という関数を定義する際、
obj.is_a(type) と書けるようにすればタイプ量が少なくて一見良さそうなのですが、
null や undefined に対して呼べなくなってしまうため、
javascript から使うには使いにくくなってしまいます。
そこで、javascript 用には Object.is_a(obj, type) という仕様の関数を用意しました。

一方 Coffee Script では ? を挟んで obj?.is_a(type) と呼べば null や undefined にも対応できるので、
Object::is_a(type) も便利に使えます。さらに、obj.is_a(type) 形式で呼び出す場合には、
is_a の呼び出し時に数値や文字列などのネイティブオブジェクトが Object にカプセル化されるため、
is_a 関数内でもう一度カプセル化してやる必要はないようです。

ただ、異なるフレームで作成されたオブジェクトに対してこの関数を呼んでも正しく判定できないようです。~
参照:http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

*** ソース [#hbe8e397]

 LANG:coffeescript
 Object::is_a = (type)->
   this instanceof type
 
 Object.is_a = (obj, type)->
   obj? and Object(obj) instanceof type

*** テスト [#p49d26a4]

 LANG:coffeescript
 # for jasmine
 describe "Object.is_a", ->
 
   it "distinguishes Number", ->
     expect( Object.is_a(1, Number) ).toBe true
     expect( Object.is_a(1, Object) ).toBe true
     expect( Object.is_a(new Number(1), Number) ).toBe true
     
   it "distinguishes String", ->
     expect( Object.is_a("a", String) ).toBe true
     expect( Object.is_a("a", Object) ).toBe true
     expect( Object.is_a(new String("a"), String) ).toBe true
   
   it "distinguishes RegExp", ->
     expect( Object.is_a(/a/, RegExp) ).toBe true
     expect( Object.is_a(/a/, Object) ).toBe true
     expect( Object.is_a(new RegExp("a"), RegExp) ).toBe true
   
   it "distinguishes Boolean", ->
     expect( Object.is_a(true, Boolean) ).toBe true
     expect( Object.is_a(true, Object) ).toBe true
     expect( Object.is_a(1==1, Boolean) ).toBe true
     expect( Object.is_a(new Boolean(true), Boolean) ).toBe true
   
   it "distinguishes Array", ->
     expect( Object.is_a([], Array) ).toBe true
     expect( Object.is_a([], Object) ).toBe true
     expect( Object.is_a(new Array([]), Array) ).toBe true
 
   it "distinguishes Object", ->
     expect( Object.is_a({}, Object) ).toBe true
   
   it "answers false for null", ->
     expect( Object.is_a(null, Object) ).toBe false
   
   it "answers false for undefined", ->
     expect( Object.is_a(undefined, Object) ).toBe false
 
   it "distinguishes with ancestors", ->
     class A
     class B extends A
     class C extends B
     expect( Object.is_a(new C, Object) ).toBe true
     expect( Object.is_a(new C, A) ).toBe true
     expect( Object.is_a(new C, B) ).toBe true
     expect( Object.is_a(new C, C) ).toBe true
     expect( Object.is_a(new B, Object) ).toBe true
     expect( Object.is_a(new B, A) ).toBe true
     expect( Object.is_a(new B, B) ).toBe true
     expect( Object.is_a(new B, C) ).toBe false
     expect( Object.is_a(new A, Object) ).toBe true
     expect( Object.is_a(new A, A) ).toBe true
     expect( Object.is_a(new A, B) ).toBe false
     expect( Object.is_a(new A, C) ).toBe false
 
   it "distinguishes objects with constructor of object", ->
     class A
     class B extends A
     o = {}
     a = new A
     b = new B
 
     expect( o.constructor ).toBe Object
     expect( a.constructor ).toBe A
     expect( b.constructor ).toBe B
     expect( o.constructor.__super__ ).toBeUndefined()
     expect( a.constructor.__super__ ).toBeUndefined()
     expect( b.constructor.__super__.constructor ).toBe A
     
     expect( Object.is_a(o, o.constructor) ).toBe true
     expect( Object.is_a(o, a.constructor) ).toBe false
     expect( Object.is_a(o, b.constructor) ).toBe false
     expect( Object.is_a(a, o.constructor) ).toBe true
     expect( Object.is_a(a, a.constructor) ).toBe true
     expect( Object.is_a(a, b.constructor) ).toBe false
     expect( Object.is_a(b, o.constructor) ).toBe true
     expect( Object.is_a(b, a.constructor) ).toBe true
     expect( Object.is_a(b, b.constructor) ).toBe true
 
  
 describe "Object::is_a", ->
 
   it "works with undefined", ->
     expect( undefined?.is_a(Object) ).toBeFalsy()
     expect( undefined_obj?.is_a(Object) ).toBeFalsy()
     `var uninitialized_obj`
     expect( uninitialized_obj?.is_a(Object) ).toBeFalsy()
 
   it "works with null", ->
     expect( null?.is_a(Object) ).toBeFalsy()
   
   it "works with literal objects", ->
     expect( 0 instanceof Number      ).toBeFalsy()
     expect( NaN instanceof Number    ).toBeFalsy()
     expect( 0?.is_a(Number)          ).toBeTruthy()
     expect( NaN?.is_a(Number)        ).toBeTruthy()
     expect( "a" instanceof String    ).toBeFalsy()
     expect( "a"?.is_a(String)        ).toBeTruthy()
     expect( /a/ instanceof RegExp    ).toBeTruthy()
     expect( /a/?.is_a(RegExp)        ).toBeTruthy()
     expect( false instanceof Boolean ).toBeFalsy()
     expect( (0==0) instanceof Boolean).toBeFalsy()
     expect( false?.is_a(Boolean)     ).toBeTruthy()
     expect( (0==0)?.is_a(Boolean)    ).toBeTruthy()
     expect( [] instanceof Array      ).toBeTruthy()
     expect( []?.is_a(Array)          ).toBeTruthy()
     expect( {} instanceof Object     ).toBeTruthy()
     expect( {}?.is_a(Object)         ).toBeTruthy()
 
   it "works with the super class", ->
     class A
     class B extends A
     class C extends B
     a = new A
     b = new B
     c = new C
     expect( c?.is_a(Object) ).toBeTruthy()
     expect( c?.is_a(A) ).toBeTruthy()
     expect( c?.is_a(B) ).toBeTruthy()
     expect( c?.is_a(C) ).toBeTruthy()
     expect( b?.is_a(Object) ).toBeTruthy()
     expect( b?.is_a(A) ).toBeTruthy()
     expect( b?.is_a(B) ).toBeTruthy()
     expect( b?.is_a(C) ).toBeFalsy()
     expect( a?.is_a(Object) ).toBeTruthy()
     expect( a?.is_a(A) ).toBeTruthy()
     expect( a?.is_a(B) ).toBeFalsy()
     expect( a?.is_a(C) ).toBeFalsy()


** Object.class_name_of [#e877d780]

こちらも null や undefined に対応するため、Object.class_name_of(obj) の形になっています。

http://webreflection.blogspot.jp/2007/01/javascript-getclass-and-isa-functions.html
のコードはたぶん意図と異なるコードになっているので、気持ちを汲み取って書き直してみました。

http://api.jquery.com/jQuery.type/ ~
http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

このあたりを見ると Function.prototype.toString() は実装依存なため下記コードは
必ずしもすべてのブラウザで動かない可能性もあるのですが、とりあえず試した範囲では大丈夫そう、
という感じです。

*** ソース [#rabfc8d7]

 LANG:coffeescript
 root = exports ? this
 
 Object.class_name_of = (obj)->
   return "null" if obj == null
   return "undefined" if obj == undefined
   if obj.constructor.toString().match /function\s+(\S+)\s*\(/
     return RegExp.$1
   if obj.constructor.prototype?
     # search constructor in the global scope
     for key, value of root
       if value?.prototype? and
          obj instanceof value
         return key
   return "anonymous"

*** 単純版のソース [#m8135b57]

上記のコードがややこしい理由は、

 LANG:javascript
 function MyClass(){};
 obj = new MyClass();
 alert(Object.class_name_of(obj));

ではなく、

 LANG:javascript
 MyClass = function(){};  // グローバルに宣言された変数
 obj = new MyClass();
 alert(Object.class_name_of(obj));

とした場合にも名前を判別できるよう工夫しているためです。
(たぶん、リンク先ではそのあたりのコードがおかしくなっているように思えるのですが・・・)

その際、グローバルオブジェクトのプロパティをスキャンしているため、
この関数を無名クラスのオブジェクトに対して頻繁に呼び出すと、
パフォーマンス的に問題が生じる可能性があります。

Coffee Script を使っている限り
上記のような無名クラスのオブジェクトを作成することはほとんどありませんし、
そもそも上のように頑張ったところで、

 LANG:javascript
 var MyClass = function(){};  // ローカルに宣言された変数
 obj = new MyClass();
 alert(Object.class_name_of(obj));

こういう場合にはうまく行きません。

そこで無名関数から作成されたオブジェクトに対して "anonymous" を返して構わないことにすれば、
if obj.constructor.prototype? の部分を除いた次のコードで良いことになります。

 LANG:coffeescript
 Object.class_name_of = (obj)->
   return "null" if obj == null
   return "undefined" if obj == undefined
   if obj.constructor.toString().match /function\s+(\S+)\(/
     return RegExp.$1
   return "anonymous"

*** テスト [#v74d0bf8]

 LANG:coffeescript
 # for jasmine
 root = exports ? this
 
 describe "Object.class_name_of", ->
 
   it "distinguishes null", ->
     expect( Object.class_name_of(null) ).toBe "null"
 
   it "distinguishes undefined", ->
     expect( Object.class_name_of(undefined) ).toBe "undefined"
     a = {}
     expect( Object.class_name_of(a["abc"]) ).toBe "undefined"
 
   it "distinguishes Object", ->
     expect( Object.class_name_of({}) ).toBe "Object"
     expect( Object.class_name_of(new Object()) ).toBe "Object"
 
   it "distinguishes Number", ->
     expect( Object.class_name_of(1) ).toBe "Number"
     expect( Object.class_name_of(new Object(1)) ).toBe "Number"
     expect( Object.class_name_of(Number(1)) ).toBe "Number"
     expect( Object.class_name_of(new Number(1)) ).toBe "Number"
 
   it "distinguishes Array", ->
     expect( Object.class_name_of([]) ).toBe "Array"
     expect( Object.class_name_of(new Object([])) ).toBe "Array"
     expect( Object.class_name_of(new Array()) ).toBe "Array"
 
   it "distinguishes RegExp", ->
     expect( Object.class_name_of(/a/) ).toBe "RegExp"
     expect( Object.class_name_of(new Object(/a/)) ).toBe "RegExp"
     expect( Object.class_name_of(new RegExp("a")) ).toBe "RegExp"
 
   it "distinguishes Boolean", ->
     expect( Object.class_name_of(true) ).toBe "Boolean"
     expect( Object.class_name_of(new Object(true)) ).toBe "Boolean"
     expect( Object.class_name_of(1==0) ).toBe "Boolean"
     expect( Object.class_name_of(new Boolean(true)) ).toBe "Boolean"
 
   it "distinguishes User class", ->
     class A
     expect( Object.class_name_of(A) ).toBe "Function"
     expect( Object.class_name_of(new A) ).toBe "A"
     class B extends A
     expect( Object.class_name_of(B) ).toBe "Function"
     expect( Object.class_name_of(new B) ).toBe "B"
 
   it "finds constructor stored in a global variable", ->
     root.global_func = ->
     expect( Object.class_name_of( new global_func() ) ).toBe "global_func"
   
 #  for simplified version
 #
 #  it "does not find constructor stored in a global variable", ->
 #    root.global_func = ->
 #    expect( Object.class_name_of( new global_func() ) ).toBe "anonymous"
    
   it "does not find constructor stored in a local variable", ->
     local_func = ->
     expect( Object.class_name_of( new local_func() ) ).toBe "anonymous"
 
   it "returns variety of names for DOM objects depending on the environment", ->
     expect( Object.class_name_of( window ) ).toMatch /^(Window|constructor|anonymous)$/
     expect( Object.class_name_of( document ) ).toMatch /^(HTMLDocument|XMLDocument|anonymous)$/
     expect( Object.class_name_of( location ) ).toMatch /^(Location|Object|anonymous)$/
 
   it "can find super class via __super__ for Coffee Script' classes", ->
     class A
     class B extends A
     a = new A
     b = new B
 
     cn_of = Object.class_name_of
     expect(cn_of                                   A           ).toBe "Function"
     expect(                                        A.__super__ ).toBeUndefined()
     expect(                            a.constructor           ).toBe A
     expect(cn_of                       a.constructor           ).toBe "Function"
     expect(                            a.constructor.__super__ ).toBeUndefined()
     expect(cn_of                       a                       ).toBe "A"
     expect(cn_of             B                                 ).toBe "Function"
     expect(cn_of             B.__super__                       ).toBe "A"
     expect(                  B.__super__.constructor           ).toBe A
     expect(                  B.__super__.constructor.__super__ ).toBeUndefined()
     expect(      b.constructor                                 ).toBe B
     expect(cn_of b.constructor                                 ).toBe "Function"
     expect(cn_of b.constructor.__super__                       ).toBe "A"
     expect(      b.constructor.__super__.constructor           ).toBe A
     expect(      b.constructor.__super__.constructor.__super__ ).toBeUndefined()
 
   it "returns only the last part of nested names", ->
     class A
     class A.B
     expect(Object.class_name_of new A.B).toBe "B"
 
   it "returns dummy name of a named function expression", ->
     # http://bonsaiden.github.io/JavaScript-Garden/#function.general
     C = `function D(){}`
     expect(Object.class_name_of new C).toBe "D"
 
   it "faces some problems with IE8", ->
     cn_of = Object.class_name_of
     # this is OK
     expect(cn_of new Error()          ).toBe "Error" 
     # IE8 returns "anonymous" for these Error objects
     expect(cn_of new EvalError()      ).toMatch "anonymous|EvalError" 
     expect(cn_of new TypeError()      ).toMatch "anonymous|TypeError"
     expect(cn_of new RangeError()     ).toMatch "anonymous|RangeError"
     expect(cn_of new ReferenceError() ).toMatch "anonymous|ReferenceError"
     expect(cn_of new SyntaxError()    ).toMatch "anonymous|SyntaxError"
     expect(cn_of new URIError()       ).toMatch "anonymous|URIError"

* コメント・質問 [#k42bd4cf]

#article_kcaptcha
**春分、秋分の計算式について [#zf02f2f0]
>[じぞう] (2013-11-23 (土) 22:44:27)~
~
初めまして、ネット検索でこのページを見つけました。~
~
題名のとおり祝日判定したくいろいろと探しておりまして~
作者様のソースでわからないところがあり質問させていた~
だきます。~
~
春分、秋分の計算式(setTime)のところ(それぞれの数値が何なのか)~
について解説いただけないでしょうか。~
~
ほとんど解説をお願いしてしまうことになり恐縮ですが~
よろしくお願いします。~

//
- ご存じの通り春分、秋分の日は年によって日付が異なります。これは、1年が正確に365日ではなく、その差を埋めるために閏年が挟まるためです。実のところ、春分、秋分はほぼ正確に等間隔でやってくるのですが、カレンダーの方が閏年分だけ等間隔にならず、そのために日付が異なることになります。
~Wikipedia の[[春分>http://ja.wikipedia.org/wiki/%E6%98%A5%E5%88%86]]・[[秋分>http://ja.wikipedia.org/wiki/%E7%A7%8B%E5%88%86]]のページに 1900年〜2099年のデータがあって、そこから春分・秋分の周期を求めると 約31556900000ミリ秒 = 約365.2423 日 でした。この周期を使って春分・秋分を計算する式が上記になります。この式で 少なくともここ200年くらいの日付が再現できていたと思います。~
ただ今見ると、同じページの 2001〜2014年の、時刻まで出ている表を使う方がより正確な式を作れそうですね。100年以上先のカレンダーが必要になることは少なそうなのであまり気にしなくても良いのでしょうけれど。 -- [武内(管理人)] &new{2013-11-28 (木) 00:06:40};

#comment_kcaptcha

Counter: 7815 (from 2010/06/03), today: 3, yesterday: 5