(1, eval) ('this') vs eval ('this') w JavaScript?

85

Zaczynam czytać wzorce JavaScript , niektóre kody mnie zmyliły.

var global = (function () {
    return this || (1, eval)('this');
}());

Oto moje pytania:

P1:

(1, eval) === eval?

Dlaczego i jak to działa?

P2: Dlaczego nie po prostu

var global = (function () {
    return this || eval('this');
}());

lub

 var global = (function () {
    return this;
}());
shawjia
źródło
Zaktualizowałem tytuł, ponieważ jest to szczególny przypadek. Również nawiasy dla konkretnego rodzaju nawiasów : [] i {} są całkowicie różne :)

Odpowiedzi:

104

Różnica między (1,eval)a zwykłym starym evalpolega na tym, że pierwsza jest wartością, a druga jest lwartością. Byłoby bardziej oczywiste, gdyby był to jakiś inny identyfikator:

var x;
x = 1;
(1, x) = 1; //  syntax error, of course!

To jest (1,eval)wyrażenie, które daje eval(tak jak powiedzmy (true && eval)lub (0 ? 0 : eval)zrobiłoby), ale nie jest odniesieniem do eval.

Dlaczego się przejmujesz?

Cóż, ECMA specyfikacja rozważa odwołanie się evalbyć „bezpośrednie połączenie eval”, ale to tylko wyraz daje w wyniku evalbyć pośrednim jeden - i pośrednie połączenia eval gwarantowane są do wykonania w zakresie globalnym.

Rzeczy, których nadal nie wiem:

  1. W jakich okolicznościach bezpośrednie wywołanie eval nie jest wykonywane w zakresie globalnym?
  2. W jakich okolicznościach thisfunkcja o zasięgu globalnym może nie dawać obiektu globalnego?

Więcej informacji można znaleźć tutaj .

EDYTOWAĆ

Najwyraźniej odpowiedź na moje pierwsze pytanie brzmi „prawie zawsze”. Bezpośredni jest evalwykonywany z bieżącego zakresu. Rozważ następujący kod:

var x = 'outer';
(function() {
  var x = 'inner';
  eval('console.log("direct call: " + x)'); 
  (1,eval)('console.log("indirect call: " + x)'); 
})();

Nic dziwnego (heh-heh), to wypisuje:

direct call: inner
indirect call: outer

EDYTOWAĆ

Po dalszych eksperymentach zamierzam wstępnie powiedzieć, że thisnie można ustawić na nulllub undefined. Można ustawić inne fałszywe wartości (0, „, NaN, fałsz”), ale tylko bardzo celowo.

Powiem, że twoje źródło cierpi na łagodną i odwracalną inwersję czaszkowo-odbytniczą i może chcieć rozważyć spędzenie tygodnia na programowaniu w Haskell.

Malvolio
źródło
3
Wow, nie wiedziałem wszystkiego valuevs lvalue(no cóż, może w praktyce, ale nie słowami). Ani reguł ewaluacyjnych ES5 (których nie powinienem evalkiedykolwiek używać ). Dzięki!
Stoive
Tak, evalma wiele paskudnych ostrych krawędzi i powinien być używany tylko w ostateczności, a potem bardzo, bardzo ostrożnie.
Malvolio
Tylko raz natknąłem się na prawidłowe zastosowanie - ocena tagu skryptu, który został dodany do DOM przezinnerHtml
Stoive
1
lwartość ma niewiele wspólnego z określeniem bezpośredniego eval, ponieważ zwykle odnosi się do wyrażenia, które może pojawić się po lewej stronie przypisania, stąd nazwa lwartość w przeciwieństwie do rwartość. Wywołanie eval jest bezpośrednie tylko w warunkach wymienionych w 15.1.2.1.1 specyfikacji, która mówi, że identyfikator musi być evali być częścią MemberExpression elementu CallExpression i odnosić się do evalfunkcji standardowej .
chuckj
1
@Malvolio Wydaje się, że sugerujesz, że lvalues ​​mają coś wspólnego z ewaluacją bezpośrednią i pośrednią, czego nie mają. Użycie identyfikatora wywoływanego evaljako cel wyrażenia wywołania jest szczególne. Twierdzisz, że ECMA traktuje odniesienia jako evalspecjalne, a tego nie robi. Jest to miejsce docelowe w wyrażeniu wywołującym, które jest szczególne i że wyrażenie przyjmuje wartość evalfunkcji standardowej . Na przykład var eval = window.eval; eval('1');jest nadal bezpośrednim eval i window.eval('1')nie jest, nawet jeśli eval jest lwartością również w tym przypadku.
chuckj
33

Fragment,

var global = (function () {  
    return this || (1, eval)('this');  
}());  

będzie poprawnie oceniać obiekt globalny nawet w trybie ścisłym. W trybie nieścisłym wartość thisjest obiektem globalnym, ale w trybie ścisłym jest undefined. Wyrażenie (1, eval)('this')zawsze będzie obiektem globalnym. Powodem tego są zasady dotyczące wersetów pośrednich bezpośrednich eval. Bezpośrednie wywołania evalmają zasięg obiektu wywołującego, a ciąg thiszostałby oszacowany na wartość thisw zamknięciu. Pośrednie evalwartości oceniają w zakresie globalnym tak, jakby były wykonywane wewnątrz funkcji w zakresie globalnym. Ponieważ ta funkcja sama w sobie nie jest funkcją trybu ścisłego, obiekt globalny jest przekazywany jako, thisa następnie wyrażenie 'this'obliczane jest na obiekt globalny. Wyrażenie (1, eval)to po prostu fantazyjny sposób na wymuszenieeval być pośrednim i zwrócić obiekt globalny.

A1: (1, eval)('this')to nie to samo, co eval('this')ze względu na specjalne zasady dotyczące bezpośredniego wywołania w wersecie pośrednim eval.

A2: Oryginał działa w trybie ścisłym, zmodyfikowane wersje nie.

chuckj
źródło
12

Do Q1:

Myślę, że to dobry przykład operatora przecinka w JS. Podoba mi się wyjaśnienie dotyczące operatora przecinka w tym artykule: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

Operator przecinka ocenia oba swoje operandy (od lewej do prawej) i zwraca wartość drugiego operandu.

Do Q2:

(1, eval)('this')jest uważane za pośrednie wywołanie eval, które w ES5 wykonuje kod globalnie. Wynik będzie więc globalnym kontekstem.

Zobacz http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope

Grace Shao
źródło
7

P1: Wiele kolejnych instrukcji javascript oddzielonych przecinkiem przyjmuje wartość ostatniej instrukcji. Więc:

(1, eval)przyjmuje wartość ostatniej, która jest odwołaniem do eval()funkcji. Najwyraźniej robi to w ten sposób, aby eval()wywołać pośrednie wywołanie ewaluacyjne, które zostanie ocenione w zakresie globalnym w ES5. Szczegóły wyjaśniono tutaj .

P2: Musi istnieć środowisko, które nie definiuje globalnego this, ale je definiuje eval('this'). To jedyny powód, jaki przychodzi mi do głowy.

jfriend00
źródło
Może ktoś próbuje ominąć haczyk do zameldowania, który uniemożliwia /eval\(/g?
Stoive
@Stoive - tak, też się nad czymś takim zastanawiałem. Jeśli nie haczyk checkin, jakiś filtr gdzieś w procesie (być może minimalizacja).
jfriend00
7
Ma to coś wspólnego z trybem ścisłym ES5. AFAIK w trybie ścisłym ES5 każdy evalkod jest wykonywany w swoim własnym kontekście, a nie w kontekście globalnym lub otaczającym. Jednym ze sposobów obejścia tego jest pośrednie odwołanie się do niego, tak jak robi to w przypadku danego kodu.
Cristian Sanchez
1
Zajrzyj do „ Global eval. Jakie są opcje? ”.
Saxoier
Zaktualizowałem moją odpowiedź, aby zawierała informacje z CDSanchez i @Saxoier. Dzięki.
jfriend00