一日一処: Arrayと書かずに配列を作成するJavaScriptの狂乱(すき)

2024/02/19に公開

Arrayと書かずに配列作成

と、聞いて、糖衣構文だろうと思った人、もう少し読んでほしい。他言語でもそうだが、配列の生成方法として、おもに2つある。一つは、クラス(関数)と用いて、もう一つは、簡易的な記述(糖衣構文)によるものだ。例えば、このような表現だ。

// Arrayを使用したもの
const array = new Array()
// 糖衣構文を使用したもの
const sugar = []

当たり前の記法だが、この記事のタイトルは、そんな簡単なことを言ってるわけではない。

JavaScriptの基礎

JavaScriptでは、他言語と大きく異なる部分として、何でも文字として出力できるという、若干頭の悪い(そういうところが好き)部分がある。例えば、数値や配列などを、そのまま出力処理に使用すると、型の問題によりエラーを引き起こす。例えば、Pythonだ。

# 失敗する
arr = [1, 2, 3]
print('array is ' + arr)
# TypeError: can only concatenate str (not "list") to str

# 成功する
arr = [1, 2, 3]
print('array is ' + str(arr))
# array is [1, 2, 3]

このように、直接、文字列と結像しようとしても、型の問題で不可能だ。ただし、言語によっては、文字変換(toStringメソッドなど)が自動的に行われ、問題ない場合もある。
だが、JavaScriptは、こうなる。

const arr = [1, 2, 3]
console.log('array is ' + arr)
// array is 1,2,3

最高だぜ。実際のところ、型変換が無いと、簡単にプログラムはかけるものの、そのせい、バグが生まれることもあるため、JavaScriptそのものではなく、TypeScriptが使われているのも納得だろう。初心者にとって型を考えなくてもよいJavaScriptは簡単な反面、大きなミスを生むことがあるのを忘れてはならない。

Arrayを書かずArrayを作る

本題に入るが、前述の通り、型に関する制約がそこまで強くないJavaScriptにおいて、Arrayと書かなくてもArrayを実現する方法がある。
これは、単純に最初に記述した糖衣構文で、ブラケット[]を用いるという話ではない。文字通り、「Array」と書かずに「Array」を実現するということだ。

先程書いた通り、JavaScriptでは、特段、型の変換を用いなくても、文字列としての出力が可能だ。

let variable
console.log(variable)
// undefined

この記述は、初期化していない変数を出力すると、初期化していないため、変数の中身である、undefinedが文字として出力される。他言語であれば、初期化していないことや、値が入っていない変数を出力しようとしているところで、エラーを出してほしいが、こんな記述がまかり通りから、何度も言う通り、バグが生まれやすい(すきです)。
これは、文字として出力しているが、内容は、紛れもなく、undefinedという値になっている。つまり、プログラム中では、完全に出力するまで、これ自身は、文字列ではない。ただ、こうすると、プログラムの内部でも文字列になってくれる。

console.log(('' + variable))

つまりどういうことかというと、プログラム中でも文字列になってくれるので、このようなことが可能だ。

console.log(('' + variable)[0])
// u

文字列は、配列同様にブラケット記法によって、文字の何番目かを取得することができる。文字列となったundefinedの0番目を取得して、出力すると、uが出力されるということだ。
このようにすれば、本来プログラムに一度も書いていない単語をアウトプットすることもできる。

console.log(
  ('' + variable)[4] +
  ('' + variable)[0] +
  ('' + variable)[1] +
  ('' + variable)[2]
)
// fund

ただし、今回の目的はArrayだ。残念なことにArrayの文字が一つもundefinedに含まれていない。そのため、文字コードを用いて、文字を取得する。

console.log(
  String.fromCharCode(
    ('' + variable).charCodeAt(2) - 3
  )
)
// a

undefinedの3番目(配列番号2)の文字を文字コードにして、そこから3を引く。3番目の文字は、dになるため、そこから3つ前の文字は、aだ。これで、Arrayにかけていた文字を見つけることができた。あとは、それぞれ見つけ、文字結合をしていく。

let variable
let target = 
  String.fromCharCode(('' + variable).charCodeAt(2) - 3) + 
  String.fromCharCode(('' + variable).charCodeAt(0) - 3) + 
  String.fromCharCode(('' + variable).charCodeAt(0) - 3) + 
  String.fromCharCode(('' + variable).charCodeAt(2) - 3) + 
  String.fromCharCode(('' + variable).charCodeAt(0) + 4)
console.log(target)
// array

これで、arrayという目的の文字をプログラムで作れた。
先頭は、以下の方法で、大文字にしておく。

String.fromCharCode(('' + variable).charCodeAt(2) - 3).toUpperCase()

あとは、簡単に括弧と挿入したい値(1,2,3のような)を作り出せばいいが、前述同様に、文字コードをただいじるだけでは面白くないため、テンプレートリテラルを使ってみよう。

function gen(values) {
  let variable
  let func = 
    String.fromCharCode(('' + variable).charCodeAt(2) - 3).toUpperCase() + 
    String.fromCharCode(('' + variable).charCodeAt(0) - 3) + 
    String.fromCharCode(('' + variable).charCodeAt(0) - 3) + 
    String.fromCharCode(('' + variable).charCodeAt(2) - 3) + 
    String.fromCharCode(('' + variable).charCodeAt(0) + 4)
  return eval(`${func}(${values})`)
}

const list = gen`'a','b','c'`
list.forEach(
  (item, index) => console.log(`${index} is ${item}`)
)
// 0 is a
// 1 is b
// 2 is c

タグ付きテンプレートリテラルを使用すれば、引用符の種類を考えることなく、値を設定できる。実際のところ、Arrayと直接かけないという状況に陥ることはないと思うが、このようなことができれば、他の処理にも応用できそうだ。特にタグ付きテンプレートリテラルやeval当たりは、使う場面が、時たま発生したりするだろう。

Discussion