uhyohyo.net

JavaScript初級者から中級者になろう

十一章第五回 プリミティブについて

今回もECMAScript5における話です。

今回の話の中心であるプリミティブという言葉は、第一章第二回で初登場し、九章第七回でも軽く解説しています。今回はECMAScript5の視点も絡めて、さらに細かく解説します。

プリミティブには、数値、文字列、真偽値、undefined,nullの5種類があり、すなわちオブジェクト以外のものです。基本的にはオブジェクトは細かく見ていくとプリミティブで構成されています。例えば、

{
  foo: 3,
  bar: false
}

というようなオブジェクトは、構成要素であるプロパティfoo,barの中身はそれぞれプリミティブです。

typeof演算子

ここで、typeof演算子を紹介します。演算子といっても+とか-とか*とかの記号ではなく普通の文字列なので違和感があるかもしれませんが、演算子です。

使い方は、

typeof

という形で、返り値は文字列で、その値の種類です。返り値には次の種類があります。

"string"
プリミティブで、文字列。
"number"
プリミティブで、数値。
"boolean"
プリミティブで、真偽値。
"undefined"
undefined。
"object"
オブジェクト(プリミティブ以外。関数は除く)。
"function"
関数。

注意すべきは、関数もオブジェクトの一種ではありますが、typeofの返り値が違うということです。

このように、typeofによって、ある値がオブジェクトかどうかとか、どんな種類のプリミティブか判定することができます。

ここで注意すべきはnullの扱いです。じつは、typeof nullの返り値は"object"です。これはとても不思議ですね。

ですから、ある変数aがオブジェクトかどうか判定するには、次のようなコードを書く必要があります。

if(typeof a=="object" && a!=null){
}

ちなみに、将来のJavaScript(ECMAScript5の次のECMAScript Harmonyと呼ばれるバージョン)では、typeof nullの結果は"null"に修正される予定です。"object"から"null"に変わってしまうと困ると思うかもしれませんが、案外それで影響を受けるコードは少ないものです。そうでないと、言語仕様を変えるということは不可能だったでしょう。

プリミティブとプロパティ

さて、プリミティブはオブジェクトではないですから、プロパティは無いはずです。逆に言うと、プロパティを持つのがオブジェクトなのです。

しかし、プリミティブのプロパティを参照したときの動作については九章第七回で説明しました。

プリミティブがあって、そのプロパティを参照したいときは、それに対応したオブジェクトのインスタンスが一時的に作られていたのでした。

文字列の場合はStringオブジェクト、数値の場合はNumberオブジェクト、真偽値の場合はBooleanオブジェクトが生成されます。

nullとundefinedには対応するオブジェクトがないので変換できず、nullやundefinedのプロパティを参照しようとするとエラーになります。

文字列のメソッド

ところで、文字列のメソッドについては四章第一回で少し紹介しました。これも実は、Stringが持つメソッドですので、実体はString.prototypeにあります。例えば、

console.log(String.prototype.indexOf);

などで確かめられます。

今回は、ECMAScript5で進化したという意味も込めて、改めて文字列(すなわちString)のメソッドを紹介します。四章第一回で省略したものは省略していますので注意して下さい。

charCodeAt

これはcharAtと似ています。charAtは、引数(1つ)で指定された位置の文字1つを返しますが、charCodeAtはそのかわりにその文字の文字コードを返します。これは、JavaScriptで文字列を文字コードに変換するための(たぶん)唯一の方法です。ちなみに、返される文字コードはUnicodeにおけるコードです。

"abc".charCodeAt(0)	//->97

concat

その文字列に、引数で渡された文字列(複数可)を全てつなげた文字列を返します。

"aaa".concat("bbb","ccc","ddd")	//->"aaabbbcccddd"

lastIndexOf

indexOfと同じく文字列を検索するメソッドですが、検索の向きが違います。実は、indexOfは、マッチする文字列が複数見つかった場合一番最初の位置を返します。

"01234567890123456789".indexOf("123")	//->1

それに対し、lastIndexOfは、一番最後のものを返します。これは「後ろから検索する」ともいいますが、返される位置はindexOfと同じく、前から数えた位置です。後ろから数えられることはありません。

"01234567890123456789".lastIndexOf("123")	//->11

ちなみに、indexOfやlastIndexOfは第二引数があって、それは検索開始位置です。

indexOfの場合、その位置より前に条件にあうものがあっても無視されます。

"012340123401234".indexOf("abc",2)	//->6

この場合、2番目(0から数えるので最初の"2"の位置)以降から探すので、最初の"123"は無視されて、次の123がヒットして返されます。

lastIndexOfの場合、(前から数えた)位置を渡してあげると、その位置より後ろは無視されます。

localeCompare

localeとは「位置」、Compareとは「比較」のことです。つまり文字列の位置を比較するということですが、このメソッドは、「何らかの方法で2つの文字列を比較して、負の数値または0または正の数値を返す」ということになっています。

つまるところ、このメソッドは2つの文字列を並べる際に、どちらか前か知ることができます。

a.localeCompare(b)

のように文字列aとbを比較したとき、aのほうがbより先なら負の値、aとbが同じなら0、aのほうがbより後なら正の値を返すということです。

"x".localeCompare("y");	//->負
"Z".localeCompare("A");	//->正
"".localeCompare("");	//->負

正とか負とかいっても、実際にどんな値になるかは、ブラウザごとに法則はあるでしょうが、はっきりとは分かりません。ですから、興味があるのは正か負か0かだけで、具体的な値は特に意味が無いものとして考えるのがよいでしょう。

さっき「何らかの方法で」と言いましたが、環境によっては同じ文字でも並べ方が異なる場合があるかもしれません。そういう場合に対応するためのメソッドであるといえます。しかし、一般的には文字コードの順で並べていると思われます。

正・負・0のいずれかという戻り値は、十一章第一回で紹介したsortメソッドのコールバック関数と合致しますね。

search

searchは、四章第三回で紹介したmatchやreplace(これらもString.prototypeがもつメソッドです)の仲間で、正規表現でマッチさせるメソッドです。

matchの場合は情報を配列で返して、replaceの場合は置き換え後の文字列を返しました。searchはもっと単純で、indexOfの正規表現版のようなものです。

つまり、引数(1つ)として渡された正規表現で文字列を検索し、マッチしたらその位置を返し、マッチしなかったら-1を返します。

ただし、indexOfのように途中から検索する機能はないので注意しましょう。

//数字の位置を返す例
"abc123def".search(/\d/)	//->3

split

splitは、「分ける」という意味です。文字列を、第一引数に渡された文字で区切って、文字列の配列にして返します。例えばこういうことです。

"aaa,bb,c,dddd,ee".split(",")	//->["aaa","bb","c","dddd","ee"] (配列)

このように文字列を配列で分けたい場合はわりとよくあるので、重宝します。また、区切り文字は複数文字でも構いませんし、正規表現も利用可能です。

//正規表現:数字で区切る例
  "fff1ghi2jjjkkk0lmn".split(/\d/)	//-> ["fff","ghi","jjjkkk","lmn"]

さらに、splitに正規表現を渡した場合、正規表現の中にグループ化の括弧( )(matchの結果として参照できるやつ)があれば、その中の部分が配列に組み入れられます。例えば、さっきの例で数字を()で囲んでみると:

"fff1ghi2jjjkkk0lmn".split(/(\d)/)	//-> ["fff","1","ghi","2","jjjkkk","0","lmn"]

今回の場合全体を括弧でくくったので、区切り文字も配列の中に現れました。

さらにsplitには第二引数があり、これはある場合、配列の要素数の上限を表します。つまり、分けた後に配列の要素数がこの数を超過してしまった場合は、多い分は捨てられてから返されます。

"aaa,bb,c,dddd,ee".split(",",3)	//->["aaa","bb","c"] 

この例では、ddddとeeが捨てられたことが分かります。

toLowerCase, toUpperCase

これらの関数は引数はありません。LowerCaseとは小文字、UpperCaseとは大文字のことであって、toLowerCaseは、文字列のうち大文字があれば小文字に変換したものを返します。toUpperCaseは逆に、小文字を大文字に変換したものを返します。その他の文字はそのままです。

大文字小文字というと、英語が思い浮かびます。

"Hello, everyone.".toUpperCase()	//-> "HELLO, EVERYONE."
"ABCDE".toLowerCase()	//-> "abcde"

しかし、英語以外にも大文字小文字は存在します。そういうものも変換できることになっています。

"Ω".toLowerCase()	//-> "ω"
"φ".toUpperCase()	//-> "Φ"
"á".toUpperCase()	//-> "Á"

toLocaleLowerCase, toLocaleUpperCase

上で紹介した関数にLocale(場所)がつきました。これは何かというと、大文字小文字が一般とは食い違う言語があるのです。

例えばトルコ語においては、i(小文字のアイ)を大文字にすると、Iではなくİです。逆に、iはİの小文字ということになります。

そして、普通のIに対する小文字として、ı(上の点がない)があります。

ところが、一般にはIの小文字はiですから、toUpperCaseなどを使うと下のような結果になります。

"İ".toLowerCase()	//->"i" (これは正しい)
"i".toUpperCase()	//->"I" (トルコ語としては正しくない)

"ı".toUpperCase()	//->"I" (これは正しい)
"I".toUpperCase()	//->"i" (トルコ語としては正しくない)

これは、普通の人はこうでないと困りますが、トルコ人はこれだと困るわけです。

この矛盾を解消するために存在するのがtoLocalLowerCase, toLocalUpperCaseです。これは、ユーザーの環境(使う言語)に合わせた結果を返してくれます。つまり、我々日本人(や英語圏の人)のブラウザではtoLowerCase, toUpperCaseと同じ感じになって、トルコの人は、toLocalLowerCase, toLocalUpperCaseなら、Iとかiに対しても(トルコ語として)正しい結果を返してくれるということになります。まあそれでも、トルコ語も他の文字の大文字小文字は英語と変わりませんから、ほとんど違いはないでしょう。

用途に応じて使い分けるのがいいでしょう。

trim

trimは、文字列の前後にある空白や改行を消した文字列を返すメソッドです。引数はありません。

"     aaa bbb ccc    ".trim()	//-> "aaa bbb ccc"

以上で文字列のメソッドの紹介が全て終了しました。次回は数値と真偽値の話です。