◆ 実用性はもちろん……

前の記事で var を書かず代入しない方法を使ってみましたが これを進化させました
この記事の要素も取り入れてます
function let(...args){
const fn = args.pop()
if(typeof fn !== "function") throw new TypeError("Last argument must be a function.")
return fn.apply(this, args)
}

let(1, 2, (x, y) => x + y)
// 3

let.bind([1])(1, 2, Array.prototype.concat)
// [1, 1, 2]

これで var などを使った代入をしないコードを書けます
ないと困る文って宣言文くらいだし 宣言文がいらなくなったら 一つの文だけで処理かけるんじゃないかな と思ってちょっとだけ複雑なものを作ってみました

1 文にまとめる

まずは ライブラリとして 2 つ関数準備しておきます
function eif(exp, ift, iff){
return exp ? ift : iff
}
function let(...args){
const fn = args.pop()
if(typeof fn !== "function") throw new TypeError("Last argument must be a function.")
return fn.apply(this, args)
}

eif は三項演算子の変わりです
なくてもいいですけど 見やすさ的に関数に揃ってる方がいいかなとおもってとりあえず


ではやることです

こういうリンクっぽいものが入った HTML を自動でリンク化します
<div>
xxxy
<span>abc http://urlurlurl bcd</span>
zzz http://z.y.x/v/w/u.html
</div>

一つの式にしたコードはこちら
let(
document.querySelector.bind(document),
document.querySelectorAll.bind(document),
(tagname, props) =>
Object.assign(document.createElement(tagname), props),
fn => (...args) => fn.apply(null, [fn].concat(args)),
($, $$, create, rfn) =>
Array.from($$("body, body *"), elem =>
eif(elem.closest("a"),
false,
[...elem.childNodes].filter(node => node.nodeType === Node.TEXT_NODE))
)
.filter(ns => ns && ns.length)
.reduce((a, b) => a.concat(b), [])
.forEach(n =>
rfn((self, node) =>
let(
node.data.match(new RegExp(String.raw `https?://[^\s]+`)),
match =>
match && let(
node.splitText(match.index),
lnktext =>
let(
lnktext.splitText(match[0].length),
next =>
lnktext.replaceWith(create("a", {href: lnktext.data, textContent: lnktext.data}))
|| self(self, next)
)
)
)
)(n)
)
)

代入代わりの let の数だけネストするのでコールバック地獄感があります
ネストを防ぐために async/await ができたのに これは逆方向に突き進んでる感がすごいです

またそれとは別の見づらさもあります
let(
a,
b,
(a, b) => a + b
)
この書き方は短いと見やすいですが ひとつひとつの式が長くなると区切りがわかりづらいです

let 変形

let の使い方を変えてこんな風につかえるようにしてみます
let(1, 2)((x, y) => x + y)

代入する値だけで一度関数呼出して返ってくる関数に実行する関数を入れます
これで書き直すと
let(
document.querySelector.bind(document),
document.querySelectorAll.bind(document),
(tagname, props) =>
Object.assign(document.createElement(tagname), props),
fn => (...args) => fn.apply(null, [fn].concat(args))
)(
($, $$, create, rfn) =>
Array.from($$("body, body *"), elem =>
eif(elem.closest("a"),
false,
[...elem.childNodes].filter(node => node.nodeType === Node.TEXT_NODE))
)
.filter(ns => ns && ns.length)
.reduce((a, b) => a.concat(b), [])
.forEach(n =>
rfn((self, node) =>
let(
node.data.match(new RegExp(String.raw `https?://[^\s]+`))
)(
match =>
match && let(
node.splitText(match.index)
)(
lnktext =>
let(
lnktext.splitText(match[0].length)
)(
next =>
lnktext.replaceWith(create("a", {href: lnktext.data, textContent: lnktext.data}))
|| self(self, next)
)
)
)
)(n)
)
)

カッコが増えましたが )( のところで 上が代入する値 下が実行する関数とわかるので見やすさは上がった気がします

普通に書く

最後に余計なことせず普通に代入して書いてみます
var $ = document.querySelector.bind(document)
var $$ = document.querySelectorAll.bind(document),
var create = (tagname, props) => Object.assign(document.createElement(tagname), props)
var rfn = fn => (...args) => fn.apply(null, [fn].concat(args))

Array.from($$("body, body *"), elem => elem.closest("a") ? false : [...elem.childNodes].filter(node => node.nodeType === Node.TEXT_NODE))
.filter(ns => ns && ns.length)
.reduce((a, b) => a.concat(b), [])
.forEach(n => {
rfn((self, node) => {
var match = node.data.match(new RegExp(String.raw `https?://[^\s]+`))
if(match){
var lnktext = node.splitText(match.index)
var next = lnktext.splitText(match[0].length)
lnktext.replaceWith(create("a", {href: lnktext.data, textContent: lnktext.data}))
self(self, next)
}
})(n)
})

わぁー シンプルです


一つの式にするのは面白いですが 実用性はあんまりですね

devtools の実行時にも 一気に進んでしまったりして不便ですし


最後にサンプルページ置いておきます
http://var.blog.jp/s/let.html