Przypuśćmymy, że mam te dwie funkcje:

function change(args) {
    args[0] = "changed";
    return " => ";
}
function f(x) {
    return [x, change(f.arguments), x];
}
console.log(f("original"));

W większości przeglądarek, z wyjątkiem operacji, powraca ["original", " => ", "original"].

Ale jeśli zmieniam funkcję f tak, jak ten,

function f(x) {
    return [x, change(f.arguments), x];
    eval("");
}

Wróci ["original", " => ", "changed"] w IE9, Safari 5 i Firefox 16 i 17.

Jeśli wymieniam eval("") za pomocą arguments, będzie również zmienić w Chrome.

Możesz przetestuj go na własnej przeglądarce na JSFiddle.

W ogóle nie rozumiem tego zachowania. Jeśli funkcja powróci przed wykonaniem tych stwierdzeń, w jaki sposób te stwierdzenia mogą wpływać na wartość zwrotu? Nawet jeśli stwierdzenia zostały wykonane, dlaczego mieliby mieć wpływ na mutację argumentu?

20
Peter Olson 13 sierpień 2012, 22:00

3 odpowiedzi

Najlepsza odpowiedź

TL; DR

Prawdopodobną przyczyną jest interakcja niestandardowa function.arguments z optymalizacjami przeglądarki dla kodu funkcyjnego zawierającego eval i / lub arguments. Jednak tylko ludzie znają szczegóły wdrażania w każdej przeglądarce byłyby w stanie wyjaśnić dlaczego dogłębnie.

Głównym problemem wydaje się być użycie niestandardowego {x0}}. Kiedy go nie używasz, dziwne zachowanie odchodzi.

Specyfikacja wspomina tylko Obiekt arguments i Nigdy nie mówi, że może być traktowany jako nieruchomość, poprzedzona z [funcName].. Nie jestem pewien, skąd pochodzi, ale to prawdopodobnie coś przede wszystkim, przechowywane na przeglądarkach ze względu na kompatybilność wstecz. Jako stany odpowiedzi Cory, to użycie to teraz zniechęcony na MDN . MSDN, jednak nie - Powiedz coś przeciwko temu. Znalazłem również, że wspomniano o tym Specyfikacja zgodności między przeglądarkami < SUP> * , co nie wydaje się być wdrażane konsekwentnie przez dostawców (brak przeglądarki wszystkie Testy). Również przy użyciu arguments jako właściwość funkcji nie jest dozwolona w trybie ścisłym (ponownie, nie jest to w specyfikacjach ECMA, a IE9 wydaje się ignorować ograniczenie).

Następnie przyjdź eval i arguments. Ponieważ jesteś świadomy, specyfikacja ECMAScript wymaga niektórych Extra Operacje do wykonania, dzięki czemu można użyć tych konstrukcji językowych (w przypadku eval, operacja jest inna, w zależności od połączenia jest Direct lub nie). Ponieważ operacje te mogą mieć wpływ na wydajność, (niektóre?) Silniki JavaScript wykonują optymalizacje, aby uniknąć ich, jeśli nie są używane eval lub arguments. Te optymalizacje w połączeniu z zastosowaniem niestandardowej właściwości obiektu Function, wydają się być tym, co powoduje, że masz dziwne rezultaty. Niestety, nie znam szczegółów wdrożenia dla każdej przeglądarki, więc nie mogę dać ci precyzyjnej odpowiedzi na Dlaczego widzimy te efekty zabezpieczenia.

(*) Spec pisany przez Więc Użytkownik, przy okazji.

Testy

Pobiegłem kilka testów, aby zobaczyć, jak eval (połączenia bezpośrednie i pośrednie), arguments i fn.arguments Interakcja na IE, Firefox i Chrome. Nie dziwi to, że wyniki różnią się w każdej przeglądarce, ponieważ mamy do czynienia z niestandardową fn.arguments.

Pierwszy test sprawdza tylko surową równość fn.arguments i arguments, a jeśli obecność eval wpływa na to w dowolny sposób. Nieuchronnie moje testy chromowane są zanieczyszczone obecnością arguments, która ma wpływ na wyniki, jak powiedziałeś w pytaniu. Oto wyniki:

                       |  no eval  |  direct eval call  |  indirect eval call
-----------------------+-----------+--------------------+---------------------
IE 9.0.8112.16421      |  true     |  true              |  true
FF 16.0.2              |  false    |  false             |  false
Chrome 22.0.1229.94    |  true     |  false             |  true

Możesz zobaczyć IE, a Firefox są bardziej spójne: obiekty są zawsze równe, tj. I nigdy nie równe na Firefoksie. W Chrome jednak są jednak równi, jeśli kod funkcji nie zawiera połączenia bezpośredniego {x0}}.

Pozostałe testy są testami przypisywania oparte na funkcjach, które wyglądają jak następujące:

function fn(x) {
    // Assignment to x, arguments[0] or fn.arguments[0]
    console.log(x, arguments[0], fn.arguments[0]);
    return; // make sure eval is not actually called
    // No eval, eval(""), or (1,eval)("")
}

Poniżej przedstawiono wyniki dla każdej przetestowanej przeglądarki.

Internet Explorer 9.0.8112.16421

                             | no eval                   | direct eval call          | indirect eval call
-----------------------------+---------------------------+---------------------------+--------------------------
arguments[0] = 'changed';    | changed, changed, changed | changed, changed, changed | changed, changed, changed
x = 'changed';               | changed, changed, changed | changed, changed, changed | changed, changed, changed
fn.arguments[0] = 'changed'; | changed, changed, changed | changed, changed, changed | changed, changed, changed

Przede wszystkim wydaje się, że moje tj. Testy dają różne wyniki niż stwierdzono w pytaniu; Zawsze jestem "zmieniany" na IE. Może użyliśmy różnych budów? W każdym razie pojawiają się wyniki powyżej, to tzn. Jest to najbardziej spójna przeglądarka. Tak jak na IE arguments === fn.arguments jest zawsze prawdą, x, arguments[0] lub function.arguments[0] wszystko wskaż tę samą wartość. Jeśli zmienisz każdy z nich, wszystkie trzy będą wysyłać tę samą zmianę wartości.

firefox 16.0.2

                             | no eval                      | direct eval call          | indirect eval call
-----------------------------+------------------------------+---------------------------+-----------------------------
arguments[0] = 'changed';    | changed, changed, original   | changed, changed, changed | changed, changed, original
x = 'changed';               | changed, changed, original   | changed, changed, changed | changed, changed, original
fn.arguments[0] = 'changed'; | original, original, original | changed, changed, changed | original, original, original

Firefox 16.0.2 jest mniej spójny: chociaż arguments jest nigdy === fn.arguments na Firefox, eval ma wpływ na zadania. Bez bezpośredniego połączenia z eval zmiana arguments[0] zmiany x również, ale nie zmienia się fn.arguments[0]. Zmiana fn.arguments[0] nie zmienia się ani x lub arguments[0]. Była to pełna niespodzianka, że zmiana {{x10}} nie zmienia się!

Po wprowadzeniu eval("") zachowanie jest inne: zmiana jednego z {x1}}, arguments[0] lub function.arguments[0] zaczyna wpływać na pozostałe dwa. Więc to jest jak arguments staje się === function.arguments - z wyjątkiem tego, że nie, Firefox nadal mówi, że arguments === function.arguments jest false. Gdy używa się pośrednio eval połączenie, Firefox zachowuje się jak gdyby nie było eval.

Chrome 22.0.1229.94

                             | no eval                    | direct eval call             | indirect eval call
-----------------------------+----------------------------+------------------------------+--------------------------
arguments[0] = 'changed';    | changed, changed, changed  | changed, changed, original   | changed, changed, changed
x = 'changed';               | changed, changed, changed  | changed, changed, original   | changed, changed, changed
fn.arguments[0] = 'changed'; | changed, changed, changed  | original, original, original | changed, changed, changed

Zachowanie Chrome jest podobne do Firefoksa: gdy nie ma eval lub pośrednie eval, zachowuje się konsekwentnie. Z bezpośrednim połączeniem eval link między arguments a fn.arguments i fn.arguments i fn.arguments i fn.arguments i fn.arguments i {x4}} jest obecny). Chrome przedstawia również dziwny przypadek fn.arguments[0] jest original nawet po przypisaniu, ale zdarza się, gdy jest obecny, gdy jest obecny {{x10}} (while na Firefoksie, gdy jest to, gdy nie ma eval, czy z połączenie pośrednie).

Oto pełny kod testów, jeśli ktoś chce je uruchomić. Jest też Wersja na żywo na JSFiddle.

function t1(x) {
    console.log("no eval: ", arguments === t1.arguments);
}
function t2(x) {
    console.log("direct eval call: ", arguments === t2.arguments);
    return;
    eval("");
}
function t3(x) {
    console.log("indirect eval call: ", arguments === t3.arguments);
    return;
    (1, eval)("");
}

// ------------

function t4(x) {
    arguments[0] = 'changed';
    console.log(x, arguments[0], t4.arguments[0]);
}

function t5(x) {
    x = 'changed';
    console.log(x, arguments[0], t5.arguments[0]);
}

function t6(x) {
    t6.arguments[0] = 'changed';
    console.log(x, arguments[0], t6.arguments[0]);
}

// ------------

function t7(x) {
    arguments[0] = 'changed';
    console.log(x, arguments[0], t7.arguments[0]);
    return;
    eval("");
}

function t8(x) {
    x = 'changed';
    console.log(x, arguments[0], t8.arguments[0]);
    return;
    eval("");
}

function t9(x) {
    t9.arguments[0] = 'changed';
    console.log(x, arguments[0], t9.arguments[0]);
    return;
    eval("");
}

// ------------

function t10(x) {
    arguments[0] = 'changed';
    console.log(x, arguments[0], t10.arguments[0]);
    return;
    (1, eval)("");
}

function t11(x) {
    x = 'changed';
    console.log(x, arguments[0], t11.arguments[0]);
    return;
    (1, eval)("");
}

function t12(x) {
    t12.arguments[0] = 'changed';
    console.log(x, arguments[0], t12.arguments[0]);
    return;
    (1, eval)("");
}

// ------------

console.log("--------------");
console.log("Equality tests");
console.log("--------------");
t1('original');
t2('original');
t3('original');

console.log("----------------");
console.log("Assignment tests");
console.log("----------------");
console.log('no eval');
t4('original');
t5('original');
t6('original');
console.log('direct call to eval');
t7('original');
t8('original');
t9('original');
console.log('indirect call to eval');
t10('original');
t11('original');
t12('original');
9
Community 20 czerwiec 2020, 09:12

Po prostu grając wokół, odkryłem, że usuwasz f. z wartości f.arguments w tablicy i wystarczy użyć arguments, zachowanie jest takie samo bez względu na to, co przychodzi po {{x3 }}:

function f(x) {
    return [x, change(arguments), x];
}
function g(x) {
    return [x, change(arguments), x];
    eval("");
}
function h(x) {
    return [x, change(arguments), x];
    arguments;
}

We wszystkich trzech przypadkach przy użyciu x = "original" Wyjście jest:

["original", " => ", "changed"]
["original", " => ", "changed"] 
["original", " => ", "changed"]

W takim przypadku wartości są modyfikowane przez change(), jakby macierz arguments jest przekazywany przez odniesienie. Aby zachować "Oryginalny" niezmieniony , mogę sugerować konwertowanie obiektu arguments do rzeczywistej tablicy (w ten sposób przechodzącej arguments "Elementy według wartości ) :

function h(x) {
    var argsByValue = Array.prototype.slice.call(arguments, 0);
    return [x, change(argsByValue), x];
}

W powyższym przykładzie x pozostanie "oryginalny" przed i po change(), ponieważ kopia x została zmodyfikowana, a nie oryginał.

Nadal nie jestem pewien, co skutki posiadania eval(""); lub arguments;}, ale twoje pytanie jest nadal interesujące, podobnie jak wyniki.

Co naprawdę dziwne jest to, że nawet wpływa na umieszczenie change() w swoim zakresie funkcji z kopią argumentów funkcji

function f(x) {
    return ((function(args) {             
        return [x, change(args), x];
    })(f.arguments));
    // the presence of the line below still alters the behavior
    arguments; 
}

Wydaje się, że odniesienia do f.arguments nadal ma w tym przypadku. Dziwne rzeczy.

AKTUALIZACJE

Z MDN:

Obiekt arguments jest zmienną lokalną dostępną we wszystkich funkcjach; {x1}} jako właściwość Function nie może być już używany.

Wydaje się, że przynajmniej dla Firefoksa, nie powinieneś używać arguments jako nieruchomości (np. function foo() { var bar = foo.arguments; }), choć nie mówią dlaczego.

3
Cᴏʀʏ 13 sierpień 2012, 20:09

Oto kilka Doskonałe Nuanse JavaScript wchodzą w życie:

change(f.arguments)
change(x)

Pierwsza zmiana lista argumentów w zmianie () jako odniesienia . Tablice mają tendencję do odniesienia w JavaScript. Oznacza to, że jeśli zmienisz element tablicy gdzieś indziej, zmiany zostaną zastosowane wszędzie tam, gdzie używasz tej samej tablicy .

Ten ostatni przechodzi argument x jako wartość . To jak wyłączanie kopii - zmiana może go zmienić i wpłynie to tylko na zmienną lokalną. Ponieważ x jest ciągiem, a struny są niezmienne, args [0] = "Zmienione" w funkcji zmiany () nic nie robi. Wypróbuj następujące informacje w konsoli:

var x = "asdf";
x[0] = "foo";
console.log(x); // should print "asdf"

W funkcji F, H, G, wartość argumentów [0] jest zmieniana w drugim indeksie na liście zwróconej. Trzeci indeks powróci "zmieniony".

Teoretycznie. Jednak niektóre przeglądarki kompilują JavaScript, co powoduje, że warunki rasy i instrukcje mogą nie wykonywać w kolejności, którą je wpisujesz, zwłaszcza jeśli są w tej samej linii i zmienisz stos i uzyskujemy dostęp do tego z tej samej linii.

return [x, change(f.arguments), x];

... próbuje zmienić zmienną argumentów i dostępu X (który jest w tym samym czasie. W Chrome, na przykład, przechodząc f.arguments do zmiany () skutkuje ["Oryginał", "=>", "Oryginalny"], przechodząc tylko argumenty powoduje, że ["Oryginał", "=>", "Zmieniono"] . Może to być również problem z zakresem zakresu i sposobu, w jaki klamka JavaScript, ale to zachowanie jest różne w różnych przeglądarkach.

Nie widziałem żadnych dziwnych zachowań z Eval (), biorąc pod uwagę to, co opisałem, ale wydaje się, że stwierdzono argumenty w funkcji H () po powrocie tworzy efekt uboczny, który podejrzewam, jest spowodowany kompilackim JavaScript CHROME. Co jest naprawdę interesujące, jest to wewnętrznie, powłoka wykonuje zmienną, zwracając jego wartość, ale nie jest to napisane wszędzie, spodziewaj się być może do pamięci podręcznej. Trudno powiedzieć, co dzieje się na stosie JavaScript, ale to, co robisz, jest z pewnością niekonwencjonalne i na pewno zrozumiem kompilator przez przeglądarki.

EDYTOWAĆ:

Jeszcze lepiej: konsola.log (H.Arguments); powrót [x, zmień (argumenty), x]; argumenty

Będzie logować

["changed"]
["original", " => ", "changed"]

Pewnie wygląda jak stan wyścigu lub jakiś Wonky przechodzący od referencji do argumentów w ramach funkcji!

1
13 sierpień 2012, 18:41