JavaScriptの文字列プリミティブと文字列オブジェクトの違いは何ですか?


116

MDNから取得

文字列リテラル(二重引用符または単一引用符で示される)およびコンストラクター以外のコンテキスト(つまり、新しいキーワードを使用しない)でのString呼び出しから返される文字列は、プリミティブ文字列です。JavaScriptはプリミティブを自動的にStringオブジェクトに変換するため、プリミティブ文字列にStringオブジェクトのメソッドを使用できます。メソッドがプリミティブ文字列で呼び出されるか、プロパティルックアップが発生するコンテキストでは、JavaScriptは自動的に文字列プリミティブをラップし、メソッドを呼び出すか、プロパティルックアップを実行します。

したがって、文字列プリミティブの前に(論理的に)操作(メソッド呼び出し)は文字列オブジェクトの操作よりも遅くなるはずだと思いました。 methodに適用されるました。

しかし、このテストケースでは、結果は逆になります。コードブロック1よりも速く実行コードブロック-2は、両方のコードブロックを以下に示します。

コードブロック1:

var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

コードブロック2:

var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
    s.charAt(i);
}

結果はブラウザによって異なりますが、コードブロック1の方が常に高速です。誰もがこれを説明してくださいすることができ、なぜコードブロック-1は、より高速であるコードブロック-2


6
を使用new Stringすると、オブジェクトラッピングの別の透明レイヤーが導入されます。typeof new String(); //"object"
ポールS.

どう'0123456789'.charAt(i)ですか?
Yuriy Galanter 2013年

@YuriyGalanter、それは問題ではありませんが、なぜcode block-1速いのですか?
アルファ

2
文字列オブジェクトが実際のコンテキストで見られることは比較的まれなので、インタプリタが文字列リテラルを最適化することは驚くに値しません。現在、コードは単に解釈されるだけでなく、舞台裏で行われる多くの最適化レイヤーがあります。
FABRICIOマット

2
これは奇妙です:リビジョン2
hjpotter92

回答:


149

JavaScriptには、プリミティブとオブジェクトの2つの主要なタイプカテゴリがあります。

var s = 'test';
var ss = new String('test');

一重引用符/二重引用符のパターンは、機能の点で同じです。それはさておき、あなたが名前を付けようとしている振る舞いはオートボクシングと呼ばれています。つまり、実際に起こるのは、ラッパータイプのメソッドが呼び出されると、プリミティブがそのラッパータイプに変換されるということです。簡単に言うと:

var s = 'test';

プリミティブデータ型です。これにはメソッドがなく、ランダムデータアクセス速度がはるかに速いことを説明する生データメモリ参照へのポインタにすぎません。

それでs.charAt(i)、例えばあなたがやるとどうなりますか?

はのsインスタンスではないためString、JavaScriptはオートラッピングしますs。これはtypeof string、ラッパータイプであるStringtypeof objectまたはより正確にs.valueOf(s).prototype.toString.call = [object String]です。

自動ボクシングの動作はs、必要に応じてラッパー型に前後にキャストされますが、より単純なデータ型を扱っているため、標準の操作は信じられないほど高速です。ただし、自動ボクシングとObject.prototype.valueOfは異なる効果があります。

自動ボクシングを強制したり、プリミティブをそのラッパータイプにキャストしたりする場合は、を使用できますObject.prototype.valueOfが、動作は異なります。さまざまなテストシナリオに基づいて、自動ボックス化は「必須」メソッドのみを適用し、変数の基本的な性質を変更しません。そのため、速度が向上します。


33

これは実装に依存していますが、試してみましょう。ここではV8を例に説明しますが、他のエンジンでも同様のアプローチを使用していると思います。

文字列プリミティブはv8::Stringオブジェクトに解析されます。したがって、jfriend00で言及されているように、メソッドを直接呼び出すことができます。

文字列オブジェクトが、一方で、に解析されるv8::StringObject延びるObject離間本格的なオブジェクトであるから、と、のラッパーとして機能しますv8::String

今ではへの呼び出しが、唯一の論理的であるnew String('').method()。このUnboxのために持っているv8::StringObjectのをv8::String、したがって、それは遅い、メソッドを実行する前に。


他の多くの言語では、プリミティブ値にはメソッドがありません。

MDNが言う方法は、プリミティブの自動ボクシングがどのように機能するかを説明する最も簡単な方法のようです(flavの回答でも述べられています)。つまり、JavaScriptのプリミティブ-y値がメソッドを呼び出す方法です。

ただし、スマートエンジンは、メソッドを呼び出す必要があるたびに、文字列プリミティブ-yをStringオブジェクトに変換しません。これは、アノテーション付きのES5仕様でも参考になります。プリミティブ値のプロパティ(および「メソッド」¹)の解決に関して:

:手順1で作成される可能性のあるオブジェクトは、上記の方法以外ではアクセスできません。実装は、オブジェクトの実際の作成を回避することを選択する場合があります。[...]

非常に低いレベルでは、文字列はほとんどの場合、不変のスカラー値として実装されます。ラッパー構造の例:

StringObject > String (> ...) > char[]

プリミティブから離れているほど、到達するまでに時間がかかります。実際には、Stringプリミティブは、はるかに頻繁よりもStringObjectエンジンが文字列プリミティブにメソッドを追加するために、したがって、それは驚きではない、の代わりの間で前後に変換クラスを(解釈)対応するオブジェクトStringおよびStringObjectMDNの説明の通り。


JavaScript JavaScriptでは、「メソッド」は、関数型の値に解決されるプロパティの命名規則にすぎません。


1
どういたしまして。=]MDNの説明がオートボクシングを理解する最も簡単な方法であると思われるか、ES仕様にそれへの参照があるかどうかという理由だけでMDNの説明があるかどうか疑問に思っています。現時点で仕様全体を読んで確認すると、参照を見つけた場合は、答えを更新してください。
FABRICIOマット

V8の実装についての洞察。ボクシングは機能を解決するためだけにあるわけではないことを付け加えておきます。this参照をメソッドに渡すこともできます。V8が組み込みメソッドでこれをスキップするかどうかはわかりませんが、独自の拡張機能を追加してString.prototypeとすると、呼び出されるたびにボックス化されたバージョンの文字列オブジェクトが取得されます。
ベン

17

文字列リテラルの場合、プロパティを割り当てることはできません

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

一方、文字列オブジェクトの場合、プロパティを割り当てることができます

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world

1
最後に、誰かがStringオブジェクトを必要とする理由を動機づけます。ありがとうございました!
CiprianTomoiagă2016年

1
なぜ誰もがこれを行う必要があるのでしょうか?
Aditya

11

文字列リテラル:

文字列リテラルは不変です。つまり、文字列リテラルは一度作成されると状態を変更できなくなり、スレッドセーフになります。

var a = 's';
var b = 's';

a==b 結果は 'true'になり、両方の文字列が同じオブジェクトを参照します。

文字列オブジェクト:

ここでは、2つの異なるオブジェクトが作成され、それらには異なる参照があります。

var a = new String("s");
var b = new String("s");

a==b 参照が異なるため、結果はfalseになります。


1
文字列オブジェクトも不変ですか?
ヤン王

両方のために、愚かな言語だ@YangWang ab割り当てようとa[0] = 'X'、それはなります正常に実行することではなく、仕事、あなたが期待するかもしれないだろうと
RUX

「var a = 's'; var b = 's'; a == b結果は 'true'になり、両方の文字列が同じオブジェクトを参照します。」不正解です。aとbは同じオブジェクトを参照していません。同じ値を持つため、結果はtrueです。これらの値は異なるメモリロケーションに保存されているため、一方を変更しても、もう一方は変更されません。
SC1000

9

を使用newする場合は、Objectのインスタンスを作成することを明示的に示しています。したがって、new String文字列プリミティブをラップするオブジェクトを生成しています。つまり、そのオブジェクトに対するアクションには、追加の作業レイヤーが含まれます。

typeof new String(); // "object"
typeof '';           // "string"

コメントで説明されているように、それらは異なるタイプであるため、JavaScriptインタープリターはそれらを異なる方法で最適化する場合もあります


5

宣言すると:

var s = '0123456789';

文字列プリミティブを作成します。その文字列プリミティブには、プリミティブを最初のクラスオブジェクトに変換せずに、そのメソッドを呼び出すことができるメソッドがあります。したがって、文字列をオブジェクトに変換する必要があるため、これが遅くなるという仮定は正しくありません。オブジェクトに変換する必要はありません。プリミティブ自体がメソッドを呼び出すことができます。

これを本格的なオブジェクトに変換すると(新しいプロパティを追加できるようになります)、追加の手順であり、文字列操作が速くなることはありません(実際には、テストによって文字列が遅くなることが示されています)。


文字列プリミティブがカスタムプロパティを含むすべてのプロトタイププロパティを継承するのはString.prototypeなぜですか?
Yuriy Galanter 2013年

1
var s = '0123456789';プリミティブな値です。この値にメソッドを設定するにはどうすればよいですか。混乱しています。
アルファ

2
@SheikhHeera-プリミティブは言語実装に組み込まれているため、インタープリターは特別な機能を提供できます。
jfriend00 2013年

1
@SheikhHeera-私はあなたの最後のコメント/質問を理解していません。文字列プリミティブだけでは、独自のプロパティを追加することはできません。それを可能にするために、javascriptには文字列プリミティブとすべて同じメソッドを持つStringオブジェクトがありますが、あらゆる方法でオブジェクトのように扱うことができる本格的なオブジェクトです。このデュアルフォームは少し乱雑に見えますが、99%のケースはプリミティブの使用であり、文字列オブジェクトよりも高速でメモリ効率が高い可能性があるため、パフォーマンスの妥協として行われたと思います。
jfriend00 2013年

1
@SheikhHeeraは「プリミティブがメソッドを呼び出すことができる方法を説明するためにMDNが文字列オブジェクトに変換する」方法です。それらは文字通り文字列オブジェクトに変換されません。
FABRICIOマット

4

この質問はずっと前に解決されたことがわかります。文字列リテラルと文字列オブジェクトには別の微妙な違いがあります。誰もそれに触れたことがないようで、完全を期すために書いただけだと思いました。

基本的に、この2つの違いはevalを使用する場合です。eval( '1 + 1')は2を返しますが、eval(new String( '1 + 1'))は '1 + 1'を返すため、特定のコードブロックを「通常」またはevalで実行できる場合、奇妙な結果につながる


入力ありがとうございます:-)
アルファ

うわー、それはいくつかの本当に奇妙な行動です。この動作を示すには、コメントに小さなインラインデモを追加する必要があります。これは非常に目を見張るものです。
EyuelDK

あなたがそれについて考えれば、これは正常です。new String("")オブジェクトを返すと、evalのは文字列のみを評価し、他はそのまま他のすべてのものを返す
フェリックス黒髪

3

ルートスコープには関数オブジェクトが含まれるだけなので、オブジェクトの存在はECMAScript / JavaScriptエンジンの文字列の実際の動作とはほとんど関係がありません。したがって、文字列リテラルの場合、charAt(int)関数が検索され、実行されます。

実際のオブジェクトでは、標準の動作が始まる前にcharAt(int)メソッドもオブジェクト自体で検索されるレイヤーを1つ追加します(上記と同じ)。どうやらこの場合、驚くほど大量の作業が行われているようです。

ところで、プリミティブが実際にオブジェクトに変換されるとは思いませんが、スクリプトエンジンはこの変数を文字列型としてマークするだけなので、提供されたすべての関数を見つけることができるので、オブジェクトを呼び出すように見えます。これは、OOランタイムとは異なる原理で動作するスクリプトランタイムであることを忘れないでください。


3

文字列プリミティブと文字列オブジェクトの最大の違いは、オブジェクトがこの==演算子の規則に従う必要があることです。

オブジェクトを比較する式は、オペランドが同じオブジェクトを参照する場合にのみ真になります。

したがって、文字列プリミティブには==値を比較する便利な機能がありますが、他の不変オブジェクトタイプ(文字列オブジェクトを含む)を値タイプのように動作させるのは不運です。

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(文字列オブジェクトはプロパティを追加できるため、技術的に変更可能であると指摘している人もいます。しかし、それが何に役立つのかは明確ではありません。文字列値自体は変更可能ではありません。)


かなり長い間、質問に価値を付加してくれてありがとう:-)
アルファ

1

コードは、JavaScriptエンジンによって実行される前に最適化されます。一般に、マイクロベンチマークは誤解を招く可能性があります。コンパイラとインタープリターがコードの一部を再配置、変更、削除、および実行して、コードをより高速に実行するためです。言い換えると、書かれたコードは目標が何であるかを伝えますが、コンパイラやランタイムがその目標を達成する方法を決定します。

主に次の理由により、ブロック1の方が高速です。var s = '0123456789'; var s = new String( '0123456789');より常に高速です。オブジェクト作成のオーバーヘッドのため。

ループ部分は、chartAt()がインタープリターによってインライン化される可能性があるため、速度低下の原因ではありません。ループを削除してテストを再実行すると、速度比がループが削除されなかった場合と同じになることがわかります。つまり、これらのテストでは、実行時のループブロックのバイトコード/マシンコードはまったく同じです。

これらのタイプのマイクロベンチマークでは、バイトコードまたはマシンコードを見ると、より明確な画像が得られます。


1
ご回答有難うございます。
のアルファ

0

JavaScriptでは、文字列などのプリミティブデータ型は非複合ビルディングブロックです。つまり、これらは単なる値であり、それ以上のものではありません。 let a = "string value"; はありません。デフォルトでは、toUpperCase、toLowerCaseなどの組み込みメソッドはありません...

しかし、あなたが書こうとすると:

console.log( a.toUpperCase() ); or console.log( a.toLowerCase() );

これはエラーをスローしませんが、必要に応じて機能します。

どうした ?まあ、あなたが文字列のプロパティにアクセスしようとすると、aJavascriptはラッパーオブジェクトnew String(a);として知られているオブジェクトに文字列を強制します

このプロセスは、関数コンストラクターと呼ばれる概念にリンクされています、JavaScriptでされています。では、関数を使用して新しいオブジェクトを作成します。

ここに入力new String('String value');すると、Stringは関数コンストラクターであり、引数を取り、関数スコープ内に空のオブジェクトを作成します。この空のオブジェクトは、この場合、文字列は前述のすべての既知の組み込み関数を提供します。大文字の操作など、操作が完了するとすぐに、ラッパーオブジェクトは破棄されます。

それを証明するために、これを実行しましょう:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log( justString );

ここでの出力は未定義になります。どうして ?この場合、JavaScriptはラッパーStringオブジェクトを作成し、新しいプロパティaddNewPropertyを設定して、ラッパーオブジェクトをすぐに破棄します。これが未定義になる理由です。疑似コードは次のようになります。

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard

0

Stringを3方向で定義できます

  1. var a = "first way";
  2. var b = String( "second way");
  3. var c = new String( "第3の方法");

//また、4を使用して作成することもできます。vard = a + '';

typeof演算子を使用して作成された文字列のタイプを確認します

  • typeof // // "文字列"
  • typeof b // "文字列"
  • typeof c // "オブジェクト"


aとbのvarを比較すると a==b ( // yes)


Stringオブジェクトを比較するとき

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.