配列には30以上のメソッドがあります。一方でオブジェクトは、、、
({}).forEach; // undefined({}).map; // undefined({}).filter; // undefined({}).every; // undefined({}).some; // undefined({}).fill; // undefined({}).reduce; // undefined({}).toString; // function toString() { [native code] }
これではObject.entries()
で配列にしたくなってしまいますが、元のオブジェクトへの参照は保てません。
配列とオブジェクトは、どちらも値を格納することができますが、できることは大きく違うようです。とはいえ、APIを通してJSONデータを取得することが多い昨今のWeb開発において、オブジェクトの操作は需要が高いのではないでしょうか(特にmap
とfilter
)。
オブジェクトにはインデックス番号でのアクセスが基本的にできず、ES2015以前はオブジェクトのプロパティの順序が保証されていなかったことから、indexOf
などオブジェクトでは使えない配列メソッドもあります。ここでは、できる限りのメソッドを取り上げます。
オブジェクトのラッパーオブジェクトを作る
ラッパーオブジェクトの名前は、objencyとします。efficiency objectの略です。IE8以上で動くものをこれから作っていきます。
まずはラッパーオブジェクトを作るところからです。
(function (root) { 'use strict'; var Objency = function (originObj) { if (!originObj || Object.prototype.toString.call(originObj) !== '[object Object]') { // プレーンオブジェクトのみを受け付ける throw new TypeError('プレーンオブジェクトでなければなりません。'); } else if (originObj.constructor && originObj.hasOwnProperty('constructor')) { // prototypeは受け付けない throw new Error('prototypeオブジェクトのようです。お引き取りください。'); } else if (originObj instanceof Objency) { // Objencyインスタンスはそのまま返す return originObj; } return new Objency.prototype._constructor(originObj); }; Objency.prototype = { _constructor: function (obj) { // ラッパーオブジェクトに格納する var keys = []; for (var key in obj) keys.push(key); this.$obj = obj; this._keys = keys; this._allKeys = keys.slice(); } }; Objency.prototype._constructor.prototype = Objency.prototype; root.objency = Objency;})(window);// サンプルobjWrap = objency({foo: 'bar', baz: {qux: 'quux'}, 71: ['corge']});// $obj: Object// > 71: ["corge"]// > baz: {qux: "quux"}// > foo: "bar"// _keys: ["71", "foo", "baz"]
forEachとmap
ここからは、objencyのメソッドを定義していきます。
// objWrap.forEach(console.log); で全ての中身を出力するforEach: function (callFn, thisArg) { // 配列のforEachに似ているが、引き続きメソッドチェーンができる var keys = this._keys; var obj = this.$obj; if (typeof thisArg === 'undefined') thisArg = undefined; for (var i = 0; i < keys.length; i++) { callFn.call(thisArg, obj[keys[i]], i, obj); } return this;}// objWrap.map(function (value) { return typeof value; }); で全てをtypeof演算子の返り値に変えるmap: function (callFn, thisArg) { // 配列のmapに似ているが、元のオブジェクトを変更する var keys = this._keys; var key; var obj = this.$obj; if (typeof thisArg === 'undefined') thisArg = undefined; for (var i = 0; i < keys.length; i++) { key = keys[i]; obj[key] = callFn.call(thisArg, obj[key], i, obj); } return this;}
filter
// objWrap.filter(Array.isArray); で値が配列であるプロパティ以外は、今後扱われないようになるfilter: function (callFn, thisArg) { // 配列のfilterに似ているが、元のオブジェクトを変更する var keys = this._keys; var obj = this.$obj; if (typeof thisArg === 'undefined') thisArg = undefined; for (var i = 0; i < keys.length; i++) { if (!callFn.call(thisArg, obj[keys[i]], i, obj)) { keys.splice(i, 1); } } return this;}
every
every: function (callFn, thisArg) { var keys = this._keys; var obj = this.$obj; if (typeof thisArg === 'undefined') thisArg = undefined; for (var i = 0; i < keys.length; i++) { if (!callFn.call(thisArg, obj[keys[i]], i, obj)) { return false; } } return true;}
some
some: function (callFn, thisArg) { var keys = this._keys; var obj = this.$obj; if (typeof thisArg === 'undefined') thisArg = undefined; for (var i = 0; i < keys.length; i++) { if (callFn.call(thisArg, obj[keys[i]], i, obj)) { return true; } } return false;}
fill
fill: function (newVal) { // 配列のfillに似ているが、範囲指定はサポートしない var keys = this._keys; var obj = this.$obj; if (typeof newVal === 'undefined') newVal = undefined; for (var i = 0; i < keys.length; i++) { obj[keys[i]] = newVal; } return this;}
reduce
reduce: function (callFn, initVal) { var keys = this._keys; var obj = this.$obj; var currentVal; var i = 0; if (typeof initVal === 'undefined') { initVal = undefined; currentVal = obj[keys[0]]; i = 1; } else { currentVal = initVal; } for (; i < keys.length; i++) { currentVal = callFn(currentVal, obj[keys[i]], i, obj); } return currentVal;}
setAllKey
filter()
で減らされたキーリストを元に戻すsetAllKey()
のコードです。
setAllKey: function () { this._keys = this._allKeys.slice(); return this;}
objencyの入手
GitHubからダウンロードできます。利用は完全に自由(CC0)です。
参考リンク