JavaScriptの文字列型で悩んだ記録

JavaScriptで文字列をいじっていたら、何だか不可解な動きをしたので気になって色々調べてみました。

文字列リテラルとStringオブジェクトは何かが違う

文字列リテラル

var str = "文字列";

Stringオブジェクト

var str = new String( "文字列" );

上の2つは全く同じものだと思っていたのですが、ちょっと違うみたいです。


あんまり意味はないのですが、文字列のプロパティに値を代入してみました。

var str = "文字列";
str.property = "プロパティ";
alert( str.property );


代入したはずの値がどこかに行ってしまいました。
代入がなかったことにされてしまったのです。


Stringオブジェクトでやるとちゃんと残っています。

var str = new String( "文字列" );
str.property = "プロパティ";
alert( str.property );


うーん。不思議だ。
「文字列リテラルはオブジェクトではなくて基本型なので、プロパティとかそういうのはやってないです。」っていうんならわかるんだけど、
lengthプロパティとかreplace()メソッドとか使えるし、

var str = "文字列";
alert( str.length );
alert( str.replace(/字/, "");

どうみてもオブジェクトっぽく振る舞ってるから「文字列っていうのはStringクラスのオブジェクトなんだ」「new String()って書くのは面倒くさいから、省略して簡単に書けるようになってるんだ」だと思って今まで生きてきたのだけど、明らかに挙動が違う。


うーん、うーん。

基本型とラッパーオブジェクトの自動変換

サイの本に答えが書いてました。

 実際はどうなのでしょうか。sはオブジェクトなのでしょうか、それとも基本データ型なのでしょうか。typeof演算子を使用して調べると、stringが返されるので、sは基本データ型であり、オブジェクトではないことがわかります。ではなぜ、オブジェクト表記で文字列を扱えるのでしょうか。

typeof演算子なんてものがあるのか。ちょっとやってみよう。
おぉ、確かに「typeof "文字列"」は「string」を返して「typeof new String( "文字列" ) 」は「object」を返した。
てゆーか、文字列って基本型なのか。これはなんというか衝撃的だ。
Javaでは「文字列などない!あれはオブジェクトだ!」と言われ、C言語では「文字列などない!あれは配列だ!」と言われてきたので、基本型として文字列型というものがあるというのにびっくりしました。

 実は、数値、文字列、論理値の3つの基本データ型には、それぞれに対応したオブジェクトクラスが用意されているのです。つまり、JavaScriptでは、数値、文字列、論理値というデータ型とは別に、基本データ型を包み込むラッパーとして、Number、String、Booleanというクラスが用意されています。基本データ型と同じ値を持つだけでなく、対応するプロパティとメソッドを定義して、そのデータを操作できます。

基本型をラップするクラスがあるのか。これはJavaにもあるのでよくわかる。IntegerとかCharacterとかだな。

指定された文字列のプロパティやメソッドにアクセスすると、JavaScriptがその文字列に対応したStringラッパーオブジェクトを内部的に生成します。そして、文字列の代わりに、このStringラッパーオブジェクトを使用します。このオブジェクトにはプロパティとメソッドが定義されているので、オブジェクトコンテキストで文字列が使用できるのです。

ふむふむ。

 オブジェクトコンテキストで文字列を使用する場合、内部的に生成されるStringオブジェクトは一時的なものであることに注意してください。これは、プロパティやメソッドにアクセスするために用意されたものです。アクセスが終了すれば不要になるのでシステムによって回収されてしまいます。

( Д ) ゚ ゚


つまりこういうことらしいです。

  • 文字列がオブジェクトみたいに扱われると、文字列は一瞬だけString型のオブジェクトに変身します。
  • そしてプロパティを参照したりメソッドを実行して、すぐまた元の文字列に戻ります。
  • 変身していたときのことは何も憶えていません。

これを踏まえて上のコードを見てみると

var str = "文字列";

strには文字列型の値が入ります。

str.property = "プロパティ";

ここで文字列からStringオブジェクトに変身!オブジェクトに変身したstrはプロパティに値を代入しますが、その後すぐに自分自身を削除してしまうので

alert( str.property );

ここでは何も憶えていない。ということなのですね。


なるほどなー。

思ったこと

サイの本はしっかり読んだつもりだったのに、今の部分が全く記憶になかったのがショックでした。文字を追いかけるだけでは頭に入らないのだなあ。

おまけ

Stringオブジェクトでも既存のプロパティに値を代入することはできないようです。

var str = new String( "文字列" );
str.length = '長さ';
alert( str.length );


ん?ってことはプロトタイプに追加したプロパティにも値を代入できないのかな。

String.prototype.property = "プロトタイプのプロパティ";

var str = new String( "文字列" );
alert( str.property );


str.property = "プロパティ";
alert( str.property );

無事に代入できた。うーん。どうなってるんだ。

lengthプロパティは特別な存在なのか。