128

MDNから取得

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

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

しかし、このテストケースでは、結果は反対です。コード block-1はコード block-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);
}

結果はブラウザーによって異なりますが、コード block-1は常に高速です。コード block-1がコード block-2よりも高速である理由を、誰でも説明できますか。

4

12 に答える 12

163

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

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

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

var s = 'test';

プリミティブ データ型です。これにはメソッドがなく、生データ メモリ参照へのポインタにすぎません。これにより、ランダム アクセス速度が大幅に高速化されます。

s.charAt(i)では、例えばこうするとどうなるでしょうか?

sは のインスタンスではないため、StringJavaScript は を自動ボックス化しますs。これはtypeof string、そのラッパー タイプ でありStringtypeof objectより正確にはs.valueOf(s).prototype.toString.call = [object String]です。

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

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

于 2013-06-22T23:34:19.763 に答える
36

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

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

一方、String オブジェクトは、v8::StringObject拡張された に解析さObjectれ、本格的なオブジェクトであるだけでなく、 のラッパーとして機能しますv8::String

これは論理的なものに過ぎず、メソッドを実行する前にnew String('').method()this をボックス化解除する必要があるため、遅くなります。v8::StringObjectv8::String


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

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

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

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

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

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

プリミティブから離れれば離れるほど、到達するのに時間がかかります。実際には、Stringプリミティブは s よりもはるかに頻繁に使用されるため、 MDN の説明が示唆するようにとのStringObject間で変換を行う代わりに、エンジンが String プリミティブの対応する (解釈された) オブジェクトのクラスにメソッドを追加することは驚くことではありません。StringStringObject


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

于 2013-06-22T23:45:51.723 に答える
19

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

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
于 2016-04-14T06:01:15.390 に答える
14

文字列リテラル:

文字列リテラルは不変です。つまり、いったん作成されると、その状態を変更することはできず、スレッド セーフにもなります。

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

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

文字列オブジェクト:

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

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

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

于 2014-06-20T05:53:56.563 に答える
10

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

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

コメントに記載されているように、それらは異なるタイプであるため、JavaScriptインタープリターはそれらを異なる方法で最適化することもできます。

于 2013-06-22T23:16:50.653 に答える
7

宣言する場合:

var s = '0123456789';

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

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

于 2013-06-22T23:15:14.407 に答える
3

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

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

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

于 2013-06-22T23:30:13.980 に答える
3

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

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

于 2018-06-03T09:18:15.690 に答える
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');すると、引数を取り、関数スコープ内に空のオブジェクトを作成する関数コンストラクターです。この空のオブジェクトがこれに割り当てられます。この場合、文字列は、前述の既知の組み込み関数をすべて提供します。操作が完了するとすぐに、たとえば大文字操作を行うと、ラッパーオブジェクトは破棄されます。

それを証明するために、次のようにしましょう。

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
于 2020-05-07T17:58:56.350 に答える