(文字列 "hello"
が入力され、変数 s に保持されているとして)以下のような操作を施すコード(入出力部分は前ページ参照):
a 左右ひっくり返す->olleh
s.split("").reverse().join("")
なお、この課題は、プログラミングの様々なアプローチを考える好材として認識されているので、「javascript 文字列 反転」なので検索すると様々な議論検討が行われていることがわかるだろう。
なお、s.split("")
の代わりに、Array.from(s)
や、
さらに少し技巧的な書き方だが[...s]
(スプレッド構文の応用)も使える。
b 一文字一文字区切る->h e l l o
s.split("").join(" ")
これは Rubyの場合とほぼ同じ。ただし JavaScriptの joinメソッドはデフォルトではカンマを挟むという点はRubyとは違う動作。
c 3回繰り返す->hello hello hello
String.prototype.repeat()
は使えるので、 s.repeat(3)
でリピートできる (が、もちろんこれは本題の正解ではない)。Array(3).fill(s).join(" ")
d 大文字にする->HELLO
s.toUpperCase()
e 各文字を3つずつ重ねる->hhheeellllllooo
各文字ごとにそれを3回出現させるという操作をするために、
Array.prototype.map()
メソッド(高階関数)を使い、=>
で記述)を渡し、という考え方でこの課題に一歩近づけるだろう。すなわち、 s.split('').map(c=>Array(3).fill(c))
というようなコードになる。
その結果は、下のように配列の配列の形になる(以下は node.js での出力例)。
[
[ 'h', 'h', 'h' ],
[ 'e', 'e', 'e' ],
[ 'l', 'l', 'l' ],
[ 'l', 'l', 'l' ],
[ 'o', 'o', 'o' ]
]
このまま join("")
で連結してしまうと中途半端な形になる。
これを解決する方法は(シンプルなものとしては)3つ考えられる(いずれを使ってもいい)。
// join を 外と中の2階層それぞれで呼ぶ
s.split('').map(c=>Array(3).fill(c).join("")).join("")
// 配列を平坦化(flat()を使って)してから join
s.split('').map(c=>Array(3).fill(c)).flat().join("")
// map の代わりに flatMap を使って最初から平坦な配列を作らせる
s.split('').flatMap(c=>Array(3).fill(c)).join("")
バックスラッシュについて:
バックスラッシュ( \
)に関する事情は日本ではプログラミング言語によらず共通のもの。
文書へのアクセス
クラスの概念と、機能の調べ方
最近のJavaScriptでは class 構文でのクラス定義ができるが、元来は、prototypeベースでのオブジェクト指向が実装されていて、その仕組みが今も使われている。
オブジェクトや変数のデータの種類(クラス)を調べるためには、typeof
演算子を使って、typeof 1
、 typeof "hello"
のように書く。
typeof
に加えて、instanceof, new など幾つかの(クラスにまつわる)操作が、 メソッドではなく演算子として提供されている(このあたりがRubyとは違う点だろう)。REPL上で(或はブラウザのコンソールでも)オブジェクト等に対して利用可能なメソッドを調べるためには、 例えば var s="hello"
に対して、
s.
或は、
String.prototype.
を入力した時点で、Tabを打鍵すれば(コンソール上だと自動的に)利用可能なワード(フィールド名やメソッド名)が列挙されるので、その中から必要なものを(名前で判断する必要があるが)選択することができる。
メソッドと、メソッドチェーン
オブジェクトに対してメソッド(関数)を呼ぶという考え方、書き方は、Ruby、JavaScript、Javaなど、多くの言語でほぼ共通。レシーバ ピリオド メソッド名 の順に書く。
ただし、JavaScriptでは、その後ろに括弧は(渡すべき引数がなくても)必須。
文法的に演算子に見えるようなメソッドを定義する(RubyやScalaで採用されている)ことは、JavaScriptではできない。
ドキュメントに記されたものがすべてということになる。
( )
や ,
も含めて演算子として扱われていることや、 C言語から多くの言語に警鐘されている ++
や --
の単項演算子はRubyにはないことなども見ておいて下さい。文字コード:
改行文字 ‘’ に関しては、特にプログラミング言語に依存せず共通の話だと理解していい。
継承:
JavaScriptもオブジェクト指向言語なので、継承の概念はある。が、以下のように昨今の主要(オブジェクト指向)言語との差異があることを知っておいて下さい。
JavaScriptでも最近はクラスの概念が導入され、クラスベースの書き方が可能になってきているが、JavaScriptは「プロトタイプベース」のオブジェクト指向だと分類されていて、
クラスとインスタンスの区別がない。
クラスベースのオブジェクト指向言語(Ruby Java Scalaなど)では、クラスに定義された性質(メソッド等)を、それを具現化したインスタンスが借り受けて使う、とともに、
クラス階層(基本的に木構造)の中で上位のクラスから下位のクラスに引き継がれる(継承される)性質も、そのインスタンスが借り受けて使うことができる。
それに対して、JavaScirptでは、プロトタイプ指定(具体的には、オブジェクトの .prototype フィールドの値)によって、オブジェクトが他の任意のオブジェクトから性質を継承することができる。
といった仕組みの話は詳しくはここでは省略するが、とりあえずは、 JavaScriptではクラス階層という概念を考えずにプログラミングしていて大丈夫だろう、 と理解しておいて下さい。
(と言いつつも若干ここで補足しておくが)
クラス名のように認識できる String や Array 等は、実体は Function である。
String
// => [Function: String]
Array
// => [Function: Array]
これらの関数を呼び出すとそれぞれに対応するデータを生成する(以下の例では引数なしで呼び出して空っぽのデータが生成されているが、 引数を渡して具体的なデータを生成することも勿論可能)。
String()
// => ''
Array()
// => []
これらのデータに対して呼び出せるメソッド等は、String.prototype
、Array.prototype
で表されるオブジェクトの メンバー(属性、フィールドとも呼ぶ)として保持されている。 例えば(これまでに見てきたが)配列を反転させるメソッドは、 Array.prototype.reverse()
がドキュメントの見出しになっている。
これらのメソッドの中(ソースコード上)では、メソッド呼出のレシーバ(ピリオドの左に置かれていたオブジェクト)が、変数 this として与えられていて(Rubyのメソッドの中では self だったものに相当する)、 this に対する処理としてプログラムが書かれている。
Array() が返す値と、String()が返す値は、以下のように違いがある。
typeof []
// => 'object'
typeof ''
// => 'string'
// 以下はブラウザでの応答例
[]
// => ▶ [] // オブジェクトとして表示される
’’
// => ”” // 文字列はプリミティブ型なのでシンプルな表示
が、プリミティブ型の文字列は必要に応じてStringオブジェクトに内部で変換されるので、 "".length
等の式(ここで length はメソッドではなく、オブジェクトの属性)を書いても大丈夫。
関数は、(これまで見てきたように)括弧を後置して(必要なら引数を与えて)呼び出すという使い方に加えて、 new 演算子を前置して、コンストラクタとして呼び出すという使い方もできる (これが元来のJavaScriptでのオブジェクト指向の実現方法だった)。
String
// => [Function: String]
String("abc")
// => "abc" // プリミティブ型の文字列
new String("abc")
// => [String: 'abc'] // String オブジェクト
new String
// => [String: ''] // 括弧なしでも呼べる
Array
// => [Function: Array]
Array(2,3,4)
// => [ 2, 3, 4 ]
new Array(2,3,4)
// => [ 2, 3, 4 ] // ここでは同じ結果になる
new Array
// => []
Date
// => [Function: Date]
Date()
// => 'Mon Dec 28 2020 20:15:50 GMT+0900 (日本標準時)' // …のような文 字列
typeof Date()
// => 'string'
new Date()
// => 2020-12-28T11:16:23.411Z // のように表示される(処理系によって違う)
typeof new Date()
// => 'object'
new Date
// => 2020-12-28T11:16:23.411Z // やはり括弧は省略しても同じだった
これらの実行例からわかるように、呼び出し方によって結果が変わる(場合と変わらない場合がある)ことと、 new の使い方の詳細はここでは省くが、 (単なる関数呼出しはその関数定義のコードに書かれたことだけを行うのに対し) new では、必ずオブジェクトを作って返すことを知っておいて下さい。