สตริง JavaScript เปลี่ยนแปลงไม่ได้หรือไม่? ฉันต้องการ“ เครื่องมือสร้างสตริง” ใน JavaScript หรือไม่


237

จาวาสคริปต์ใช้สตริงที่ไม่เปลี่ยนรูปหรือไม่แน่นอนหรือไม่? ฉันต้องการ "เครื่องมือสร้างสตริง" หรือไม่?


3
ใช่ y ไม่เปลี่ยนรูปและคุณต้องมี "ตัวสร้างสตริง" ของบางประเภท อ่านblog.codeeffects.com/Article/String-Builder-In-Java-Scriptนี้หรือcodeproject.com/KB/scripting/stringbuilder.aspx
Kizz

2
น่าสนใจตัวอย่างเหล่านั้นขัดแย้งกับสิ่งที่ฉันค้นพบในคำตอบของฉัน
Juan Mendes

คำตอบ:


294

พวกมันไม่เปลี่ยนรูป var myString = "abbdef"; myString[2] = 'c'คุณไม่สามารถเปลี่ยนเป็นตัวละครที่อยู่ในสตริงกับสิ่งที่ต้องการ วิธีการจัดการสตริงเช่นtrim, sliceกลับสตริงใหม่

ในทำนองเดียวกันหากคุณมีสองการอ้างอิงไปยังสตริงเดียวกันการแก้ไขอย่างใดอย่างหนึ่งจะไม่ส่งผลต่ออีก

let a = b = "hello";
a = a + " world";
// b is not affected

อย่างไรก็ตามฉันมักจะได้ยินสิ่งที่ Ash พูดถึงในคำตอบของเขา (การใช้ Array.join นั้นเร็วกว่าสำหรับการต่อข้อมูล) ดังนั้นฉันจึงต้องการทดสอบวิธีการต่างๆในการเชื่อมสตริงและสรุปวิธีที่เร็วที่สุดใน StringBuilder ฉันเขียนการทดสอบบางอย่างเพื่อดูว่านี่เป็นเรื่องจริงหรือไม่ (ไม่ใช่!)

นี่คือสิ่งที่ฉันเชื่อว่าจะเป็นวิธีที่เร็วที่สุดแม้ว่าฉันจะคิดอยู่เสมอว่าการเพิ่มวิธีการโทรอาจทำให้ช้าลง ...

function StringBuilder() {
    this._array = [];
    this._index = 0;
}

StringBuilder.prototype.append = function (str) {
    this._array[this._index] = str;
    this._index++;
}

StringBuilder.prototype.toString = function () {
    return this._array.join('');
}

นี่คือการทดสอบความเร็วประสิทธิภาพ ทั้งสามคนสร้างสตริงขนาดมหึมาที่ประกอบด้วยการต่อ"Hello diggity dog"หนึ่งแสนครั้งเป็นสตริงว่าง

ฉันสร้างการทดสอบสามประเภท

  • การใช้Array.pushและArray.join
  • การใช้การทำดัชนี Array เพื่อหลีกเลี่ยงArray.pushจากนั้นใช้Array.join
  • การต่อสตริงตรง

แล้วฉันจะสร้างสามการทดสอบโดยสรุปพวกเขาลงStringBuilderConcat, StringBuilderArrayPushและStringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5โปรดไปที่นั่นและทดสอบการทำงานเพื่อให้เราสามารถได้รับตัวอย่างที่ดี โปรดทราบว่าฉันแก้ไขข้อผิดพลาดเล็ก ๆ ดังนั้นข้อมูลสำหรับการทดสอบได้ถูกลบไปฉันจะอัปเดตตารางเมื่อมีข้อมูลประสิทธิภาพเพียงพอ ไปที่http://jsperf.com/string-concat-without-sringbuilder/5สำหรับตารางข้อมูลเก่า

นี่คือตัวเลข (อัพเดทล่าสุดใน Ma5rch 2018) หากคุณไม่ต้องการติดตามลิงค์ จำนวนในการทดสอบแต่ละครั้งอยู่ใน 1,000 การดำเนินงาน / วินาที ( สูงกว่าดีกว่า )

| Browser          | Index | Push | Concat | SBIndex | SBPush | SBConcat |
---------------------------------------------------------------------------
| Chrome 71.0.3578 | 988   | 1006 | 2902   | 963     | 1008   | 2902     |
| Firefox 65       | 1979  | 1902 | 2197   | 1917    | 1873   | 1953     |
| Edge             | 593   | 373  | 952    | 361     | 415    | 444      |
| Exploder 11      | 655   | 532  | 761    | 537     | 567    | 387      |
| Opera 58.0.3135  | 1135  | 1200 | 4357   | 1137    | 1188   | 4294     | 

ผลการวิจัย

  • ทุกวันนี้เบราว์เซอร์ที่เขียวชอุ่มตลอดเวลาสามารถจัดการการเรียงต่อสตริงได้ดี Array.joinช่วย IE 11 เท่านั้น

  • โดยรวมแล้ว Opera เร็วที่สุดเร็วกว่า Array.join 4 เท่า

  • Firefox เป็นที่สองและArray.joinช้าลงเล็กน้อยใน FF แต่ช้าลงมาก (3x) ใน Chrome

  • Chrome เป็นลำดับที่สาม แต่สตริงที่ต่อกันเร็วกว่า Array.join 3 เท่า

  • การสร้าง StringBuilder ดูเหมือนจะไม่ส่งผลกระทบต่อ perfomance มากเกินไป

หวังว่าคนอื่นจะพบว่ามีประโยชน์นี้

กรณีทดสอบที่แตกต่างกัน

เนื่องจาก @RoyTinker คิดว่าการทดสอบของฉันมีข้อบกพร่องฉันจึงสร้างเคสใหม่ที่ไม่ได้สร้างสตริงขนาดใหญ่โดยการต่อสตริงเดียวกันเข้าด้วยกันจึงใช้อักขระที่แตกต่างกันสำหรับการทำซ้ำแต่ละครั้ง การต่อสตริงยังดูเหมือนเร็วกว่าหรือเร็วกว่า มาทดสอบกันดีกว่า

ฉันขอแนะนำให้ทุกคนควรคิดถึงวิธีอื่น ๆ ในการทดสอบและอย่าลังเลที่จะเพิ่มลิงก์ใหม่ไปยังกรณีทดสอบที่แตกต่างกันด้านล่าง

http://jsperf.com/string-concat-without-sringbuilder/7


@Juan ลิงก์ที่คุณขอให้เราเข้าชมเชื่อมสตริง 112-char 30 ครั้ง นี่คือการทดสอบอีกอย่างที่อาจช่วยปรับสมดุล - Array.join เทียบกับการต่อสายอักขระกับสตริง1-char ที่แตกต่างกัน 20,000 ชุด (เข้าร่วมเร็วกว่ามากใน IE / FF) jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@ Royinker Roy, Oh Roy การทดสอบของคุณกำลังโกงเพราะคุณกำลังสร้างอาร์เรย์ในการตั้งค่าการทดสอบ นี่คือการทดสอบจริงโดยใช้อักขระที่แตกต่างjsperf.com/string-concat-without-sringbuilder/7อย่าลังเลที่จะสร้างกรณีทดสอบใหม่ แต่การสร้างอาร์เรย์เป็นส่วนหนึ่งของการทดสอบ
Juan Mendes

@ JuanMendes เป้าหมายของฉันคือการ จำกัด กรณีทดสอบให้แคบลงเพื่อเปรียบเทียบการjoinเรียงต่อกันของสตริงและดังนั้นจึงสร้างอาร์เรย์ก่อนการทดสอบ ฉันไม่คิดว่าเป็นการโกงหากเข้าใจเป้าหมายนั้น (และjoinระบุอาร์เรย์ภายในดังนั้นจึงไม่ใช่การโกงที่จะละเว้นการforวนซ้ำจากการjoinทดสอบ)
Roy Tinker

2
@RoyTinker ใช่แล้วตัวสร้างสตริงใด ๆ จะต้องสร้างอาร์เรย์ คำถามเกี่ยวกับว่าต้องการสร้างสตริงหรือไม่ หากคุณมีสายในอาร์เรย์แล้วมันไม่ได้เป็นกรณีทดสอบที่ถูกต้องสำหรับสิ่งที่เรากำลังพูดคุยที่นี่
ฮวน Mendes

1
@Baltusaj คอลัมน์ที่กล่าวว่า Index / Push กำลังใช้งานArray.join
Juan Mendes

41

จากหนังสือแรด :

ใน JavaScript สตริงเป็นวัตถุที่ไม่เปลี่ยนรูปซึ่งหมายความว่าตัวละครภายในอาจไม่สามารถเปลี่ยนแปลงได้และการดำเนินการใด ๆ กับสตริงจะสร้างสตริงใหม่ สตริงถูกกำหนดโดยการอ้างอิงไม่ใช่ตามค่า โดยทั่วไปเมื่อวัตถุถูกกำหนดโดยการอ้างอิงการเปลี่ยนแปลงที่ทำกับวัตถุผ่านการอ้างอิงหนึ่งจะสามารถมองเห็นได้ผ่านการอ้างอิงอื่น ๆ ทั้งหมดไปยังวัตถุ เนื่องจากสตริงไม่สามารถเปลี่ยนแปลงได้ แต่คุณสามารถมีการอ้างอิงหลายรายการไปยังวัตถุสตริงและไม่ต้องกังวลว่าค่าสตริงจะเปลี่ยนแปลงโดยที่คุณไม่รู้ตัว


7
เชื่อมโยงไปยังส่วนที่เหมาะสมของหนังสือเล่มแรด: books.google.com/...
baudtack

116
ใบเสนอราคาหนังสือแรด (และคำตอบนี้) ผิดที่นี่ ใน JavaScript สตริงประเภทค่าดั้งเดิมและไม่วัตถุ(ข้อมูลจำเพาะ) ในความเป็นจริงของ ES5 พวกเขาเป็นหนึ่งในเพียง 5 ประเภทค่าข้างและnull undefined number booleanสตริงถูกกำหนดโดยค่าและไม่ใช่โดยการอ้างอิงและถูกส่งผ่านเช่นนั้น ดังนั้นสตริงไม่ได้เปลี่ยนรูปเพียงแค่พวกเขามีค่า การเปลี่ยนสตริง"hello"ที่จะ"world"เป็นเหมือนการตัดสินใจว่าจากนี้ไปที่หมายเลข 3 คือหมายเลข 4 ... มันไม่สมเหตุสมผล
Benjamin Gruenbaum

8
ใช่เช่นเดียวกับความคิดเห็นของฉันบอกว่าสตริงนั้นไม่เปลี่ยนรูป แต่พวกเขาไม่ใช่ประเภทอ้างอิงหรือเป็นวัตถุ - เป็นประเภทค่าดั้งเดิม เป็นวิธีที่ง่ายที่จะเห็นพวกเขากำลังค่าจะพยายามที่จะเพิ่มคุณสมบัติที่จะสตริงแล้วอ่านมัน:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
เบนจามิน Gruenbaum

9
@ VidarS.Ramdal ไม่มีStringวัตถุที่สร้างขึ้นโดยใช้ตัวสร้างสตริงเป็นห่อรอบค่าสตริง JavaScript คุณสามารถเข้าถึงค่าสตริงของชนิดบรรจุกล่องโดยใช้.valueOf()ฟังก์ชั่นซึ่งเป็นจริงสำหรับNumberวัตถุและค่าตัวเลข มันเป็นสิ่งสำคัญที่จะต้องทราบStringวัตถุที่สร้างขึ้นโดยใช้new Stringไม่ได้สตริงที่เกิดขึ้นจริง แต่จะมีการห่อหรือกล่องรอบสตริง ดูes5.github.io/#x15.5.2.1 เกี่ยวกับวิธีที่สิ่งต่าง ๆ เปลี่ยนเป็นวัตถุให้ดูes5.github.io/#x9.9
Benjamin Gruenbaum

5
สำหรับสาเหตุที่บางคนพูดว่าสตริงเป็นวัตถุพวกเขาอาจมาจาก Python หรือ Lisp หรือภาษาอื่น ๆ ที่ข้อมูลจำเพาะของมันใช้คำว่า "วัตถุ" เพื่อหมายถึงประเภทข้อมูลใด ๆ (แม้แต่จำนวนเต็ม) พวกเขาเพียงแค่ต้องอ่านว่าข้อมูลจำเพาะ ECMA กำหนดคำว่า: "สมาชิกของประเภทวัตถุ" นอกจากนี้แม้คำว่า "ค่า" อาจหมายถึงสิ่งที่แตกต่างกันตามข้อกำหนดของภาษาที่แตกต่างกัน
Jisang Yoo

21

เคล็ดลับประสิทธิภาพ:

หากคุณต้องต่อสตริงขนาดใหญ่ใส่ส่วนสตริงลงในอาร์เรย์และใช้ Array.Join()วิธีการรับสตริงโดยรวม อาจเร็วกว่านี้หลายครั้งในการเชื่อมสตริงจำนวนมาก

ไม่มีStringBuilderใน JavaScript


ฉันรู้ว่ามีไม่ได้เป็น StringBuilder, msAjax มีหนึ่งและผมก็แค่ขบคิดหรือไม่ว่ามีประโยชน์
DevelopingChris

5
สิ่งนี้เกี่ยวข้องกับสตริงที่ไม่เปลี่ยนรูปหรือไม่?
baudtack

4
@docgnome: เพราะสายจะไม่เปลี่ยนรูป, สตริงต้องสร้างวัตถุมากกว่าวิธี Array.join
ฆ Mendes

8
จากการทดสอบของ Juan ดังกล่าวการต่อสตริงจะเร็วขึ้นทั้งใน IE และ Chrome ในขณะที่ช้ากว่าใน Firefox
Bill Yang

9
ลองอัปเดตคำตอบของคุณมันอาจจะเป็นจริงเมื่อนานมาแล้ว แต่ไม่ใช่อีกต่อไป ดูjsperf.com/string-concat-without-sringbuilder/5
Juan Mendes

8

เพียงชี้แจงให้เห็นถึงจิตใจที่เรียบง่ายเช่น Mine (จากMDN ):

Immutables เป็นวัตถุที่ไม่สามารถเปลี่ยนสถานะได้เมื่อสร้างวัตถุแล้ว

สตริงและตัวเลขไม่เปลี่ยนรูป

หมายความว่าไม่เปลี่ยนรูปที่:

คุณสามารถทำให้ชื่อตัวแปรชี้ไปที่ค่าใหม่ แต่ค่าก่อนหน้านี้ยังคงอยู่ในหน่วยความจำ ดังนั้นความต้องการในการเก็บขยะ

var immutableString = "Hello";

// ในโค้ดด้านบนวัตถุใหม่ที่มีค่าสตริงจะถูกสร้างขึ้น

immutableString = immutableString + "World";

// ตอนนี้เรากำลังผนวก "โลก" กับค่าที่มีอยู่

ดูเหมือนว่าเรากำลังกลายพันธุ์สตริง 'immutableString' แต่เราไม่ แทน:

ในการต่อท้าย "immutableString" ด้วยค่าสตริงเหตุการณ์ต่อไปนี้เกิดขึ้น:

  1. มีการดึงค่าที่มีอยู่ของ "immutableString"
  2. "World" ถูกผนวกเข้ากับค่าที่มีอยู่ของ "immutableString"
  3. จากนั้นค่าผลลัพธ์จะถูกจัดสรรให้กับบล็อกหน่วยความจำใหม่
  4. วัตถุ "immutableString" จะชี้ไปยังพื้นที่หน่วยความจำที่สร้างขึ้นใหม่
  5. ขณะนี้พื้นที่หน่วยความจำที่สร้างขึ้นก่อนหน้านี้พร้อมใช้งานสำหรับการรวบรวมขยะแล้ว

4

ค่าชนิดสตริงไม่เปลี่ยนรูปแต่วัตถุสตริงซึ่งสร้างขึ้นโดยใช้ตัวสร้าง String () สามารถเปลี่ยนแปลงได้เนื่องจากเป็นวัตถุและคุณสามารถเพิ่มคุณสมบัติใหม่ได้

> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }

ในขณะที่แม้ว่าคุณจะสามารถเพิ่มคุณสมบัติใหม่ได้ แต่คุณไม่สามารถเปลี่ยนคุณสมบัติที่มีอยู่แล้วได้

ภาพหน้าจอของการทดสอบในคอนโซล Chrome

โดยสรุป 1. ค่าสตริงทั้งหมด (ชนิดดั้งเดิม) ไม่มีการเปลี่ยนแปลง 2. วัตถุ String ไม่สามารถเปลี่ยนแปลงได้ แต่ค่าชนิดสตริง (ชนิดดั้งเดิม) ที่มีอยู่ไม่เปลี่ยนรูป


วัตถุสตริง Javascript เป็นdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Data_structures ที่
prasun

@prasun แต่ในหน้านั้นบอกว่า: "ทุกประเภทยกเว้นวัตถุกำหนดค่าที่ไม่เปลี่ยนรูปแบบ (ค่าซึ่งไม่สามารถเปลี่ยนแปลงได้)" วัตถุสตริงเป็นวัตถุ และมันจะไม่เปลี่ยนรูปแบบอย่างไรถ้าคุณสามารถเพิ่มคุณสมบัติใหม่ได้?
zhanziyang

อ่านส่วน "ประเภทสตริง" ลิงก์สตริงของ Javascript ชี้ไปที่ทั้งดั้งเดิมและวัตถุและต่อมาก็ระบุว่า "สตริง JavaScript นั้นไม่สามารถเปลี่ยนแปลงได้" ดูเหมือนว่าเอกสารจะไม่ชัดเจนในหัวข้อนี้เนื่องจากมันขัดแย้งกับสองโน้ตที่แตกต่างกัน
prasun

6
new Stringสร้าง wrapper ที่ไม่แน่นอนรอบ ๆ สตริงที่ไม่เปลี่ยนรูปแบบ
tomdemuyt

1
มันง่ายมากที่จะทดสอบโดยใช้รหัสของ @ zhanziyang ด้านบน คุณสามารถเพิ่มคุณสมบัติใหม่ทั้งหมดลงในStringวัตถุ (wrapper) ซึ่งหมายความว่ามันไม่เปลี่ยนรูป (โดยค่าเริ่มต้น; เช่นเดียวกับวัตถุอื่น ๆ ที่คุณสามารถเรียกObject.freezeมันเพื่อทำให้มันไม่เปลี่ยนรูป) แต่ชนิดของค่าสตริงดั้งเดิมไม่ว่าจะอยู่ในStringwrapper วัตถุหรือไม่นั้นจะไม่เปลี่ยนรูปเสมอ
Mark Reed

3

เงื่อนไขไม่เปลี่ยนรูป - ไม่สามารถเปลี่ยนแปลงได้เราสามารถสร้างสตริงใหม่ได้เท่านั้น

ตัวอย่าง:

var str= "Immutable value"; // it is immutable

var other= statement.slice(2, 10); // new string

1

เกี่ยวกับคำถามของคุณ (ในความคิดเห็นของคุณต่อการตอบสนองของ Ash) เกี่ยวกับ StringBuilder ใน ASP.NET Ajax ผู้เชี่ยวชาญดูเหมือนจะไม่เห็นด้วยกับเรื่องนี้

Christian Wenz กล่าวในหนังสือของเขาที่ชื่อว่าASP.NET AJAX (O'Reilly) ว่า "วิธีนี้ไม่มีผลใด ๆ ที่วัดได้ในหน่วยความจำ (อันที่จริงการใช้งานดูเหมือนว่าจะช้ากว่ามาตรฐาน)"

ในทางตรงกันข้าม Gallo et al พูดในหนังสือASP.NET AJAX in Action (Manning) ว่า "เมื่อจำนวนของสตริงที่เชื่อมต่อกันมีขนาดใหญ่ขึ้นตัวสร้างสตริงจะกลายเป็นวัตถุสำคัญเพื่อหลีกเลี่ยงการลดลงของประสิทธิภาพการทำงานที่มาก"

ฉันเดาว่าคุณต้องทำการเปรียบเทียบและผลลัพธ์อาจแตกต่างกันระหว่างเบราว์เซอร์เช่นกัน อย่างไรก็ตามแม้ว่าจะไม่ปรับปรุงประสิทธิภาพ แต่ก็อาจถือว่าเป็น "มีประโยชน์" สำหรับโปรแกรมเมอร์ที่ใช้ในการเข้ารหัสด้วย StringBuilders ในภาษาเช่น C # หรือ Java


1

มันเป็นการโพสต์ล่าช้า แต่ฉันไม่พบคำพูดที่ดีในคำตอบ

นี่คือหนังสือที่ชัดเจนยกเว้นจากหนังสือที่เชื่อถือได้:

สตริงไม่สามารถเปลี่ยนแปลงได้ใน ECMAScript หมายความว่าเมื่อสร้างแล้วค่าจะไม่สามารถเปลี่ยนแปลงได้ ในการเปลี่ยนสตริงที่ถือโดยตัวแปรสตริงเดิมจะต้องถูกทำลายและตัวแปรที่เต็มไปด้วยสตริงอื่นที่มีค่าใหม่ ... —Professional JavaScript สำหรับนักพัฒนาเว็บ, 3rd Ed., p.43

ตอนนี้คำตอบที่ตัดตอนมาจากหนังสือของ Rhino นั้นถูกต้องเกี่ยวกับการผันแปรของสตริง แต่ไม่ถูกต้องว่า "สตริงถูกกำหนดโดยการอ้างอิงไม่ใช่ตามค่า" (บางทีพวกเขาอาจหมายถึงการวางคำในทางตรงกันข้าม)

ความเข้าใจผิด "อ้างอิง / ค่า" ได้รับการอธิบายใน "Professional JavaScript" บทที่ชื่อ "ค่าดั้งเดิมและค่าอ้างอิง":

รูปแบบดั้งเดิมห้าประเภท ... [คือ]: ไม่ได้กำหนด, Null, Boolean, Number และ String ตัวแปรเหล่านี้ถูกกล่าวถึงว่าสามารถเข้าถึงได้โดยค่าเพราะคุณกำลังจัดการค่าจริงที่เก็บไว้ในตัวแปร —Professional JavaScript สำหรับนักพัฒนาเว็บรุ่นที่ 3, หน้า 82

ตรงข้ามกับวัตถุ :

เมื่อคุณจัดการกับวัตถุคุณกำลังทำงานกับการอ้างอิงไปยังวัตถุนั้นมากกว่าที่จะเป็นวัตถุจริง ด้วยเหตุผลนี้ค่าดังกล่าวถูกกล่าวถึงโดยการอ้างอิง —Professional JavaScript สำหรับนักพัฒนาเว็บรุ่นที่ 3, หน้า 82


FWIW: หนังสือ Rhino อาจหมายความว่าภายใน / การใช้งานการกำหนดสตริงกำลังจัดเก็บ / คัดลอกตัวชี้ (แทนที่จะคัดลอกเนื้อหาของสตริง) มันดูไม่เหมือนอุบัติเหตุในส่วนของพวกเขาตามข้อความหลังจากนั้น แต่ฉันเห็นด้วย: พวกเขาใช้คำว่า "โดยอ้างอิง" ในทางที่ผิด มันไม่ได้ "โดยการอ้างอิง" เพียงเพราะการใช้งานผ่านตัวชี้ (สำหรับประสิทธิภาพ) วิกิพีเดีย - กลยุทธ์การประเมินผลเป็นอ่านที่น่าสนใจในหัวข้อนี้
ToolmakerSteve

-1

สตริง JavaScript นั้นไม่เปลี่ยนรูปแบบแน่นอน


ลิงก์เอกสาร ฉันไม่สามารถหาข้อมูลเฉพาะเกี่ยวกับสิ่งนี้
กำลังพัฒนา

1
"ความพยายามในการตั้งค่าอักขระแต่ละตัวจะไม่ทำงาน" - developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Ken

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.