◆ できるようなのを作りました
◆ 使い方にクセがあります

メソッドチェーンしたい 再び

メソッドチェーンで書いているとき (そうでないときでも)null undefined が来た場合の分岐ってめんどうですよね
null undefined はプロパティアクセスできないので メソッドを呼び出した(プロパティアクセスした)瞬間エラーが発生がおきてしまいます

x.pop().split(",")[1].indexOf("a")

y.match(/a([0-9]+)/)[1]
というコードだと
.pop() したときに x が空の配列だと undefined ですし .split(",") で 「,」が1つもない文字列だと [1] undefined なので 次のメソッド呼び出しがエラーです

.match() も見つからなければ null が返ってくるので [1] がエラーになります

ですがいちいち確認して分岐って書くのがめんどうなだけでなく コードも綺麗じゃなくなるので嫌です


楽~に書く方法が考えてみると

  • 返り値が null や undefined になるなら特殊なundefinedオブジェクトを返す
  • undefinedオブジェクトのすべてのメソッドの返り値は undefinedオブジェクト

とすれば行けるんでは?
と思いましたが JavaScriptでは PHPの __get__call みたいに存在しないプロパティにアクセスされたときに好きな値を返す方法が無いです

ないですよね。


なので苦労してたのですが この記事callBy みたいに何かを通してメソッドを実行することでなんとかできる ということで callBy に似た使い方で使えるものができました

メソッドチェーンする

mcall

!function(){
Object.prototype.mcall = function(method, bind_this){
var that = this
var this_to_arg = true
var fn = method
if(typeof fn === "string" && fn.charAt(0) === ":"){
fn = fn.substr(1)
bind_this = window
}
else if(arguments.length < 2){
bind_this = that
this_to_arg = false
}

if(typeof method !== "function"){
fn = (bind_this || window)[method]
}
return typeof fn === "function" ? function(){
var args = [].slice.call(arguments)
this_to_arg && args.unshift(that)
var res = fn.apply(bind_this, args)
return res == undefined ? new Undef : res
} : Undef.maker
}
Object.prototype._ = Object.prototype.mcall

Object.prototype.get = function(key){
return this[key]
}
Object.prototype.__ = function(key){
return this[key] == undefined ? new Undef : this[key]
}

function Undef(){
}
Undef.prototype.mcall = function(){
return Undef.maker
}
Undef.prototype.isUndef = true
Undef.maker = function(){
return new Undef
}
}()

ちょっと長くなってます
Object mcall というメソッドを作っています
arr
.mcall("pop")()
.mcall("substr")(1)

という使い方です

mcall にメソッド名を渡すと 実行用の関数が返ってくるので引数を入れて実行します

arr ["a","bcd"] だと返り値は "cd" です
コレは普通です

arr [] だと返り値は Undef というundefinedまたはnullを表すオブジェクトです
substr の実行段階ではエラーが出ていません
返り値が Undef にどこかでなるとそれ以降で指定されたメソッドは実行しない作りです

_

mcall って毎回書くのはめんどうですよね
なので _ でも使えるようにしています
["ab", "c;de"]
._("pop")() // "c;de"
._("split")(";") // ["c", "de"]
._("map")(function(e){return e+"X"}) // ["cX", "deX"]
._("join")("_") // "cX_deX"
._("getElementById", document)() // elem?
._("getChildIds", null)() // ["id01", "id02"]?
._("shift")() // "id01"?
._(parseInt, window)(16) // NaN?
._(":String")() // "NaN"?

function getChildIds(elem){
return [].map.call(elem.children, function(e){return e.id})
}

ちょっと長めの例を書いてみました

getElementById で 要素がないかもしれないです
getChildIds が宣言されてないかもしれません
getChildIds で空配列が返って来て shift undefined になるかもしれません

そんなのに対応できてます

他機能の紹介です

_ の第二引数


指定することで メソッドを実行する this を指定できます
省略すると元々の this です
なので 配列に対しての pop map では指定しなくていいです

指定した場合は今の this が第一引数に入ります
._("getElementById", document)()
では getElementById を実行する this document になります
そうすると今までの this が消えてしまうので 第一引数に入れて実行します
document.getElementById(this)
となります
引数を渡した場合は2つめ以降に入ります


call apply と同じで null の指定は window になります
._("getChildIds", null)()
では グローバルで定義された関数 getChildIds を使うので 第二引数に null を指定します

文字列でメソッド名を渡した場合はメソッドを実行する this の中から探されます

「:」から始まるメソッド名


コロンから始まるメソッド名を書くと 第二引数を書かなくても window を指定したことになります
._(":String")()

._("String", window)()
と同じことです

メソッド名じゃなくて関数を第一引数に渡すと


メソッド名を渡すと 実行する this からメソッドを探しましたが その処理をしなくなります

Array.prototype.slice.call(

むりやり配列として メソッドを呼ぶ方法があります
Array.prototype.slice.call(obj) は配列変換によく使われます

これをやることもできます
var x = {0:10,1:11,2:12,length:3}

Array.prototype.slice.call(x, 2) // [12]
x.mcall(Array.prototype.slice)(2) // [12]
x._(Array.prototype.slice)(2) // [12]

3つとも同じになります
mcall だと普通に書くより文字数ちょっと多くなりますけど _ だとちょっと少なくなります

get

メソッドチェーンするなら 配列やオブジェクトの値を取り出す時に
var a = [1,2,3]
a[1] // 2

var o = {
a: 1,
b: 2
}
x["b"] // 2
じゃなくて
var a = [1,2,3]
a.get(1) // 2

var o = {
a: 1,
b: 2
}
o.get("b") // 2
としたいところです
ということで Object.prototype.get が用意されてます

このメソッドは undefined null はそのまま返します
なので
x._("get")(1)._("get")("a")._("get")(0)

とかくことになります

ちょっと長すぎ。。。

なので Object.prototype.__ も用意してます
これは get undefined null Undefオブジェクトに 置き換えて返すものです

ところで

いきなりですが 存在するかわからない
a.b.c.d
にアクセスするとき あなたならどうやりますか?

スタンダードな方法 &&

a && a.b && a.b.c && a.b.c.d

プロパティが存在すれば さらに奥を見ていく ということをちょっと楽してやっています
チェーンが長くなるとどんどん長くなってきて 後半は嫌になってきます

|| にしてみる

(((a || {}).b || {}).c || {}).d

false になれば 空オブジェクトに置き換えてどんどん奥を見ていきます

オブジェクトなら絶対trueとみなされますし 非オブジェクトが途中に来てもプロパティアクセスで undefined になるので次が false で空オブジェクトになって という感じで続きます

カッコが多いし書いていて分かりづらいのであまりオススメじゃないです

裏ワザ? Object

Object(Object(Object(a).b).c).d

|| に近い方法で Object 関数を通します
Object関数は Objectが引数ならそのまま返して 非オブジェクトなら オブジェクト化して返します
null undefined がオブジェクトになるのでチェーンを続けることが出来ます

こっちもカッコが見づらい難点があります

__

さて 話を戻すとします

__を使うと
a.__("b").__("c").__("d")
と書けます


さっきまでのと比較してみると
a._("get","b")._("get","c")._("get","d")
Object(Object(Object(a).b).c).d
(((a || {}).b || {}).c || {}).d
a && a.b && a.b.c && a.b.c.d
a.__("b").__("c").__("d")

__ が一番綺麗です!

もうちょっとサンプル

これも特に意味のある例じゃないです
random_word()
._("match")(/sample-([a-f][0-9]+)/)
._("get")(1)
._("getElementsByClassName", document)()
._(Array.prototype.slice)()
._("sort")(function(a,b){return a.dataset.order - b.dataset.order}))
.__(4)
._("getAttribute")("id")

function random_word(){
var a = trueRate(50) ? "sample-" : strgen(6)
var b = trueRate(80) ? "c63" : strgen(3)
return a + b

function strgen(n){
return btoa(Math.random()).substr(0,n)
}

function trueRate(percent){
return Math.random()*100 < percent
}
}

たまに マッチする結果が random_word から返ってくるので そのときそれに応じた class の要素とって~
ってまぁ見ればわかるので略します

こんな感じになるっていう見た目のイメージです

最後の値を使いたい時

チェーンの最後でDOM更新するなど副作用を起こして それだけで完了してしまえばいいですが 返り値を受け取って分岐したり 変数に保存しておくということもあると思います

最終的に返って来たのが Undef オブジェクトなのかを簡単に把握するために Undef オブジェクトには isUndef プロパティが true で用意されています
var v = x._("match")(/[0-9]+/)__(1)
if(!v.isUndef){
y(v)
}

var val = v.isUndef ? undfined : v
こんな感じに使ってください

たいてい undefined だと何もしなくて ちゃんと値があるときだけ続きをすると思うので 上側のような使い方が多いと思います

jQueryぽい?

どことなくjQueryっぽい気がしますが jQueryを嫌う理由の

  • 内部でよくわからないことしてあちこちDOM書き換える
  • 全部jQueryでラップされてる

というのがないのでべつにいいかな

null, undefined が来た時以外は普通にメソッドをそのまま使ってるだけですし 途中の返り値も普通のJavaScriptの値ですし 知らない間に変な所書き換えられることもないです