Dlaczego + jest tak zły na konkatenację?

44

Wszyscy mówią, że jednym z problemów JavaScript jest użycie +[ przykład ] do łączenia łańcuchów. Niektórzy twierdzą, że problem nie jest używany +, jest to przymus typu [patrz komentarze z poprzedniego przykładu]. Jednak w przypadku silnie typowanych języków bezproblemowo używaj + do łączenia i wymuszania typów. Na przykład w języku C #:

int number = 1;
MyObject obj = new MyObject();

var result = "Hello" + number + obj;
// is equivalent to
string result = "hello" + number.ToString() + obj.ToString();

Dlaczego więc łączenie łańcuchów w JavaScript jest tak dużym problemem?

konfigurator
źródło
17
dlaczego jest bad. Kim jest Everyone?
Neal,
3
Myślę, że problem polega na tym, że + jest operatorem matematycznym. I że funkcja konkatynacji + wprowadza w błąd ten standardowy proces. ponieważ chociaż „Cześć” + numer może działać numer + „Cześć” może nie.
SoylentGray,
3
@Neal, zgadzam się - chcę wiedzieć, kim wydają się być wszyscy ci mistyczni „wszyscy” we wszystkich tych pytaniach.
GrandmasterB,
Używanie + do konkatenacji jest OK. Używanie pisania dynamicznego jest OK. Używanie obu w tym samym języku jest złe ^ H ^ H ^ H prowadzi do niejednoznaczności.
Gerry,
6
@Neal - „Wszyscy” to Douglas Crockford. Wymienia to jako „okropne” w swojej książce „JavaScript: The Good Parts”.
kirk.burleson

Odpowiedzi:

68

Rozważ ten fragment kodu JavaScript:

var a = 10;
var b = 20;
console.log('result is ' + a + b);

To się zaloguje

wynik to 1020

Co najprawdopodobniej nie jest zamierzone i może być trudnym do wyśledzenia.

Mchl
źródło
4
Bardzo ładna ilustracja leżącego u podstaw problemu: konkatenacja ciągów nie jest dobrze zdefiniowana, gdy typy (ciąg + int + int w przykładzie) mieszają się. Lepiej mieć zupełnie inny operator (jak Perl '.') Z dobrze zdefiniowaną semantyką.
Bruce Ediger,
9
Albo sposób Pythona rozwiązywania tego, co byłoby, aby zmusić cię do zawijania ai bw str(a)i str(b)połączenia; w przeciwnym razie otrzymasz ValueError: cannot concatenate 'str' and 'int'.
Aphex,
6
@FrustratedWithFormsDesigner: Dodaj nawiasy. 'result is ' + (a + b).
Jon Purdy,
18
Chodzi o to, że w C # możesz zrobić to samo i uzyskasz ten sam wynik - System.Console.WriteLine("result is " + 10 + 20);ale nikt nigdy nie narzeka na to w C #. Tego nie rozumiem - co takiego jest w JavaScript, co czyni go bardziej mylącym?
konfigurator
7
@configurator: ponieważ ludzie mówią, że C # jest doskonały, a javascript jest okropny. Obaj się mylą, ale wydaje się, że reputacja języka się utrzymuje, spostrzeżenia liczą się bardzo, szczególnie w Internecie.
gbjbaanb
43

Kiedy mówisz „zły”, masz na myśli „niepoprawny” czy „powolny”? Argument o używaniu operatorów matematycznych zrobić ciąg konkatenacji niewątpliwie jest „nieprawidłowy” Argument, ale jest również argument, że należy dokonać za pomocą +zrobić wiele z łańcuchów znaków może być bardzo powolne.

Nie mówimy o tym, "Hello" + numberkiedy mówimy o wydajności, mówimy o budowaniu stosunkowo dużego ciągu przez wielokrotne dołączanie do niego w pętli.

var combined = "";
for (var i = 0; i < 1000000; i++) {
    combined = combined + "hello ";
}

W JavaScript (i C # jeśli o to chodzi) ciągi są niezmienne. Nigdy nie mogą być zmienione, tylko zastąpione innymi ciągami. Prawdopodobnie zdajesz sobie sprawę, że combined + "hello "nie modyfikuje bezpośrednio combinedzmiennej - operacja tworzy nowy ciąg, który jest wynikiem połączenia dwóch ciągów razem, ale musisz przypisać nowy ciąg do combinedzmiennej, jeśli chcesz ją zmienić.

Ta pętla tworzy milion różnych obiektów łańcuchowych i wyrzuca 999 999 z nich. Utworzenie tak wielu ciągów, które ciągle się powiększają, nie jest szybkie, a teraz śmieciarz ma wiele do zrobienia, aby potem wyczyścić.

C # ma dokładnie ten sam problem, który jest rozwiązany w tym środowisku przez klasę StringBuilder . W JavaScript uzyskasz znacznie lepszą wydajność, budując tablicę wszystkich ciągów, które chcesz połączyć, a następnie łącząc je razem na końcu, zamiast miliona razy w pętli:

var parts = [];
for (var i = 0; i < 1000000; i++) {
    parts.push("hello");
}
var combined = parts.join(" ");
Joel Mueller
źródło
3
To doskonała odpowiedź. Nie tylko odpowiedziała na pytanie, ale także edukowała.
Charles Sprayberry
3
Nie o to mi w ogóle chodziło, jest to całkowicie niezwiązane z pytaniem.
konfigurator
4
Przepraszam. Wygląda na to, że co najmniej 7 osób źle zrozumiało twoje pytanie w jego brzmieniu.
Joel Mueller
9
Re wydajność: Być może miało to miejsce w 2011 roku, ale teraz metoda operatora + jest znacznie szybsza niż metoda array.join (przynajmniej w wersji 8). Zobacz ten jsperf: jsperf.com/string-concatenation101
UpTheCreek
2
Masz pojęcie, w jaki sposób metoda łączenia generuje jeden ciąg z wielu części w jednym kroku, zamiast łączyć również jeden po drugim, generując w ten sposób wszystkie te łańcuchy pośrednie?
Angelo.Hannes,
14

Nie jest źle używać + zarówno do dodawania, jak i konkatenacji, pod warunkiem, że można tylko dodawać liczby i tylko łańcuchy konkatenacji. Jednak Javascript automatycznie konwertuje między liczbami i łańcuchami w razie potrzeby, więc masz:

3 * 5 => 15
"3" * "5" => 15
2 + " fish" => "2 fish"
"3" + "5" => "35"  // hmmm...

Być może prościej, przeciążenie „+” w obecności automatycznej konwersji typu przerywa oczekiwane skojarzenie operatora +:

("x" + 1) + 3 != "x" + (1 + 3)
Kevin Cline
źródło
2
Z konkatenacji brakuje także przemiennej właściwości dodania, 3 + 5 == 5 + 3ale"3" + "5" != "5" + "3"
Caleth
10

Typowy problem związany z łączeniem łańcuchów dotyczy kilku zagadnień:

  1. +symbol przeciążenia
  2. proste błędy
  3. programiści są leniwi

# 1

Pierwszym i najważniejszym problemem związanym z używaniem +do łączenia i dodawania ciągów jest to, że używasz +do łączenia i dodawania ciągów .

jeśli masz zmienne ai bustawiłeś c = a + b, to niejednoznaczność zależy od typów ai b. Ta dwuznaczność zmusza do podjęcia dodatkowych kroków w celu złagodzenia problemu:

Concat String:

c = '' + a + b;

Dodanie:

c = parseFloat(a) + parseFloat(b);

To prowadzi mnie do drugiego punktu

# 2

Bardzo łatwo jest przypadkowo rzucić zmienną na ciąg, nie zdając sobie z tego sprawy. Łatwo jest również zapomnieć, że dane wejściowe pojawiają się w postaci ciągu:

a = prompt('Pick a number');
b = prompt('Pick a second number');
alert( a + b );

Daje nieintuicyjne wyniki, ponieważ zachęta zwraca wartość ciągu wejściowego.

# 3

Potrzebowanie pisania parseFloatlub za Number()każdym razem, gdy chcę to zrobić, jest żmudne i denerwujące. Uważam się za wystarczająco mądrego, aby pamiętać, że nie zepsuję moich dynamicznych typów, ale to nie znaczy, że nigdy nie zawiodłem moich dynamicznych typów . Prawda jest taka, że ​​jestem zbyt leniwy, aby pisać parseFloatlub za Number()każdym razem, gdy dodam, ponieważ robię dużo dodawania.

Rozwiązanie?

Nie wszystkie języki używają +do łączenia łańcuchów. PHP używa .do łączenia ciągów, co pomaga rozróżnić, kiedy chcesz dodać liczby, a kiedy chcesz połączyć ciągi.

Do łączenia łańcuchów można użyć dowolnego symbolu, ale większość z nich jest już używana i najlepiej unikać duplikacji. Gdybym miał swoją drogę, prawdopodobnie użyłbym _do łączenia łańcuchów i nie zezwalałbym na _nazwy zmiennych.

zzzzBov
źródło