Các chuỗi JavaScript có bất biến không? Tôi có cần một trình xây dựng chuỗi Tiếng Việt trong JavaScript không?


237

Liệu javascript sử dụng chuỗi bất biến hoặc đột biến? Tôi có cần một "trình tạo chuỗi" không?


3
Vâng, y là bất biến và bạn cần một "trình tạo chuỗi" nào đó. Đọc blog.codeeffects.com/Article/String-Builder-In-Java-Script hay này codeproject.com/KB/scripting/stringbuilder.aspx
kizz

2
Thật thú vị, những ví dụ đó mâu thuẫn với những phát hiện của tôi trong câu trả lời của tôi.
Juan Mendes

Câu trả lời:


294

Họ là bất biến. Bạn không thể thay đổi một ký tự trong một chuỗi bằng một cái gì đó như var myString = "abbdef"; myString[2] = 'c'. Các phương thức thao tác chuỗi như trim, slicetrả về chuỗi mới.

Theo cùng một cách, nếu bạn có hai tham chiếu đến cùng một chuỗi, việc sửa đổi một tham chiếu sẽ không ảnh hưởng đến chuỗi khác

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

Tuy nhiên, tôi đã luôn nghe những gì Ash đề cập trong câu trả lời của anh ấy (rằng sử dụng Array.join nhanh hơn để nối), vì vậy tôi muốn thử nghiệm các phương pháp nối chuỗi khác nhau và trừu tượng hóa cách nhanh nhất vào StringBuilder. Tôi đã viết một số thử nghiệm để xem điều này có đúng không (không phải vậy!).

Đây là những gì tôi tin sẽ là cách nhanh nhất, mặc dù tôi cứ nghĩ rằng việc thêm một cuộc gọi phương thức có thể làm cho nó chậm hơn ...

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('');
}

Dưới đây là các bài kiểm tra tốc độ hiệu suất. Tất cả ba người trong số họ tạo ra một chuỗi khổng lồ được tạo thành từ việc nối "Hello diggity dog"một trăm ngàn lần thành một chuỗi trống.

Tôi đã tạo ra ba loại thử nghiệm

  • Sử dụng Array.pushArray.join
  • Sử dụng lập chỉ mục mảng để tránh Array.push , sau đó sử dụngArray.join
  • Nối chuỗi thẳng

Sau đó, tôi đã tạo ra cùng ba bài kiểm tra bằng cách trừu tượng chúng vào StringBuilderConcat, StringBuilderArrayPushStringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Hãy đến đó và chạy thử nghiệm vì vậy chúng tôi có thể nhận được một mẫu tốt đẹp. Lưu ý rằng tôi đã sửa một lỗi nhỏ, vì vậy dữ liệu cho các bài kiểm tra đã bị xóa, tôi sẽ cập nhật bảng sau khi có đủ dữ liệu hiệu suất. Đi đến http://jsperf.com/string-concat-without-sringbuilder/5 cho bảng dữ liệu cũ.

Dưới đây là một số con số (Cập nhật mới nhất trong Ma5rch 2018), nếu bạn không muốn theo liên kết. Con số trên mỗi bài kiểm tra là trong 1000 thao tác / giây ( cao hơn là tốt hơn )

| 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     | 

Kết quả

  • Ngày nay, tất cả các trình duyệt thường xanh xử lý nối chuỗi tốt. Array.joinchỉ giúp IE 11

  • Nhìn chung, Opera nhanh nhất, nhanh gấp 4 lần Array.join

  • Firefox đứng thứ hai và Array.joinchỉ chậm hơn một chút trong FF nhưng chậm hơn đáng kể (3x) trong Chrome.

  • Chrome đứng thứ ba nhưng chuỗi concat nhanh hơn 3 lần so với Array.join

  • Tạo một StringBuilder dường như không ảnh hưởng quá nhiều đến độ hoàn hảo.

Hy vọng ai đó thấy điều này hữu ích

Trường hợp kiểm tra khác nhau

Vì @RoyTinker nghĩ rằng thử nghiệm của tôi không hoàn hảo, tôi đã tạo ra một trường hợp mới không tạo ra một chuỗi lớn bằng cách nối cùng một chuỗi, nó sử dụng một ký tự khác nhau cho mỗi lần lặp. Nối chuỗi vẫn có vẻ nhanh hơn hoặc nhanh như vậy. Hãy chạy thử nghiệm.

Tôi đề nghị mọi người nên tiếp tục nghĩ đến những cách khác để kiểm tra điều này và thoải mái thêm các liên kết mới vào các trường hợp thử nghiệm khác nhau bên dưới.

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


@Juan, liên kết bạn yêu cầu chúng tôi ghé thăm nối chuỗi 112-char 30 lần. Đây là một thử nghiệm khác có thể giúp cân bằng mọi thứ - Array.join vs nối chuỗi trên 20.000 chuỗi 1 char khác nhau (tham gia nhanh hơn nhiều trên IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@RoyTinker Roy, oh Roy, các bài kiểm tra của bạn gian lận vì bạn đang tạo mảng trong quá trình thiết lập bài kiểm tra. Đây là thử nghiệm thực tế bằng cách sử dụng các ký tự khác nhau jsperf.com/opes-concat-without-sringbuilder/7 Hãy thoải mái tạo các trường hợp thử nghiệm mới, nhưng việc tạo mảng là một phần của chính thử nghiệm
Juan Mendes

@JuanMendes Mục tiêu của tôi là thu hẹp trường hợp thử nghiệm để so sánh chặt chẽ giữa joinnối chuỗi với chuỗi, do đó xây dựng mảng trước khi thử nghiệm. Tôi không nghĩ đó là gian lận nếu mục tiêu đó được hiểu (và joinliệt kê mảng bên trong, vì vậy, cũng không phải là gian lận để bỏ qua một forvòng lặp từ joinbài kiểm tra).
Roy Tinker

2
@RoyTinker Đúng vậy, bất kỳ nhà xây dựng chuỗi nào cũng sẽ yêu cầu xây dựng mảng. Câu hỏi là về việc có cần xây dựng chuỗi không. Nếu bạn đã có các chuỗi trong một mảng, thì đó không phải là trường hợp thử nghiệm hợp lệ cho những gì chúng ta đang thảo luận ở đây
Juan Mendes

1
@Baltusaj Các cột cho biết Index / Push đang sử dụngArray.join
Juan Mendes

41

từ cuốn sách tê giác :

Trong JavaScript, các chuỗi là các đối tượng bất biến, có nghĩa là các ký tự bên trong chúng có thể không bị thay đổi và bất kỳ thao tác nào trên chuỗi thực sự tạo ra các chuỗi mới. Chuỗi được gán bởi tham chiếu, không phải theo giá trị. Nói chung, khi một đối tượng được gán bởi tham chiếu, một thay đổi được thực hiện cho đối tượng thông qua một tham chiếu sẽ hiển thị thông qua tất cả các tham chiếu khác đến đối tượng. Tuy nhiên, vì các chuỗi không thể thay đổi, bạn có thể có nhiều tham chiếu đến một đối tượng chuỗi và không lo lắng rằng giá trị chuỗi sẽ thay đổi mà bạn không biết


7
Liên kết đến một phần thích hợp của cuốn sách tê giác: books.google.com/...
baudtack

116
Trích dẫn cuốn sách Rhino (và do đó câu trả lời này) là sai ở đây. Trong chuỗi JavaScript là các loại giá trị nguyên thủy và không phải là đối tượng (spec) . Trên thực tế, kể từ ES5, chúng là một trong 5 loại giá trị duy nhất bên cạnh null undefined numberboolean. Strings được sự phân công của giá trịkhông bằng cách tham khảo và được thông qua như vậy. Do đó, chuỗi không chỉ là bất biến, chúng là một giá trị . Thay đổi chuỗi "hello"thành "world"giống như quyết định rằng từ bây giờ số 3 là số 4 ... thật vô nghĩa.
Benjamin Gruenbaum

8
Vâng, như nhận xét của tôi nói rằng chuỗi bất biến, nhưng chúng không phải là kiểu tham chiếu cũng không phải là đối tượng - chúng là kiểu giá trị nguyên thủy. Một cách dễ dàng để thấy họ sẽ không cố gắng thêm thuộc tính vào chuỗi và sau đó đọc nó:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum

9
@ VidarS.Ramdal Không, Stringcác đối tượng được tạo bằng hàm tạo chuỗi là các hàm bao quanh các giá trị chuỗi JavaScript. Bạn có thể truy cập giá trị chuỗi của loại được đóng hộp bằng .valueOf()hàm - điều này cũng đúng với Numbercác đối tượng và giá trị số. Điều quan trọng cần lưu ý là Stringcác đối tượng được tạo bằng cách sử dụng new Stringkhông phải là chuỗi thực tế mà là trình bao bọc hoặc hộp xung quanh chuỗi. Xem es5.github.io/#x15.5.2.1 . Về cách mọi thứ chuyển đổi thành các đối tượng, hãy xem es5.github.io/#x9.9
Benjamin Gruenbaum

5
Về lý do tại sao một số người nói các chuỗi là các đối tượng, có lẽ chúng đến từ Python hoặc Lisp hoặc bất kỳ ngôn ngữ nào khác mà thông số kỹ thuật của nó sử dụng từ "đối tượng" để chỉ bất kỳ loại dữ liệu nào (thậm chí cả số nguyên). Họ chỉ cần đọc cách đặc tả ECMA định nghĩa từ: "thành viên của loại Đối tượng". Ngoài ra, ngay cả từ "giá trị" cũng có thể có nghĩa là những thứ khác nhau theo thông số kỹ thuật của các ngôn ngữ khác nhau.
Jisang Yoo

21

Mẹo hiệu suất:

Nếu bạn phải nối các chuỗi lớn, đặt các phần chuỗi vào một mảng và sử dụng Array.Join()phương thức để có được chuỗi tổng thể. Điều này có thể nhanh hơn nhiều lần để nối một số lượng lớn các chuỗi.

Không có StringBuilderJavaScript.


Tôi biết có không phải là một StringBuilder, msAjax có một, và tôi đã chỉ cân nhắc có hay không nó hữu ích
DevelopingChris

5
Điều này có liên quan gì với chuỗi là bất biến hay không?
baudtack

4
@docgnome: Vì các chuỗi là bất biến, nên nối chuỗi yêu cầu tạo nhiều đối tượng hơn phương pháp Array.join
Juan Mendes

8
Theo thử nghiệm của Juan ở trên, nối chuỗi thực sự nhanh hơn trong cả IE và Chrome, trong khi chậm hơn trong Firefox.
Bill Yang

9
Hãy xem xét cập nhật câu trả lời của bạn, nó có thể đã đúng từ lâu, nhưng nó không còn nữa. Xem jsperf.com/opes-concat-without-sringbuilder/5
Juan Mendes

8

Chỉ cần làm rõ cho những tâm trí đơn giản như của tôi (từ MDN ):

Bất biến là các đối tượng có trạng thái không thể thay đổi sau khi đối tượng được tạo.

Chuỗi và số là bất biến.

Bất biến có nghĩa là:

Bạn có thể đặt một điểm tên biến thành một giá trị mới, nhưng giá trị trước đó vẫn được giữ trong bộ nhớ. Do đó cần thu gom rác.

var immutableString = "Hello";

// Trong đoạn mã trên, một đối tượng mới có giá trị chuỗi được tạo.

immutableString = immutableString + "World";

// Chúng tôi hiện đang nối thêm "Thế giới" vào giá trị hiện có.

Điều này có vẻ như chúng ta đang thay đổi chuỗi 'bất biếnString', nhưng chúng ta thì không. Thay thế:

Khi nối thêm "bất biến" với giá trị chuỗi, các sự kiện sau xảy ra:

  1. Giá trị hiện tại của "infutableString" được lấy
  2. "Thế giới" được gắn vào giá trị hiện có của "bất biến"
  3. Giá trị kết quả sau đó được phân bổ cho một khối bộ nhớ mới
  4. Bây giờ đối tượng "infutableString" trỏ đến không gian bộ nhớ vừa tạo
  5. Không gian bộ nhớ được tạo trước đây hiện có sẵn để thu gom rác.

4

Giá trị loại chuỗi là bất biến, nhưng đối tượng String, được tạo bằng cách sử dụng hàm tạo String (), có thể thay đổi, bởi vì nó là một đối tượng và bạn có thể thêm các thuộc tính mới vào nó.

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

Trong khi đó, mặc dù bạn có thể thêm các thuộc tính mới, bạn không thể thay đổi các thuộc tính đã có

Ảnh chụp màn hình kiểm tra trong bảng điều khiển Chrome

Tóm lại, 1. tất cả giá trị loại chuỗi (kiểu nguyên thủy) là bất biến. 2. Đối tượng String có thể thay đổi, nhưng giá trị loại chuỗi (kiểu nguyên thủy) mà nó chứa là không thay đổi.


Các đối tượng Chuỗi Javascript là nhà phát triển
biến.mozilla.org/en

@prasun nhưng trong trang đó có ghi: "Tất cả các loại trừ các đối tượng xác định các giá trị bất biến (các giá trị không có khả năng bị thay đổi)." Các đối tượng chuỗi là đối tượng. Và làm thế nào là bất biến nếu bạn có thể thêm các thuộc tính mới trên nó?
zhanziyang

đọc phần "Kiểu chuỗi". Liên kết Chuỗi của Javascript trỏ đến cả nguyên thủy và Đối tượng và sau đó nó nói "Chuỗi JavaScript là bất biến". Có vẻ như tài liệu không rõ ràng về chủ đề này vì nó mâu thuẫn ở hai ghi chú khác nhau
prasun

6
new Stringtạo ra một trình bao bọc có thể thay đổi xung quanh một chuỗi bất biến
tomdemuyt

1
Rất dễ kiểm tra bằng cách chạy mã của @ zhanziyang ở trên. Bạn hoàn toàn có thể thêm các thuộc tính mới vào một Stringđối tượng (trình bao bọc), có nghĩa là nó không bất biến (theo mặc định; giống như bất kỳ đối tượng nào khác mà bạn có thể gọi Object.freezetrên đó để hiển thị nó không thay đổi). Nhưng một loại giá trị chuỗi nguyên thủy, cho dù có chứa trong một Stringtrình bao bọc đối tượng hay không, luôn luôn là bất biến.
Mark Reed

3

Chuỗi là bất biến - chúng không thể thay đổi, chúng ta chỉ có thể tạo chuỗi mới.

Thí dụ:

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

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

1

Về câu hỏi của bạn (trong nhận xét của bạn về câu trả lời của Ash) về StringBuilder trong ASP.NET Ajax, các chuyên gia dường như không đồng ý với câu hỏi này.

Christian Wenz nói trong cuốn sách Lập trình ASP.NET AJAX (O'Reilly) của mình rằng "cách tiếp cận này không có bất kỳ ảnh hưởng nào đến bộ nhớ (trên thực tế, việc triển khai dường như chậm hơn so với cách tiếp cận tiêu chuẩn)."

Mặt khác, Gallo và cộng sự đã nói trong cuốn sách ASP.NET AJAX in Action (Manning) của họ rằng "Khi số lượng chuỗi để nối lớn hơn, trình tạo chuỗi trở thành một đối tượng thiết yếu để tránh hiệu suất giảm rất lớn."

Tôi đoán bạn cũng cần phải làm điểm chuẩn của riêng mình và kết quả cũng có thể khác nhau giữa các trình duyệt. Tuy nhiên, ngay cả khi nó không cải thiện hiệu năng, nó vẫn có thể được coi là "hữu ích" cho các lập trình viên đã quen với việc mã hóa với StringBuilders bằng các ngôn ngữ như C # hoặc Java.


1

Đó là một bài viết muộn, nhưng tôi đã không tìm thấy một trích dẫn cuốn sách hay trong số các câu trả lời.

Đây là một định nghĩa ngoại trừ từ một cuốn sách đáng tin cậy:

Các chuỗi là bất biến trong ECMAScript, có nghĩa là một khi chúng được tạo, các giá trị của chúng không thể thay đổi. Để thay đổi chuỗi được giữ bởi một biến, chuỗi gốc phải bị hủy và biến chứa đầy một chuỗi khác chứa giá trị mới ... JavaScriptProf Professional JavaScript dành cho nhà phát triển web, Ed 3, tr.43

Bây giờ, câu trả lời trích dẫn đoạn trích của cuốn sách Rhino là đúng về tính bất biến của chuỗi nhưng nói sai "Chuỗi được gán bởi tham chiếu, không phải theo giá trị." (có lẽ ban đầu họ có nghĩa là đặt các từ theo cách ngược lại).

Quan niệm sai lầm "tham chiếu / giá trị" được làm rõ trong "JavaScript chuyên nghiệp", chương có tên "Giá trị nguyên thủy và tham chiếu":

Năm loại nguyên thủy ... [là]: Không xác định, Null, Boolean, Number và String. Các biến này được cho là được truy cập theo giá trị, bởi vì bạn đang thao túng giá trị thực được lưu trữ trong biến. JavaScriptProf Professional JavaScript dành cho nhà phát triển web, Ed 3, trang 85

điều đó trái ngược với các đối tượng :

Khi bạn thao tác với một đối tượng, bạn thực sự đang làm việc trên một tham chiếu đến đối tượng đó chứ không phải chính đối tượng thực tế. Vì lý do này, các giá trị như vậy được cho là được truy cập bằng cách tham chiếu. JavaScriptProf Professional JavaScript dành cho nhà phát triển web, Ed 3, trang 85


FWIW: Cuốn sách Rhino có thể có nghĩa là bên trong / thực hiện một phép gán chuỗi đang lưu trữ / sao chép một con trỏ (thay vì sao chép nội dung của chuỗi). Nó không giống như một tai nạn từ phía họ, dựa trên văn bản sau đó. Nhưng tôi đồng ý: họ sử dụng sai thuật ngữ "bằng cách tham khảo". Nó không "bằng cách tham chiếu" chỉ vì việc triển khai vượt qua con trỏ (cho hiệu suất). Wiki - chiến lược đánh giá là một đọc thú vị về chủ đề này.
ToolmakerSteve


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.