プログラミング/CoffeeScript の履歴(No.15)
更新CoffeeScript†
もっと簡単に JavaScript を書きたい、という人向けに作られた言語だそうです。
最近の Ruby on Rails や Play Framework には自動的に JavaScript に変換してくれる機構が備わっているので シームレスに使うことができます
メリット†
- JavaScript より簡潔に書ける
- JavaScript で気をつけなければならない点のいくつかを自動的にカバーしてくれる
- まずまず書いてて気持ちいい
@ はクラススコープではちゃんとクラス名になる†
例えば、
LANG:coffeescript class Outer class @Inner
という書式で、ネストクラス Outer.Inner を定義可能
このときの @ は Outer.Inner に変換される。かしこい。
恐るべきことに、
LANG:coffeescript class Outer class this.Inner
と書いても、Outer.Inner に変換される!なんと。
デメリット or 注意点†
- 普通の JavaScript が書きたくなくなる
- 普通の for( ; ; ) が書けないのが痛いときがある
for ... in ... と for ... of ... の見分けが付きにくい†
JavaScript では for ... in ... は配列要素を列挙するための文法ではなく、 連想配列のキーを列挙するための文法なのですが、
http://d.hatena.ne.jp/amachang/20070202/1170386546
CoffeeScript では配列の列挙に使えるようになっています。
http://coffeescript.org/#loops
じゃ、連想配列のキーを列挙するにはどうするかというと、 for ... of ... なんだそうです。
わかりづらすぎる・・・
-> と => の違いも、気付きにくすぎ†
まあ、エラーが出たら => に直すというスタンスで書いておけば、 読むときは気にせず読めるから良いのかもしれないけれど???
多重ループから脱出するためのラベル付き continue, break がない†
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: の後ろに ; が入るので、それをコメントアウトしているわけですね。
書いてみた†
日本の休日を求めるスクリプト(休日判定)†
まあ、車輪の再発明っぽいところもあるわけですが、 祝日名が取れて、なおかつメンテナンスしやすい形の ライブラリが見付からなかったので書いてみました。
現在はこちらで公開しています。 ソフトウェア/javascript/japanese-holidays
一応、https://gist.github.com/Songmu/703311 と突き合わせて 齟齬がないことを確認しましたが、運用は個々の責任でお願いします。
法律改正などで休日が変更になっても、よほどのことがない限り definition のところをいじるだけで対応できるはず???
ソース†
以下のコードは古いです
ソフトウェア/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 程度で済んでいることになります。
これを使ってカレンダーを表示†
休日セルにマウスを重ねると休日名がポップアップします。
使うときは、
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)†
引数無しなら最初と最後を返すが、
引数に (element)->Boolean を渡すと、テストが成功する物のうち、
始めと最後を返すので、検索に使える。
見付からなければ null を返す。
ソース†
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†
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/
ソース†
LANG:coffeescript Object::is_a = (type)-> this instanceof type Object.is_a = (obj, type)-> obj? and Object(obj) instanceof type
テスト†
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†
こちらも 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() は実装依存なため下記コードは 必ずしもすべてのブラウザで動かない可能性もあるのですが、とりあえず試した範囲では大丈夫そう、 という感じです。
ソース†
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"
単純版のソース†
上記のコードがややこしい理由は、
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"
テスト†
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"
コメント・質問†
春分、秋分の計算式について†
[じぞう] (2013-11-23 (土) 22:44:27)
初めまして、ネット検索でこのページを見つけました。
題名のとおり祝日判定したくいろいろと探しておりまして
作者様のソースでわからないところがあり質問させていた
だきます。
春分、秋分の計算式(setTime)のところ(それぞれの数値が何なのか)
について解説いただけないでしょうか。
ほとんど解説をお願いしてしまうことになり恐縮ですが
よろしくお願いします。
- ご存じの通り春分、秋分の日は年によって日付が異なります。これは、1年が正確に365日ではなく、その差を埋めるために閏年が挟まるためです。実のところ、春分、秋分はほぼ正確に等間隔でやってくるのですが、カレンダーの方が閏年分だけ等間隔にならず、そのために日付が異なることになります。
Wikipedia の春分・秋分のページに 1900年〜2099年のデータがあって、そこから春分・秋分の周期を求めると 約31556900000ミリ秒 = 約365.2423 日 でした。この周期を使って春分・秋分を計算する式が上記になります。この式で 少なくともここ200年くらいの日付が再現できていたと思います。
ただ今見ると、同じページの 2001〜2014年の、時刻まで出ている表を使う方がより正確な式を作れそうですね。100年以上先のカレンダーが必要になることは少なそうなのであまり気にしなくても良いのでしょうけれど。 -- [武内(管理人)]