ko.utils(その他)その2
これはKnockoutJSアドベントカレンダー10日目の記事です。
KnockoutJS Advent Calendar 2014 - Qiita
今日もまた Knockout の便利機能が入った ko.utils について適当に紹介します。
まだまだあるよ!
今日紹介するのは以下のメソッド。
- ko.utils.domData.clear
- ko.utils.domNodeDisposal.cleanNode / ko.utils.domNodeDisposal.removeNode
- ko.utils.domNodeDisposal.addDisposeCallback
- ko.utils.parseHtmlFragment
- ko.utils.setHtml
- ko.utils.compareArrays
- ko.utils.setDomNodeChildrenFromArrayMapping
内部的に使われているのが殆どなので、実際に使う機会はほとんどないと思いますが気にしないでいきましょう。
※たぶん使うとしたら ko.utils.domNodeDisposal.addDisposeCallback ぐらい?
- ko.utils.domData.clear
ko.utils.domData というオブジェクトが持っているメソッドです。
clearだけじゃなくて set/get/nextKey があります。
elementオブジェクトに対してkoで使う内部的なデータを保持しています。
ko.utils.domData.clear だけ独立して exportSymbol してるのはテストとかで内部的な値をクリアするときに使うからっぽいです。
内部的なやつなので殆ど使うことはないでしょう。
サンプル
<div id="sample"></div> <script> var output = null; ko.utils.domData.set(document.getElementById("sample"), "foo", "bar"); output = ko.utils.domData.get(document.getElementById("sample"), "foo"); console.log(output); ko.utils.domData.clear(document.getElementById("sample")); output = ko.utils.domData.get(document.getElementById("sample"), "foo"); console.log(output); </script>
結果
bar undefined
- ko.utils.domNodeDisposal.cleanNode / ko.utils.domNodeDisposal.removeNode
それぞれ ko.cleanNode と ko.removeNode です。
ko.cleanNodeは ko.applyBindings で ViewModelとDOM要素とバインディングしたデータをクリアできます。
例えば以下のような HTML を用意します。
<input id="sample1" type="text" data-bind="textInput: foo" /> <span id="sample2" data-bind="text: bar"></span> <script> function ViewModel(){ var self = this; self.foo = ko.observable(); self.bar = ko.pureComputed(function(){ return self.foo(); }); } ko.applyBindings(new ViewModel()); </script>
ここで ID=sample1 の入力フィールドに値をいれるとID=sample2 の値も同時に変わります。
次に、(Chromeの場合)デベロッパーコンソールの Console を開いて以下を入力してみます。
ko.cleanNode(document.getElementById("sample2"));
するとどうでしょう。
さっきまで反応していた ID=sample2 が入力フィールドに何を入れても反応しなくなりました。
今度は Console に以下を入力してみましょう。
ko.removeNode(document.getElementById("sample1"));
今度は入力フィールドが削除されました。
Knockoutを使ってバインディングされた要素はこのように removeNode で削除してください。
これをしないで単純にDOM要素を削除してしまうとメモリリークの原因になるので要注意です。
※このメソッドは要素を削除する前に内部でko.cleanNodeを呼び出して要素のデータをクリアしています。
- ko.utils.domNodeDisposal.addDisposeCallback
説明は公式ドキュメントにあります。
http://knockoutjs.com/documentation/custom-bindings-disposal.html
公式ドキュメントにもあるようにこのメソッドはカスタムバインディングハンドラでよく使用します。
pureComputedで使ったサンプルを使用してこれに独自のカスタムバインディングを作って使ってみましょう。
<!--ko if: step() == 0--> <p>First name: <input data-bind="textInput: firstName" /></p> <!--/ko--> <!--ko if: step() == 1--> <p>Last name: <input data-bind="textInput: lastName, hoge" /></p> <!--/ko--> <!--ko if: step() == 2--> <div>Prefix: <select data-bind="value: prefix, options: ['Mr.', 'Ms.','Mrs.','Dr.']"></select></div> <h2>Hello, <span data-bind="text: fullName"> </span>!</h2> <!--/ko--> <p><button type="button" data-bind="click: next">Next</button></p>
ko.bindingHandlers.hoge = { init: function(element, valueAccessor) { alert("init"); ko.utils.domNodeDisposal.addDisposeCallback(element, function() { alert("dispose"); }); } }; function AppData() { this.firstName = ko.observable('John'); this.lastName = ko.observable('Burns'); this.prefix = ko.observable('Dr.'); this.fullName = ko.pureComputed(function () { var value = this.prefix() + " " + this.firstName() + " " + this.lastName(); return value; }, this); this.step = ko.observable(0); this.next = function () { this.step(this.step() === 2 ? 0 : this.step()+1); }; }; ko.applyBindings(new AppData());
hoge というバインディングハンドラを作り2つめの画面のテキストフィールドに適用します。
サンプル
http://jsfiddle.net/tan_go238/86vxks2u/1/
最初の画面からNextボタンを押して、2つめの画面が呼び出されたときに hoge のバインディングハンドラが初期化されます。
そのページからNextボタンを押して次のページに移動しようとしたときにバインディングハンドラが破棄されます。
上記のサンプルでは、初期化、破棄のそれぞれタイミングでアラートダイアログが表示されます。
- ko.utils.parseHtmlFragment
文字列のHTMLコードからDOM要素を作成して返します。jQueryが使える場合は jQuery.parseHTMLを呼び出します。
knockoutは"jQueryが使えれば jQuery を使う、でなければ自前のやつを使う" みたいな実装のところがちらほらあるので、
KnockoutJSを使うときは jQuery も一緒に使うとより安心して使えると思います。
- ko.utils.setHtml
ko.utils.setHtml(node, html)
第二引数で渡したHTMLコードをDOM要素にして第一引数に渡したノードにセットします。
jQueryがある場合は $.html(htmlString) を使用します。
ない場合は ko.utils.parseHtmlFragment を使いDOM要素を作成してappendChildしていきます。
- ko.utils.compareArrays
配列同士を比較します。
結果は Objectの配列になります。
Objectのプロパティには status と value があり、valueは実際の値、statusは3つの状態("added", "retained", "deleted")のいずれかになります。
var foo = ["aaa", "bbb", "ccc"]; var bar = ["bbb", "ddd", "eee"]; var differences = ko.utils.compareArrays(foo, bar); var addedValues = []; var retainedValues = []; var deletedValues = []; ko.utils.arrayForEach(differences, function (difference) { if (difference.status === "added") { addedValues.push(difference.value); } else if (difference.status === "retained") { retainedValues.push(difference.value); } else if (difference.status === "deleted") { deletedValues.push(difference.value); } }); console.log(addedValues); console.log(retainedValues); console.log(deletedValues);
結果
["ddd", "eee"] ["bbb"] ["aaa", "ccc"]
- ko.utils.setDomNodeChildrenFromArrayMapping
option バインディングや foreach バインディングに使用しています。
子要素が追加されたり削除されたりしたときにコールバックを呼んだり削除時にko.removeNodeを呼んだりと色々なことをしてくれます。
3回に分けてざっと ko.utils のメソッドやオブジェクトを紹介してきましたが、
まだあると思うので探してみるといいかと思います。
3.1までのバージョンなら以下に一覧があります。
※一部ko内部でしか使えないものもあり
KnockoutJS 3.1.0 utils (ko.utils) signatures
明日は @sukobuto さんの「KO + TypeScript で大規模 SPA 開発」です!
よろしくお願いします!
ko.utils(その他)
これはKnockoutJSアドベントカレンダー8日目の記事です。
KnockoutJS Advent Calendar 2014 - Qiita
今日は Knockout の便利機能が入った ko.utils について適当に紹介します。
前回は配列だったのでそれ以外を紹介します。
ソースコードはここ。
https://github.com/knockout/knockout/blob/master/src/utils.js
紹介するのは以下のメソッドです。
- ko.utils.extend
- ko.utils.fieldsIncludedWithJsonPost
- ko.utils.getFormFields
- ko.utils.peekObservable
- ko.utils.postJson
- ko.utils.postJson
- ko.utils.parseJson
- ko.utils.stringifyJson
- ko.utils.range
- ko.utils.registerEventHandler
- ko.utils.triggerEvent
- ko.utils.unwrapObservable
- ko.utils.objectForEach
- ko.utils.toggleDomNodeCssClass
- ko.utils.addOrRemoveItem
それでは順番にいきましょう。
- ko.utils.extend
Objectを拡張します。
サンプル
var target = {a:1, b:2} var option = {d:3, e:4, f:5, a:6} var result = ko.utils.extend(target, option); console.log(result);
結果
Object {a: 1, b: 2, d: 3, e: 4, f: 5}
- ko.utils.fieldsIncludedWithJsonPost
これはメソッドではなく配列です。あとで出てくる ko.utils.postJson のデータを送信するときに自動的に含めるフィールド名が入ってます。
fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
軽くググった感じだとRailsのCSRF対策で自動的に埋め込まれるトークン用、もう一つは ASP.NETでリクエストの検証トークン用っぽいです。
- ko.utils.getFormFields
フォームから指定した文字列もしくは正規表現に一致するフィールドを配列で返します。
HTML
<form id="form1" action="/" method="post"> <input type="text" name="f1" value="" /> <input type="checkbox" name="f2" value="" /> </form>
var form = document.getElementById("form1"); var result1 = ko.utils.getFormFields(form, "f2"); console.log("result1 length: " + result1.length); console.log("result1: " + result1[0].getAttribute("name")); var result2 = ko.utils.getFormFields(form, /f[12]/); console.log("result2 length: " + result2.length); console.log("result2: " + result2[0].getAttribute("name")); console.log("result2: " + result2[1].getAttribute("name"));
結果
result1 length: 1 result1: f2 result2 length: 2 result2: f2 result2: f1
- ko.utils.peekObservable
ko.observableの peek() と同じです。
<div id="peekObservable"> <p>ko.utils.peekObservable</p> <form id="form1" action="/" method="post"> <input type="text" name="f1" value="" data-bind="value: input1"/> <input type="text" name="f2" value="" data-bind="value: input2"/> <p data-bind="text: result"></p> <input type="checkbox" name="f3" value="1" data-bind="checked: input3" id="chk"/><label for="chk">ここを押せば更新される</label> </form> <script> function ViewModel() { var self = this; self.input1 = ko.observable(); self.input2 = "Input 2"; self.input3 = ko.observable(false); self.result = ko.pureComputed(function(){ var i1 = ko.utils.peekObservable(self.input1); // self.input.peek() と同じ var i2 = ko.utils.peekObservable(self.input2); // observableじゃなくても使える(無視される) var checked = self.input3(); return i1 + " - " + i2 + " " + checked; }); } ko.applyBindings(new ViewModel(), document.getElementById('peekObservable')); </script> </div>
- ko.utils.postJson
ko.utils.postJson(urlOrForm, data, options)
urlOrForm
URLかフォーム要素をいれる
data
送信するデータ。Modelでもいいけどprototypeで継承してきたプロパティも送信するってソースに書いてる
送信する値は ko.utils.stringifyJson で JSON化される
options
オブジェクトで指定する。配列でもいけるかもしれないけど試してない。
指定できるオプションは次のとおり
params
送信するパラメータ。dataとほぼ一緒だけどこちらは JSON化しない
includeFields
送信する対象のフィールドを配列で指定する
指定がない場合は自動的に以下のフィールドが指定される(CSRF対策のため)
authenticity_token
__RequestVerificationToken_ から始まるフィールド
submitter
これに内部で作成したフォームを引数として渡して実行する
<form id="sendForm" action="/"> <input type="text" name="includeField1" value="1" /> <input type="text" name="includeField2" value="2" /> <input type="text" name="excludeField1" value="3" /> <input type="text" name="excludeField2" value="4" /> <input type="text" name="otherField" value="5" /> </form> <script> var model = {a:{b:"bb",c:["cc1","cc2"]}, d:["ddd"]}; var mySubmitter = function(data){ console.log(data); }; ko.utils.postJson(document.getElementById('sendForm'), model, {params: {x:'xx', y:'yy'}, includeFields: [/includeField[0-9]/, 'otherField'], submitter: mySubmitter}); </script>
結果(内部で生成されてPOSTされるフォーム)
<form action="http://localhost:8080/" method="post" style="display: none;"> <input type="hidden" name="a" value="{"b":"bb","c":["cc1","cc2"]}"> <input type="hidden" name="d" value="["ddd"]"> <input type="hidden" name="x" value="xx"> <input type="hidden" name="y" value="yy"> <input type="hidden" name="otherField" value="5"> <input type="hidden" name="includeField1" value="1"> <input type="hidden" name="includeField2" value="2"> </form>
- ko.utils.parseJson
内部的に JSON.parse が呼ばれるのでほぼ同じものとして利用すればよい。
違いは引数をJSON.parseする前にトリムすることと、JSON.parseに対応していない古いブラウザにも対応していること
古いブラウザはあんまり安全でない(Fallback on less safe)と書いてある
- ko.utils.stringifyJson
JSON.stringifyを内部で呼び出すのでほぼ同じものとして利用すればよい
違いは IE8より前だとエラーをメッセージ付きで投げることと、
JSON.stringifyに渡すJavaScriptオブジェクトを ko.utils.unwrapObservableしてから渡す
- ko.utils.range
ko.utils.range(min, max)
最小(min)から最大(max)までの範囲の値を配列にして返します
var result = ko.utils.range(2, 5);
console.log(result);
結果
[2, 3, 4, 5]
- ko.utils.registerEventHandler
要素にイベントハンドラを登録できる
<button id="btnClick">Click</button> <script> console.log("ko.utils.registerEventHandler"); ko.utils.registerEventHandler(document.getElementById('btnClick'), 'click', function(){ alert('clicked!'); }); </script>
- ko.utils.triggerEvent
イベントをディスパッチできる。
<button id="btnClick">Click</button> <script> console.log("ko.utils.registerEventHandler"); ko.utils.registerEventHandler(document.getElementById('btnClick'), 'click', function(){ alert('clicked!'); }); ko.utils.triggerEvent(document.getElementById('btnClick'), 'click'); </script>
- ko.utils.unwrapObservable
ko.unwrapはこれのショートカット。observableは値を取り出すときに unwrapしないと取り出せない。
とはいえ observable でないものを手動で unwrap する判断が間違え易いのでこのメソッドがあると思ってる。
例えば
self.a = ko.observable();
で宣言した変数の値を取り出すときは
self.a()
としてやらないといけないが、
self.b = 1;
で宣言した変数の値を取り出すときに
self.b();
とするとエラーになるため。
- ko.utils.objectForEach
オブジェクトのプロパティをぐるぐるする
<script> var obj = {a:1, b:2, c:3}; ko.utils.objectForEach(obj, function(item, index){ console.log(index + ":" + item); }); </script>
結果
1:a 2:b 3:c
- ko.utils.toggleDomNodeCssClass
jQueryでいう removeClass や addClass ができる
$(element).addClass(value); $(element).removeClass(value)
KnockoutJS
ko.utils.toggleDomNodeCssClass(element, value, true); ko.utils.toggleDomNodeCssClass(element, value, false);
<style> .add { color: red; } .remove { text-decoration:line-through; } </style> <span id="myText" class="remove">Hello</span> <script> ko.utils.toggleDomNodeCssClass(document.getElementById('myText'), 'add', true); ko.utils.toggleDomNodeCssClass(document.getElementById('myText'), 'remove', false); </script>
- ko.utils.addOrRemoveItem
配列に特定の要素を追加もしくは削除する
ko.utils.toggleDomNodeCssClass の内部や
"checked" のバインディングハンドラでモデルが更新されたときにチェックボックスを更新するのに使われている
APIは以下のようになる
ko.utils.addOrRemoveItem(array, value, included)
サンプル
var arr = ["aaa","bbb"]; ko.utils.addOrRemoveItem(arr, "ccc", true); console.log(arr); ko.utils.addOrRemoveItem(arr, "bbb", false); console.log(arr);
結果
["aaa", "bbb", "ccc"] ["aaa", "ccc"]
明日は @hkusu_ さんの 「ko.editables で入力をロールパックしてみる」です!
よろしくお願いします!
ko.utils(配列編)
これはKnockoutJSアドベントカレンダーの記事です。
KnockoutJS Advent Calendar 2014 - Qiita
今日は Knockout の便利機能が入った ko.utils について適当に紹介します。
今回は配列編。
紹介するのは以下のメソッドです。
- ko.utils.arrayForEach
- ko.utils.arrayFirst
- ko.utils.arrayFilter
- ko.utils.arrayGetDistinctValues
- ko.utils.arrayMap
- ko.utils.arrayPushAll
- ko.utils.arrayRemoveItem
ちなみにソースコードはここです。
https://github.com/knockout/knockout/blob/master/src/utils.js
- ko.utils.arrayForEach
配列をぐるぐるします。
サンプル
var years = [1950, 1960, 1970, 1980]; ko.utils.arrayForEach(years, function(item, index){ console.log(index + " : " + item); });
結果
0 : 1950 1 : 1960 2 : 1970 3 : 1980
- ko.utils.arrayFirst
配列の中で最初に条件に一致した値を返します。
サンプル
var strArray = ["a1", "b2", "c3", "d4"]; var result = ko.utils.arrayFirst(strArray, function(value){ return value.charAt(0) === "b"}); console.log(result);
結果
b2
- ko.utils.arrayFilter
配列を指定した条件でフィルタした結果の配列を返します。
サンプル
var result = ko.utils.arrayFilter(["a", "b", "c", "d", "e", "f"], function (item, index) { return index % 2 == 0; }); console.log(result);
結果
["a", "c", "e"]
- ko.utils.arrayGetDistinctValues
配列の中の重複しない値を返します。
var result = ko.utils.arrayGetDistinctValues(["a", "b", "b", "c", "c"]); console.log(result);
結果
["a", "b", "c"]
- ko.utils.arrayIndexOf
最初に見つかった配列の値のインデックスを返します。
見つからなかった場合は -1 を返します。
var result1 = ko.utils.arrayIndexOf(["a", "b", "c", "d", "c"], "c"); console.log(result1); var result2 = ko.utils.arrayIndexOf(["a", "b", "c"], "d"); console.log(result2);
結果
2 -1
- ko.utils.arrayMap
配列の中の各要素に対して特定の処理を行った配列を返します。
var result = ko.utils.arrayMap([1, 2, 3, 4, 5], function(item, index){ return item * 2; }); console.log(result);
結果
[2, 4, 6, 8, 10]
- ko.utils.arrayPushAll
配列に他の配列を追加します。
サンプル
var targetArray = [1,2,3]; var extraArray = ["a", "b", "c"]; ko.utils.arrayPushAll(targetArray, extraArray); console.log(targetArray);
結果
[1, 2, 3, "a", "b", "c"]
- ko.utils.arrayRemoveItem
最初に一致した値を配列から削除します。(一つだけ)
サンプル
var input = ["a", "b", "c", "b", "e"]; ko.utils.arrayRemoveItem(input, "b"); console.log(input);
結果
["a", "c", "b", "e"]
ここのブログと内容がかぶったけどCoffeeではなくJavaScriptで書いたのでご愛嬌。
http://weathercook.hatenadiary.jp/entry/2013/01/06/211720
ko.computedとko.pureComputed
これはKnockoutJSアドベントカレンダー5日目の記事です。
KnockoutJS Advent Calendar 2014 - Qiita
依存性の追跡(トラッキング)は ko.observable / ko.observableArray と ko.computed の組み合わせで行います。
例えば、生まれた年(西暦)から現在の年齢を算出するのをKnockoutを使って書くと次のようになります。
<p>Enter your birth year: <input data-bind='value: birthYear' maxlength="4"/></p> <p>You're <strong data-bind='text: age'></strong> years old.</p> <script> function ViewModel() { var self = this; self.birthYear = ko.observable(); self.age = ko.computed(function(){ var now = new Date(); var age = now.getFullYear() - parseInt(self.birthYear()); if(isNaN(age)) return 0; return age; }); } ko.applyBindings(new ViewModel()); </script>
- (値が変わらなくても)常に通知するようにしたり、一定時間経ってから通知したりする
上記のサンプルにあるように ko.computed の中で呼ばれる ko.observable / ko.observableArray の値が変わると ko.computed が呼ばれます。
逆にいうと値が変わらないと通知されないのですが、これを常に通知するように簡単に拡張できます。
myViewModel.fullName = ko.pureComputed(function() { return myViewModel.firstName() + " " + myViewModel.lastName(); }).extend({ notify: 'always' });
上記のように computed に .extend({ notify: 'always'}) をつけるだけです。
この書き方は extender と呼ばれ、 notify 以外にも rateLimit extender があります。
これは通知されるまでに設定された時間分待ってから通知します。
これは KnockoutJS 3.1.0 で追加されたのでそれ以前のバージョンでは throttle という名前でした(若干動きが違いますが・・・)
また、この extender は簡単に拡張できるのでドキュメントや実際のプロジェクトとか見てみると参考になるかと思います。
ドキュメント
Knockout : Using extenders to augment observables
実際のプロジェクト
knockout.persist/knockout.persist.js at master · spoike/knockout.persist · GitHub
- peek を使ってトラッキングを制御する
もし computed 内で呼び出している observable A と observable B のうち、
observable A が変更したときだけ computed を実行して、observable B が変更されても computed が実行されない場合はどうしたらよいでしょうか。
これをするには computed 内の observable B に peek() を最後に追加するだけで実現できます。
- ko.pureComputed とは何か
ko.pureComputed は Knockout 3.2.0 で追加されました。
公式サイトによるとこの機能を使うことにより以下の点で改善があるようです。
1. メモリーリークを防ぐ
2. 計算のオーバーヘッドを防ぐ
これはHTMLに現在バインディングされていない(アクティブでない) pureComputed は実行されないということです。
公式ドキュメントのサンプルがよくできているのでこれを使って説明します。
http://knockoutjs.com/documentation/computed-pure.html
<div class="log" data-bind="text: computedLog"></div> <!--ko if: step() == 0--> <p>First name: <input data-bind="textInput: firstName" /></p> <!--/ko--> <!--ko if: step() == 1--> <p>Last name: <input data-bind="textInput: lastName" /></p> <!--/ko--> <!--ko if: step() == 2--> <div>Prefix: <select data-bind="value: prefix, options: ['Mr.', 'Ms.','Mrs.','Dr.']"></select></div> <h2>Hello, <span data-bind="text: fullName"> </span>!</h2> <!--/ko--> <p><button type="button" data-bind="click: next">Next</button></p>
function AppData() { this.firstName = ko.observable('John'); this.lastName = ko.observable('Burns'); this.prefix = ko.observable('Dr.'); this.computedLog = ko.observable('Log: '); this.fullName = ko.pureComputed(function () { var value = this.prefix() + " " + this.firstName() + " " + this.lastName(); // Normally, you should avoid writing to observables within a pure computed // observable (avoiding side effects). But this example is meant to demonstrate // its internal workings, and writing a log is a good way to do so. this.computedLog(this.computedLog.peek() + value + '; '); return value; }, this); this.step = ko.observable(0); this.next = function () { this.step(this.step() === 2 ? 0 : this.step()+1); }; }; ko.applyBindings(new AppData());
このサンプルでは表示するHTMLをNextボタンを押す度に次のステップになり、ステップ0〜2 の 3回に分けて表示しています。
ステップ0では First name の入力、ステップ1では Last name の入力、ステップ2では Prefix の入力と fullName の表示を行っています。
ここで注目すべきところは、fullName が ko.pureComputed で定義されていることです。
fullNameの中では First name, Last name, Prefix を呼び出しているにも関わらず、fullName が実行されるのは
fullNameが実際にバインドされる「ステップ2」の段階になったときです。
今まで使っていた ko.computed だとどのステップにも関わらず実行されていたものを、ちゃんと「今つかっているか」を判断し、計算してくれます。
ko.pureComputed はComponentsを使った大規模なSPAでは、特に必要になってくるため地味な機能に見えて大変重要な機能ではないのかと思っています。
実際にサンプルも今まで ko.computed で書いてあったところを ko.pureComputed で書き直していたりしているので、
これからは ko.computed よりも ko.pureComputed を使った方が良い気がします。
逆に ko.computed を使う場面としては公式ドキュメントには以下のように書いてあります。
Knockout : Pure computed observables
1. 複数の observable に影響があるコールバックを ko.computed で使う場合
ko.computed(function () { var cleanData = ko.toJS(this); myDataClient.update(cleanData); }, this);
2. バインディングの init メソッドで要素を更新するために ko.computed を使う場合
ko.computed({ read: function () { element.title = ko.unwrap(valueAccessor()); }, disposeWhenNodeIsRemoved: element });
用途として基本的には pureComputedを使い、初期値やサーバサイドで値が変わったら常に実行したい場合は ko.computed を使う感じなのかなと思います。
明日は @yusuke_nozoe さんです!よろしくお願いします!
Components を使ってみる
これはKnockoutJSアドベントカレンダー3日目の記事です。
KnockoutJS Advent Calendar 2014 - Qiita
KnockoutJS 3.2では新しく Components という機能が追加されました。
作者のブログにもあるようにこの機能は web components に強く影響された機能で、コンポーネント単位で非同期にページをレンダリングします。
Componentsを使うとどのように書くことができるのか見てみましょう。
まず普通のKnockoutで書いたサンプルです。
<html lang="ja"> <html> <head> <title> Component Examples</title> <script src="knockout-3.2.0.js"></script> </head> <body> <p>Enter your name: <input data-bind='value: name'/></p> <p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p> <script> var viewModel = { name: ko.observable('something') }; ko.applyBindings(viewModel); </script> </body> </html>
デモはこちら。
このデモを全く同じ動作のまま Components を使って書くと以下のようになります。
<!DOCTYPE html> <html lang="ja"> <head> <title> Component Example </title> <script src="js/knockout-3.2.0.js"></script> </head> <body> <name-editor></name-editor> <script> ko.components.register('name-editor', { template: "<p>Enter your name: <input data-bind='value: name'/></p><p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p>", viewModel: function() { this.name = ko.observable('something'); } }); ko.applyBindings(); </script> </body> </html>
このデモはこちら。
まったく同じ動作ですが、ko.components.register を使い "name-editor" という名前のComponentを登録しています。
これによりコンポーネントの再利用することが可能になります。
またコンポーネントは作成するときに以下のようにパラメータを渡してやることができます。
KnockoutJSのComponentsでは "component"バインディングも用意されており、他のバインディングと同じように data-bind でコンポーネントを指定することもできます。
デモはこちら。
<!DOCTYPE html> <html lang="ja"> <head> <title> Component Example </title> <script src="js/knockout-3.2.0.js"></script> </head> <body> <name-editor params="value: 'hello world'"></name-editor> <div data-bind="component: { name: 'name-editor', params: { value: 'hoge bar' } }"></div> <script> ko.components.register('name-editor', { template: "<p>Enter your name: <input data-bind='value: name'/></p><p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p>", viewModel: function(params) { this.name = ko.observable(params.value); } }); ko.applyBindings(); </script> </body> </html>
いかがでしたでしょうか。
ざっと軽く見ただけでも便利なのが分かってもらえるかと思います。
さらに、KnockoutJSはこの機能もIE6のような古いブラウザーでも動くように実装しているらしいですが未検証です。
明日は @sukobuto さんの 「THETA BINDING」 です!よろしくお願いします!
JVM仕様にあるクラスファイルのフォーマットについて
これはJVMアドベントカレンダー1日目の記事です。
JVM Advent Calendar 2014 - Qiita
JVM仕様にあるクラスファイルのフォーマットについて書きます。
JavaのクラスファイルのフォーマットはJVM仕様の4章で定義されています。
Chapter 4. The class File Format
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
全く分からない状態からこれらのフォーマットについてひとつづつ読んでいくのは辛いと思うのでざっくり説明します。
クラスファイルが必要なので適当なJavaのソースコードを作ってコンパイルします。
class AdventCalendar2014 { public static void main(String[] args) { System.out.println("Hello JVM Advent Calendar 2014!"); } }
% javac AdventCalendar2014.java % ls AdventCalendar2014.class AdventCalendar2014.java
クラスファイルが出来たので今度はバイナリを覗いてみましょう。
以前そういうやつ書いたことがあるのでこれを使うことにします。
これをそのままコピってきてコンパイルしてさっきのクラスファイルを引数に指定すると、
2進数と16進数でクラスファイルの中身を出力します。
% java ClassReader /path/to/AdventCalendar2014.class Binary |Hex ------------ 11001010 CA 11111110 FE 10111010 BA 10111110 BE 00000000 0 00000000 0 00000000 0 00110100 34 00000000 0 00011101 1D 00001010 A 00000000 0 00000110 6 00000000 0 00001111 F 00001001 9 00000000 0 00010000 10 00000000 0 00010001 11 00001000 8 00000000 0 00010010 12 00001010 A 00000000 0 00010011 13 00000000 0 00010100 14 00000111 7 00000000 0 00010101 15 00000111 7 00000000 0 00010110 16 00000001 1 00000000 0 00000110 6 00111100 3C 01101001 69 01101110 6E 01101001 69 01110100 74 00111110 3E ・・・
2進数は読みにくいので16進数だけにしたら以下のような感じになりました。
CAFEBABE00000034001D0A0006000F09001000110800120A001300140700150700160100063C696E 69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100046D6169 6E010016285B4C6A6176612F6C616E672F537472696E673B295601000A536F7572636546696C6501 0017416476656E7443616C656E646172323031342E6A6176610C000700080700170C001800190100 1F48656C6C6F204A564D20416476656E742043616C656E64617220323031342107001A0C001B001C 010012416476656E7443616C656E646172323031340100106A6176612F6C616E672F4F626A656374 0100106A6176612F6C616E672F53797374656D0100036F75740100154C6A6176612F696F2F507269 6E7453747265616D3B0100136A6176612F696F2F5072696E7453747265616D0100077072696E746C 6E010015284C6A6176612F6C616E672F537472696E673B2956002000050006000000000002000000 070008000100090000001D00010001000000052AB70001B100000001000A00000006000100000002 0009000B000C00010009000000250002000100000009B200021203B60004B100000001000A000000 0A000200000005000800060001000D00000002000E
これを最初のJVM仕様のクラスフォーマットと照らし合わせていきましょう。
magic っていうのはマジックナンバーでJavaクラスの先頭はこの値が入ります。
これは Cafe(C++, A Front End)というC++コンパイラのプロダクトマネージャの Kim Polese さんの愛称(babe)からきているというのがまことしやかに噂として流れています。
CA FE BA BE // u4 magic; 00 00 // u2 minor_version; 00 34 // u2 major_version; 00 1D // u2 constant_pool_count;
ってみていくと cp_info っていうよく分からないのが登場しました。
これもJVM仕様読んでいくと 4.4 で以下のように定義されています。
cp_info { u1 tag; u1 info[]; }
一番最初の tag でデータの種類を定義していて、その値によって次の info[] の読み方が変わります。
例えば tag = 7 の場合は CONSTANT_Class なので、その場合は CONSTANT_Class_info を読みます。
というような感じで進めていくと以下のようになります。
CA FE BA BE // u4 magic 00 00 // u2 minor_version = 0 00 34 // u2 major_version = 57 00 1D // u2 constant_pool_count = 29 // cp_info #1 0A // tag = 10 (CONSTANT_Methodref) 00 06 // u2 class_index = 6 00 0F // u2 name_and_type_index = 15 // cp_info #2 09 // tag = 9 (CONSTANT_Fieldref) 00 10 // u2 class_index = #16 00 11 // u2 name_and_type_index = #17 // cp_info #3 08 // tag = 8 (CONSTANT_String) 00 12 // u2 string_index = #18 // cp_info #4 0A // tag = 10 (CONSTANT_Methodref) 00 13 // u2 class_index = #19 00 14 // u2 name_and_type_index = #20 // cp_info #5 07 // tag = 7 (CONSTANT_Class) 00 15 // u2 name_index = #21 // cp_info #6 07 // tag = 7 (CONSTANT_Class) 00 16 // u2 name_index = #18 // cp_info #7 01 // u1 tag = 1 (CONSTANT_Utf8) 00 06 // u2 length = 6 3C 69 6E 69 74 // u1 bytes[length] = <init> 3E // cp_info #8 01 // u1 tag = 1 (CONSTANT_Utf8) 00 03 // u2 length = 3 28 29 56 // u1 bytes[length] = ()V // cp_info #9 01 // u1 tag = 1 (CONSTANT_Utf8) 00 04 // u2 length = 4 43 6F 64 65 // u1 bytes[length] = Code // cp_info #10 01 // u1 tag = 1 (CONSTANT_Utf8) 00 0F // u2 length = 15 4C 69 6E 65 4E // u1 bytes[length] = LineNumberTable 75 6D 62 65 72 54 61 62 6C 65 // cp_info #11 01 // u1 tag = 1 (CONSTANT_Utf8) 00 04 // u2 length = 4 6D 61 69 6E // u1 bytes[length] = main // cp_info #12 01 // u1 tag = 1 (CONSTANT_Utf8) 00 16 // u2 length = 22 28 5B 4C 6A 61 // u1 bytes[length] = ([Ljava/lang/String;)V 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 // cp_info #13 01 // u1 tag = 1 (CONSTANT_Utf8) 00 0A // u2 length = 10 53 6F 75 72 63 // u1 bytes[length] = SourceFile 65 46 69 6C 65 // cp_info #14 01 // u1 tag = 1 (CONSTANT_Utf8) 00 17 // u2 length = 23 41 64 76 65 6E // u1 bytes[length] = AdventCalendar2014.java 74 43 61 6C 65 6E 64 61 72 32 30 31 34 2E 6A 61 76 61 // cp_info #15 0C // u1 tag = 12 (CONSTANT_NameAndType) 00 07 // u2 name_index = #7 00 08 // u2 descriptor_index = #8 // cp_info #16 07 // tag = 7 (CONSTANT_Class) 00 17 // u2 name_index = #23 0C // u1 tag = 12 (CONSTANT_NameAndType) 00 18 // u2 name_index = #24 00 19 // u2 descriptor_index = #25 ・・・
まぁあとは同じです。。
なんと驚くべきことに、ここまですべて手作業で解析してきましたが、さすがにもうしんどいです。
Javaチョットデキル人はご存知の人も多いですが、この解析は javap すれば簡単に出力してくれます。
% javap -c -verbose AdventCalendar2014 Classfile /path/to/AdventCalendar2014.class Last modified 2014/11/30; size 461 bytes MD5 checksum 94ccbcbc2ad1fdedf9c05cf088ca89f5 Compiled from "AdventCalendar2014.java" class AdventCalendar2014 minor version: 0 major version: 52 flags: ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello JVM Advent Calendar 2014! #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // AdventCalendar2014 #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 AdventCalendar2014.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello JVM Advent Calendar 2014! #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 AdventCalendar2014 #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { AdventCalendar2014(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 2: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello JVM Advent Calendar 2014! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 } SourceFile: "AdventCalendar2014.java"
こんな感じです。
明日は @sugarlife さんの「OpenJDKのソースコード配置(1-2サブディレクトリ程度まで)」です!
よろしくお願いします!
KnockoutJSの紹介
これはKnockoutJSアドベントカレンダー1日目の記事です。
KnockoutJS Advent Calendar 2014 - Qiita
一発目なので最初は KnockoutJS とはどんなものなのかについて書きたいと思います。
KnockoutJSとはMVVMパターンでJavaScriptで作る動的なUIをシンプルに構築するライブラリです。
※個人的にはフレームワークではなくライブラリだと思っている
公式サイトには以下のような標語で書かれています。
Simplify dynamic JavaScript UIs with the Model-View-View Model (MVVM)
KnokoutJSを一言でいえば、「バインディングフレームワーク」の一種です。
以下の特徴があります。
・他のライブラリ(jQueryなど)に依存しない
・IE 6+, Firefox 3.5+, Chrome, Opera, Safari など比較的古いブラウザに対応している
個人的にKnockoutJSが好きな箇所としては主に以下の2点です。
(1) 余計な機能があまりない(学習コストが高くない)
(2)(前述の特徴にもあるように)古いブラウザに対応している
まず、(1)ですが、KnockoutJSでは「data-bind=""」って空でタイプすることができれば7割ぐらいKnockoutをマスターしたものだと思ってもらっても過言ではないです。
3.1までは・・・。
それほどまでに余分な機能は削ぎ落されていてはいるものの、拡張性も高く様々なプラグインも用意されています。
Plugins · knockout/knockout Wiki · GitHub
プラグインは上記ページだけではなく、他にも
SteveSanderson/knockout.mapping · GitHub
SteveSanderson/knockout-projections · GitHub
SteveSanderson/knockout-es5 · GitHub
などといったKnockoutJS作者本人が作ったプラグインも数多くあるので見てみるのもいいと思います。
knockout-mappingについてはまた後日説明しようと思います。
(2) については KnockoutJSはVue.jsとは違い、レガシー・ブラウザもサポートしています。
その代わりVue.jsほどスタイリッシュではないし色々内部で泥臭いこともやっているのですが、現実解として古いブラウザーもサポートしないといけない場面は実際の仕事ではよくあることなのではないでしょうか?
というわけで明日は @hakurai さんです!たぶん Haxe について書いてくれると思います!