Sự khác biệt giữa nguyên mẫu chuỗi và đối tượng chuỗi trong JavaScript là gì?


116

Lấy từ MDN

Các chuỗi ký tự (được biểu thị bằng dấu ngoặc kép hoặc đơn) và các chuỗi được trả về từ các lệnh gọi Chuỗi trong ngữ cảnh không phải là hàm tạo (tức là không sử dụng từ khóa mới) là các chuỗi nguyên thủy. JavaScript tự động chuyển đổi các nguyên mẫu thành các đối tượng Chuỗi, để có thể sử dụng các phương thức đối tượng Chuỗi cho các chuỗi nguyên thủy. Trong các ngữ cảnh mà một phương thức sẽ được gọi trên một chuỗi nguyên thủy hoặc xảy ra tra cứu thuộc tính, JavaScript sẽ tự động bọc chuỗi nguyên thủy và gọi phương thức hoặc thực hiện tra cứu thuộc tính.

Vì vậy, tôi nghĩ (về mặt logic) các hoạt động (gọi phương thức) trên chuỗi nguyên thủy sẽ chậm hơn các hoạt động trên Đối tượng chuỗi vì bất kỳ nguyên thủy chuỗi nào cũng được chuyển đổi thành Đối tượng chuỗi (công việc bổ sung) trước khi methodđược áp dụng trên chuỗi.

Nhưng trong trường hợp thử nghiệm này , kết quả là ngược lại. Khối mã-1 chạy nhanh hơn khối mã-2 , cả hai khối mã được đưa ra bên dưới:

mã khối-1:

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

mã khối-2:

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

Kết quả khác nhau trên các trình duyệt nhưng khối mã 1 luôn nhanh hơn. Bất cứ ai có thể vui lòng giải thích điều này, tại sao mã khối-1 nhanh hơn mã khối-2 .


6
Sử dụng new Stringgiới thiệu một lớp trong suốt khác của gói Object . typeof new String(); //"object"
Paul S.

thì '0123456789'.charAt(i)sao?
Yuriy Galanter

@YuriyGalanter, nó không phải là một vấn đề nhưng tôi đang hỏi tại sao code block-1nhanh hơn?
The Alpha

2
Các đối tượng chuỗi tương đối hiếm khi được nhìn thấy trong bối cảnh cuộc sống thực, do đó không có gì ngạc nhiên khi các trình thông dịch tối ưu hóa các ký tự chuỗi. Ngày nay, mã của bạn không chỉ được diễn giải đơn giản , có rất nhiều lớp tối ưu hóa diễn ra đằng sau hậu trường.
Fabrício Matté

2
Đây là kỳ lạ: phiên bản 2
hjpotter92

Câu trả lời:


149

JavaScript có hai loại chính, primivites và object.

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

Các mẫu báo giá đơn / báo giá kép giống hệt nhau về chức năng. Ngoài ra, hành vi mà bạn đang cố gắng đặt tên được gọi là tự động đấm bốc. Vì vậy, những gì thực sự xảy ra là một nguyên thủy được chuyển đổi thành loại trình bao bọc của nó khi một phương thức của loại trình bao bọc được gọi. Đặt đơn giản:

var s = 'test';

Là một kiểu dữ liệu nguyên thủy. Nó không có phương thức, nó chỉ là một con trỏ trỏ đến một tham chiếu bộ nhớ dữ liệu thô, điều này giải thích cho tốc độ truy cập ngẫu nhiên nhanh hơn nhiều.

Vậy điều gì xảy ra khi bạn làm s.charAt(i)như vậy?

skhông phải là một phiên bản của String, JavaScript sẽ tự động đóng hộp s, cótypeof string loại trình bao bọc của nó String, với typeof objecthoặc chính xác hơn s.valueOf(s).prototype.toString.call = [object String].

Hành vi tự động đấm bốc truyền squa lại đến loại trình bao bọc của nó khi cần thiết, nhưng các hoạt động tiêu chuẩn cực kỳ nhanh vì bạn đang xử lý loại dữ liệu đơn giản hơn. Tuy nhiên tự động đấm bốc và Object.prototype.valueOfcó tác dụng khác nhau.

Nếu bạn muốn ép quyền tự động hoặc truyền một nguyên thủy sang loại trình bao bọc của nó, bạn có thể sử dụng Object.prototype.valueOf, nhưng hành vi thì khác. Dựa trên nhiều tình huống thử nghiệm, tính năng tự động quyền anh chỉ áp dụng các phương pháp 'bắt buộc' mà không làm thay đổi bản chất nguyên thủy của biến. Đó là lý do tại sao bạn có được tốc độ tốt hơn.


33

Điều này khá phụ thuộc vào việc triển khai, nhưng tôi sẽ chụp lại. Tôi sẽ làm ví dụ với V8 nhưng tôi cho rằng các động cơ khác sử dụng các cách tiếp cận tương tự.

Một nguyên thủy chuỗi được phân tích cú pháp thành một v8::Stringđối tượng. Do đó, các phương thức có thể được gọi trực tiếp trên nó như jfriend00 đã đề cập .

Mặt khác, một đối tượng String được phân tích cú pháp thành một đối tượng v8::StringObjectmở rộng Objectvà ngoài việc là một đối tượng chính thức đầy đủ, nó còn đóng vai trò là một trình bao bọc v8::String.

Bây giờ nó chỉ là hợp lý, một lời kêu gọi new String('').method()phải Unbox này v8::StringObjectv8::Stringtrước khi thực hiện phương pháp này, do đó nó là chậm hơn.


Trong nhiều ngôn ngữ khác, các giá trị nguyên thủy không có phương thức.

Cách MDN đặt nó có vẻ là cách đơn giản nhất để giải thích cách hoạt động của tính năng tự động đấm bốc của nguyên thủy (như cũng được đề cập trong câu trả lời của flav ), tức là cách các giá trị y nguyên thủy của JavaScript có thể gọi ra các phương thức.

Tuy nhiên, một công cụ thông minh sẽ không chuyển đổi một chuỗi nguyên thủy-y thành đối tượng Chuỗi mỗi khi bạn cần gọi một phương thức. Điều này cũng được đề cập một cách đầy đủ trong thông số Annotated ES5. liên quan đến việc phân giải các thuộc tính (và "phương thức" ¹) của các giá trị nguyên thủy:

CHÚ THÍCH : Không thể truy cập đối tượng có thể được tạo trong bước 1 bên ngoài phương pháp trên. Một triển khai có thể chọn để tránh việc tạo đối tượng thực sự. [...]

Ở mức rất thấp, các Chuỗi thường được triển khai dưới dạng các giá trị vô hướng bất biến. Cấu trúc trình bao bọc mẫu:

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

Bạn càng xa bản gốc, bạn sẽ mất nhiều thời gian hơn để đạt được nó. Trong thực tế, Stringcác nguyên thủy thường xuyên hơn StringObjects, do đó không có gì ngạc nhiên khi các engine thêm các phương thức vào Lớp đối tượng tương ứng (được thông dịch) của String nguyên thủy thay vì chuyển đổi qua lại giữa StringStringObjectnhư lời giải thích của MDN.


¹ Trong JavaScript, "phương thức" chỉ là quy ước đặt tên cho thuộc tính phân giải thành giá trị của hàm kiểu.


1
Không có gì. =]Bây giờ tôi đang tự hỏi liệu lời giải thích của MDN có ở đó không chỉ vì nó có vẻ là cách dễ hiểu nhất về tự động đấm bốc hoặc liệu có bất kỳ tham chiếu nào đến nó trong thông số kỹ thuật ES hay không .. Đọc toàn bộ thông số kỹ thuật vào lúc này để kiểm tra, bạn sẽ nhớ cập nhật câu trả lời nếu tôi tìm thấy một tài liệu tham khảo.
Fabrício Matté

Cái nhìn sâu sắc về việc triển khai V8. Tôi sẽ nói thêm rằng quyền anh không chỉ ở đó để giải quyết chức năng. Nó cũng ở đó để chuyển tham chiếu này vào phương thức. Bây giờ tôi không chắc liệu V8 có bỏ qua điều này cho các phương thức tích hợp hay không nhưng nếu bạn thêm tiện ích mở rộng của riêng mình để nói String.prototype, bạn sẽ nhận được phiên bản đóng hộp của đối tượng chuỗi mỗi khi nó được gọi.
Ben

17

Trong trường hợp chuỗi ký tự, chúng tôi không thể gán thuộc tính

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

Trong khi trong trường hợp Đối tượng chuỗi, chúng ta có thể gán các thuộc tính

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

1
Cuối cùng, ai đó thúc đẩy lý do tại sao chúng ta cần Stringđồ vật. Cảm ơn bạn!
Ciprian Tomoiagă

1
Tại sao mọi người có nhu cầu làm điều này?
Aditya

11

Chuỗi chữ:

Các ký tự chuỗi là bất biến, có nghĩa là, khi chúng được tạo, trạng thái của chúng không thể thay đổi, điều này cũng làm cho chúng an toàn.

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

a==b kết quả sẽ là 'true' của cả hai đối tượng tham chiếu chuỗi.

Đối tượng chuỗi:

Ở đây, hai đối tượng khác nhau được tạo và chúng có các tham chiếu khác nhau:

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

a==b kết quả sẽ là false, bởi vì chúng có các tham chiếu khác nhau.


1
Đối tượng chuỗi có phải là bất biến không?
Yang Wang,

@YangWang đó là một ngôn ngữ ngớ ngẩn, cho cả a& bcố gắng phân định a[0] = 'X'sẽ được thực hiện thành công nhưng sẽ không làm việc như bạn mong đợi
ruX

Bạn đã viết "var a = 's'; var b = 's'; a == b kết quả sẽ là 'true' cả hai chuỗi tham chiếu cùng một đối tượng." Điều đó không đúng: a và b không tham chiếu đến bất kỳ đối tượng nào giống nhau, kết quả là true vì chúng có cùng giá trị. Những giá trị đó được lưu trữ trong các vị trí bộ nhớ khác nhau, đó là lý do tại sao nếu bạn thay đổi một cái thì cái kia không thay đổi!
SC1000

9

Nếu bạn sử dụng new, bạn đang nói rõ rằng bạn muốn tạo một phiên bản của Đối tượng . Do đó, new Stringđang tạo ra một Đối tượng bao bọc chuỗi nguyên thủy, có nghĩa là bất kỳ hành động nào trên nó đều liên quan đến một lớp công việc bổ sung.

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

Vì chúng thuộc các loại khác nhau, trình thông dịch JavaScript của bạn cũng có thể tối ưu hóa chúng theo cách khác nhau, như đã đề cập trong phần nhận xét .


5

Khi bạn khai báo:

var s = '0123456789';

bạn tạo một chuỗi nguyên thủy. Chuỗi nguyên thủy đó có các phương thức cho phép bạn gọi các phương thức trên đó mà không cần chuyển đổi nguyên thủy thành đối tượng lớp đầu tiên. Vì vậy, giả định của bạn rằng điều này sẽ chậm hơn vì chuỗi phải được chuyển đổi thành một đối tượng là không chính xác. Nó không phải được chuyển đổi thành một đối tượng. Bản thân nguyên thủy có thể gọi các phương thức.

Chuyển đổi nó thành một đối tượng toàn diện (cho phép bạn thêm các thuộc tính mới vào nó) là một bước bổ sung và không làm cho chuỗi nhanh hơn (thực tế là thử nghiệm của bạn cho thấy rằng nó làm cho chúng chậm hơn).


Tại sao chuỗi nguyên thủy lại kế thừa tất cả các thuộc tính nguyên mẫu bao gồm cả các thuộc tính tùy chỉnh String.prototype?
Yuriy Galanter

1
var s = '0123456789';là giá trị nguyên thủy, làm sao giá trị này có các phương thức được, mình hoang mang quá!
The Alpha

2
@SheikhHeera - các nguyên mẫu được tích hợp vào việc triển khai ngôn ngữ để trình thông dịch có thể cấp cho chúng những quyền năng đặc biệt.
jfriend00

1
@SheikhHeera - Tôi không hiểu nhận xét / câu hỏi cuối cùng của bạn. Bản thân một chuỗi nguyên thủy không hỗ trợ bạn thêm các thuộc tính của riêng mình vào nó. Để cho phép điều đó, javascript cũng có một đối tượng String có tất cả các phương thức giống như một chuỗi nguyên thủy, nhưng là một đối tượng toàn diện mà bạn có thể coi như một đối tượng theo mọi cách. Dạng kép này có vẻ hơi lộn xộn, nhưng tôi nghi ngờ nó được thực hiện như một sự thỏa hiệp về hiệu suất vì trường hợp 99% là sử dụng các nguyên mẫu và chúng có thể nhanh hơn và hiệu quả hơn bộ nhớ so với các đối tượng chuỗi.
jfriend00

1
@SheikhHeera "được chuyển đổi thành Đối tượng chuỗi" là cách MDN diễn đạt nó để giải thích cách các phương thức nguyên thủy có thể gọi các phương thức. Chúng không được chuyển đổi theo nghĩa đen thành Đối tượng chuỗi.
Fabrício Matté

4

Tôi có thể thấy rằng câu hỏi này đã được giải quyết từ lâu, có một sự phân biệt tinh tế khác giữa các ký tự chuỗi và các đối tượng chuỗi, dường như không ai chạm vào nó, tôi nghĩ tôi chỉ viết nó cho hoàn chỉnh.

Về cơ bản, một sự phân biệt khác giữa hai là khi sử dụng eval. eval ('1 + 1') cho kết quả 2, trong khi eval (Chuỗi mới ('1 + 1')) cho '1 + 1', vì vậy nếu khối mã nhất định có thể được thực thi cả 'bình thường' hoặc với eval, nó có thể dẫn đến kết quả kỳ lạ


Cảm ơn bạn đã đóng góp ý kiến ​​:-)
Alpha

Wow, đó là một số hành vi thực sự kỳ lạ. Bạn nên thêm một bản trình diễn nội dòng nhỏ trong nhận xét của mình để thể hiện hành vi này - nó cực kỳ mở mang tầm mắt.
EyuelDK

điều này là bình thường, nếu bạn nghĩ về nó. new String("")trả về một đối tượng, và chỉ eval đánh giá chuỗi, et trở lại tất cả mọi thứ khác vì nó là
Félix Brunet

3

Sự tồn tại của một đối tượng ít liên quan đến hành vi thực tế của một Chuỗi trong các công cụ ECMAScript / JavaScript vì phạm vi gốc sẽ chỉ chứa các đối tượng chức năng cho việc này. Vì vậy, hàm charAt (int) trong trường hợp là một chuỗi ký tự sẽ được tìm kiếm và thực thi.

Với một đối tượng thực, bạn thêm một lớp nữa nơi phương thức charAt (int) cũng được tìm kiếm trên chính đối tượng đó trước khi hành vi chuẩn bắt đầu (giống như ở trên). Rõ ràng là có một lượng lớn công việc đáng ngạc nhiên được thực hiện trong trường hợp này.

BTW Tôi không nghĩ rằng các nguyên thủy thực sự được chuyển đổi thành Đối tượng nhưng công cụ tập lệnh sẽ chỉ đơn giản đánh dấu biến này là kiểu chuỗi và do đó nó có thể tìm thấy tất cả các hàm được cung cấp cho nó để có vẻ như bạn gọi một đối tượng. Đừng quên đây là thời gian chạy script hoạt động trên các nguyên tắc khác với thời gian chạy OO.


3

Sự khác biệt lớn nhất giữa một chuỗi nguyên thủy và một đối tượng chuỗi là các đối tượng phải tuân theo quy tắc này cho ==toán tử :

Biểu thức so sánh các Đối tượng chỉ đúng nếu các toán hạng tham chiếu đến cùng một Đối tượng.

Vì vậy, trong khi các nguyên mẫu chuỗi có một tiện ích ==so sánh giá trị, bạn đã gặp may khi làm cho bất kỳ loại đối tượng bất biến nào khác (bao gồm một đối tượng chuỗi) hoạt động giống như một loại giá trị.

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

(Những người khác đã lưu ý rằng một đối tượng chuỗi là có thể thay đổi về mặt kỹ thuật vì bạn có thể thêm thuộc tính vào nó. Nhưng không rõ điều đó hữu ích cho điều gì; bản thân giá trị chuỗi không thể thay đổi.)


Cảm ơn vì đã thêm giá trị cho câu hỏi sau một thời gian dài :-)
The Alpha

1

Mã được tối ưu hóa trước khi chạy bởi công cụ javascript. Nói chung, các điểm chuẩn vi mô có thể gây hiểu lầm vì trình biên dịch và trình thông dịch sắp xếp lại, sửa đổi, xóa và thực hiện các thủ thuật khác trên các phần của mã của bạn để làm cho mã chạy nhanh hơn. Nói cách khác, mã được viết cho biết mục tiêu là gì nhưng trình biên dịch và / hoặc thời gian chạy sẽ quyết định cách đạt được mục tiêu đó.

Khối 1 nhanh hơn chủ yếu là do: var s = '0123456789'; luôn nhanh hơn var s = new String ('0123456789'); vì chi phí tạo đối tượng.

Phần vòng lặp không phải là phần gây ra sự chậm lại vì chartAt () có thể được trình thông dịch nội tuyến. Hãy thử gỡ bỏ vòng lặp và chạy lại kiểm tra, bạn sẽ thấy tỷ lệ tốc độ sẽ giống như khi chưa gỡ bỏ vòng lặp. Nói cách khác, đối với các thử nghiệm này, các khối vòng lặp tại thời điểm thực thi có cùng một mã bytecode / mã máy.

Đối với những loại điểm chuẩn vi mô này, nhìn vào mã bytecode hoặc mã máy sẽ cung cấp một bức tranh rõ ràng hơn.


1
Cảm ơn câu trả lời của bạn.
The Alpha

0

Trong Javascript, các kiểu dữ liệu nguyên thủy như chuỗi là một khối xây dựng không kết hợp. Điều này có nghĩa là chúng chỉ là giá trị, không có gì hơn: let a = "string value"; Theo mặc định, không có phương thức tích hợp nào như toUpperCase, toLowerCase, v.v.

Nhưng, nếu bạn cố gắng viết:

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

Điều này sẽ không tạo ra bất kỳ lỗi nào, thay vào đó chúng sẽ hoạt động như bình thường.

Chuyện gì đã xảy ra ? Chà, khi bạn cố gắng truy cập một thuộc tính của một chuỗi, aJavascript buộc chuỗi thành một đối tượng new String(a);được gọi là đối tượng wrapper .

Quá trình này được liên kết với khái niệm được gọi là hàm tạo hàm trong Javascript, nơi các hàm được sử dụng để tạo các đối tượng mới.

Khi bạn gõ new String('String value');vào đây String là hàm tạo hàm, nó nhận một đối số và tạo một đối tượng trống bên trong phạm vi hàm, đối tượng trống này được gán cho và trong trường hợp này, String cung cấp tất cả những hàm được xây dựng sẵn mà chúng ta đã đề cập trước đó. và ngay sau khi hoàn thành thao tác, ví dụ như thao tác viết hoa, đối tượng trình bao bọc sẽ bị loại bỏ.

Để chứng minh điều đó, hãy làm điều này:

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

Ở đây đầu ra sẽ không được xác định. Tại sao ? Trong trường hợp này, Javascript tạo đối tượng wrapper String, đặt thuộc tính mới addNewProperty và loại bỏ đối tượng wrapper ngay lập tức. đây là lý do tại sao bạn không được xác định. Mã giả sẽ trông như thế này:

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

0

chúng ta có thể xác định Chuỗi theo 3 cách

  1. var a = "cách đầu tiên";
  2. var b = String ("cách thứ hai");
  3. var c = new String ("cách thứ ba");

// cũng có thể tạo bằng cách sử dụng 4. var d = a + '';

Kiểm tra loại chuỗi được tạo bằng toán tử typeof

  • typeof a // "chuỗi"
  • typeof b // "chuỗi"
  • typeof c // "đối tượng"


khi bạn so sánh a và b var a==b ( // yes)


khi bạn so sánh đối tượng Chuỗi

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
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.