C言語から継承された伝統的な書き方:
for(let i=0 ; i < 3 ; i++) console.log(i)
配列(またはイテレータ)から出発する記法:
配列に対して .map または .forEach を使う。
[...Array(3)].forEach((_, i) => console.log(i))
// 添え字を印字する
[...Array(3).keys()].forEach(i=> console.log(i))
// .keys() は添え字を配列(実際にはイテレータ)として生成させるメソッド
[...Array(3)].map((_, i) => console.log(i))
// .map でも動作は同じ(値として新たな配列が作られるが即座に廃棄されるだけ)
Array(3).fill(0).map(_, i) => console.log(i))
// fill()で充填するための値は何でもいい
Array(3) は寸法3の配列を生成するが配列として初期化されていない(値を取り出そうとすると undefined が返る)。 なので Array(3).forEach(...)
は(.map
でも)何もしない。
ループが機能するためには配列が初期化されている必要がある。その1つの方法は [...Array(3)]
。 値 undefined が各要素として収められているという形になる。
...
を使った表現はスプレッド構文と呼ばれ、様々な場所に配列を展開する機能を持つ。
.map
や .forEach
は無名関数(など)を呼ぶときに2つの引数を渡す。第1引数がそれぞれの要素、第2引数はその添え字(配列内での位置を示す整数)。
上の第1の例では、第1引数(配列の要素の値)を無視して、第2引数(添え字)を印字している。
第一仮引数として名前 _
を使っているのは、使わない引数であることを示すための慣例的な書き方。但し、この名前は通常の変数名であるため、1つの括弧内で2度使うという使い方はできない(Scala言語などでは _
はプレースホルダとして特別な記号とされているので複数回使っても支障ないのだが)。
なお、添え字は0から始まるので、1から3の整数を印字したいときには、 [...Array(3).keys()].map(x=>x+1)
或は [...Array(4).keys()].slice(1)
を使うことになるだろう。
for 文(要素を取り出すfor文)
JavaScript には for~in と for~of の形式があり、 前者はオブジェクトのプロパティを順々に取り出して(連想配列のキーに相当するもの)処理を繰り返す構文、 後者が配列の要素を順々に取り出して繰り返す構文(Rubyのfor~in と概ね同等)。
for(i of Array(3).keys())console.log(i)
for の構文による繰り返しと、配列をレシーバとしてして高階関数(forEachやmap)を呼び出す書き方と、 ほぼ同じ意味のことを実現する2つの記法が提供されているという状況は、 Ruby、JavaScript、Scala 等で共通だと考えていいだろう。
スペースの都合で、本ページのこれ以降のプログラム例では、主に高階関数による記法を使い、for~of による記述例は省略することを了解されたい。
配列の要素に対する繰り返しを実現する書き方としては、Rubyと同様の破壊的メソッド shift を使った繰返しも可能。
var a=[1,2,3,4,5] // 配列を準備する
var n // n も予め宣言しておく必要がある
while(n=a.shift()) console.log(n)
// このループの終了後 配列 a は空になっている
なお、CoffeeScript(AltJSの1つ)では、(Rubyと同様に)Rangeを使う記法が使える。
for i in [0 ... 3] console.log(i)
console.log() で出力したあとの改行を抑制する方法はない。 が、改行せずに印字するメソッドとしては process.stdout.write()
がある(が、あまり一般的ではない)。
横に並べて出力する際に(これまで見てきたような、かなり面倒な)ループを使う必要はなく、
console.log が複数の引数を受け付けて、それらをスペース区切りで一行に出力してくれる(このとき、前出のスプレッド構文が再び必要になる)。
console.log(...Array(3).fill("hello"))
Rubyでは String#reverse
が既存のものとして用意されているが、JavaScriptには String.prototype.reverse()
はない。 これを(講義の本編のように Array.prototype.reverse() を借用するのではなく)自分で作るとしたら…
(対象の文字列は変数 s に保持されているとする。 それぞれの考え方の解説は本編を参照。)
もとの場所で、交換:
ただし、JavaScriptには、文字列の指定位置の部分文字(列)を置き換えるメソッドは用意されていない(同じことをするためには、変更しない前部、変更対象、変更しない後部を連結するやり方になる)。
なので、まずは配列に変換して反転させた後にjoinするアプローチから入ることにする。
let a=s.split(''), l=s.length
[...Array(l/2>>0).keys()].forEach(i=>[a[i],a[l-i-1]]=[a[l-i-1],a[i]])
a.join('')
l/2>>0
の部分は、(sの長さ l が奇数の時に)2で割った余りを切り捨てる整数除算をさせるやや裏技っぽい(シフト演算子を使った)記法 (Rubyだと整数を整数で割ると切捨てを行うのでスラッシュ /
だけでいい)。
他に、Math.trunc(l/2)
、 Math.floor(l/2)
、~~(l/2)
という書き方もある。
forEachに渡す無名関数では、配列の i番目の要素と l-i-1 番目の要素を交換するために、 代入演算子 =
の右辺、左辺とも2要素の配列の形にし、対応する要素間での多重代入を行っている。この2つの代入は同時に行われるため、値を一時的変数に退避させるという伝統的な値の交換の記法は(Rubyと同様に)必要ない。
また、繰り返しではなく再帰呼出を使って書くこともできる。その時は、sliceや正規表現のキャプチャを使って部分文字列の先頭と末尾を切り出すことができるので、配列に変換しなくても実装は可能。
// slice を使う場合
let rev=s=>s.length<=1?s:s.slice(-1)+rev(s.slice(1,-1))+s[0]; rev(s)
// 正規表現 を使う場合
let rev=(s,m)=>(m=s.match(/(.)(.*)(.)/))?m[3]+rev(m[2])+m[1]:s; rev(s)
上記2つの用例は、同じ論理で組み立てていて、文字列から先頭と末尾の1文字ずつを切り出し、残り(中間部)を再帰的に関数 rev に渡して反転させている。
slice の使い方は、ドキュメントで確認されたい。なお、最初の1文字を取り出すところは sliceでなくブラケットによる添え字指定を使っている。
上のサンプルは一行で記述したため初学者には難解かも知れないので、 slice版を(function if 等を使って)伝統的な書き方でリライトしたソースコードも参考にされたい。
sliceの代わりに正規表現によるマッチとキャプチャを使ったのが2つめ。
正規表現については次章で扱う予定(あとでこのコードも見直して下さい)。
rev を2引数の関数として定義している(でも渡す引数は1つだけ)のは、 マッチの結果(配列として返される)を受け取る一時的変数(の名前)を提供するだけのため。 let などを使って中で宣言してもいいが、上記のようにすると関数revの本体(=> の右)を2つの式(括弧 ()
で囲んでカンマ ','
で区切った)で表現できて 少しコンパクトに書ける。
新しい場所に向けて、交換:
let l=s.length
[...Array(l).keys()].map(i=>s[l-i-1]).join('')
回れ右(の列を押し込んで行く)
やはり配列(空の配列から出発)を使うことになる。また、対象となる文字列 s も配列に分割してから順々に forEach で送り込むという使い方になる。
let r=[]
s.split('').forEach(c=>r.unshift(c))
r.join('')
Rubyの String#each_char
に相当するような、文字列を構成する各文字を個別に扱うための直接の機能は JSにはない。
また、JSでは、文字列の部分に対して変更を加える操作も提供されていない (ので、こういう機能の実現のためには splitで一旦配列に分解するしかないだろう)。
ダブルクォートとシングルクォートは(Rubyでは式展開が行われるかどうかの違いがあるが) JSでは機能的に同等、なのでどちらを使ってもいい(文字列の中にクォート文字を含ませたい時に使い分けるといい)。
"n=#{n}"
のように使う)は、今のJSでは、第3のクォート文字(バッククォート ` `
) の中で使うことができる(`n=${n}`
のような記法になる)。node.js では、process.argv
にコマンドラインパラメータが収められている。ただし、その収容されている場所(添え字)が CやRubyでの感覚と若干ずれているのでご注意。
このプログラム を適当に実行してみるとわかるが、
node 3_2.js a b c
// => [
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\nobu\\OneDrive\\class\\PL\\js\\3_2.js',
'a',
'b',
'c'
]
配列の0番目がnode.jsのパス名、1番目が実行したプログラムのパス名、 3番名以降にパラメータとして与えた値が入る。
ということは、以下のようなコードでパラメータ(だけ)を配列として得られることがわかる。
console.log(process.argv.slice(2))
ブラウザの中で(コンソールではなくWeb画面上で)何か文字列を渡して結果を画面に表示させたい時は、たとえば以下のようなHTMLファイルを用意するといい。
<!doctype html>
<meta charset="utf-8">
<input type="text" name="exp" oninput="io()">
=
<span id="ans"></span>
<script>
const exp=document.getElementsByName('exp')[0],
out=document.getElementById('ans')
function io(){ // input要素に値が打鍵される毎に呼ばれ、
out.innerText=calc(exp.value) // span 領域に結果の値を表示する
}
function calc(s){ // 今は文字列を反転さているだけだが、
// この関数の中(や名前)は必要に応じて書き換えて使う
return s.split('').reverse().join('')
}
</script>