(1, eval) ('this') против eval ('this') в JavaScript?

85

Я начинаю читать шаблоны JavaScript , некоторые коды меня сбивали с толку.

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

Вот мои вопросы:

Q1:

(1, eval) === eval?

Почему и как это работает?

Q2: Почему не просто

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

или

 var global = (function () {
    return this;
}());
Shawjia
источник
Я обновил заголовок, потому что это особый случай. Кроме того, круглые скобки для конкретного вида скобок : [] и {} совершенно разные :)

Ответы:

104

Разница между (1,eval)и простым старым evalсостоит в том, что первое - это значение, а второе - lvalue. Было бы более очевидно, если бы это был какой-то другой идентификатор:

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

Это (1,eval)выражение, которое дает eval(точно так же, как (true && eval)и (0 ? 0 : eval)было бы), но это не ссылка на eval.

Почему тебя это волнует?

Ну, Ecma спецификация рассматривает ссылку на evalбыть «прямым вызовом Eval», но выражение, просто дающим evalбыть косвенным - и косвенные вызовы Eval гарантированно выполнять в глобальном масштабе.

То, чего я до сих пор не знаю:

  1. При каких обстоятельствах прямой вызов eval не выполняется в глобальной области?
  2. При каких обстоятельствах thisфункция в глобальной области видимости может не возвращать глобальный объект?

Дополнительную информацию можно почерпнуть здесь .

РЕДАКТИРОВАТЬ

Видимо, ответ на мой первый вопрос - «почти всегда». Прямое evalвыполняется из текущей области. Рассмотрим следующий код:

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

Неудивительно (хе-хе), это распечатывает:

direct call: inner
indirect call: outer

РЕДАКТИРОВАТЬ

После дополнительных экспериментов я временно скажу, что thisнельзя установить на nullили undefined. Он может быть установлен на другие ложные значения (0, '', NaN, false), но только очень намеренно.

Я собираюсь сказать, что ваш источник страдает легкой и обратимой кранио-ректальной инверсией и может подумать о том, чтобы потратить неделю на программирование на Haskell.

Мальволио
источник
3
Ого, не знал всего valueпротив lvalue(ну, может быть, на практике, но не на словах). Ни правила ES5 eval (не то, чтобы мне разумно было evalкогда-либо использовать ). Благодарность!
Stoive 03
Да, у evalнего много неприятных острых краев, и его следует использовать только в крайнем случае и очень, очень осторожно.
Мальволио
Я только однажды наткнулся на правильное использование - оценку тега скрипта, который был добавлен в DOM черезinnerHtml
Stoive
1
lvalue имеет мало общего с определением прямого eval, поскольку обычно он ссылается на выражение, которое может появляться в левой части присваивания, отсюда и имя lvalue, а не rvalue. Вызов eval является прямым только при условиях, перечисленных в 15.1.2.1.1 спецификации, в которой говорится, что идентификатор должен быть evalчастью MemberExpression CallExpression и ссылаться на стандартную evalфункцию.
chuckj 03
1
@Malvolio Похоже, вы подразумеваете, что lvalues ​​имеют какое-то отношение к прямому и косвенному eval, чего они не делают. Использование идентификатора, вызываемого evalв качестве цели выражения вызова, является особенным. Вы утверждаете , что ECMA рассматривает ссылки на evalособенный , которые он не делает. Это размещение в выражении вызова является особенным, поскольку выражение оценивает стандартную evalфункцию. Например, var eval = window.eval; eval('1');по-прежнему является прямым eval и им window.eval('1')не является, хотя в этом случае eval также является lvalue.
chuckj
33

Фрагмент,

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

будет правильно оценивать глобальный объект даже в строгом режиме. В нестрогом режиме значением thisявляется глобальный объект, но в строгом - это так undefined. Выражение (1, eval)('this')всегда будет глобальным объектом. Причина этого заключается в правилах, касающихся косвенных стихов, прямых eval. Прямые вызовы evalимеют область действия вызывающего, и строка thisбудет оцениваться как значение thisв закрытии. Косвенные evals оцениваются в глобальной области, как если бы они были выполнены внутри функции в глобальной области. Поскольку эта функция сама по себе не является функцией строгого режима, глобальный объект передается как, thisа затем выражение 'this'оценивается в глобальный объект. Выражение (1, eval)- всего лишь причудливый способ заставитьeval быть косвенным и возвращать глобальный объект.

A1: (1, eval)('this')это не то же самое, eval('this')что из-за особых правил, касающихся прямых обращений к косвенным стихам eval.

О2: Оригинал работает в строгом режиме, модифицированные версии - нет.

чакдж
источник
12

К Q1:

Думаю, это хороший пример оператора запятой в JS. Мне нравится объяснение оператора запятой в этой статье: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

Оператор запятая оценивает оба своих операнда (слева направо) и возвращает значение второго операнда.

К 2 кварталу:

(1, eval)('this')считается косвенным вызовом eval, который в ES5 выполняет код глобально. Таким образом, результатом будет глобальный контекст.

См. Http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope

Грейс Шао
источник
7

Q1: несколько последовательных операторов javascript, разделенных запятой, принимают значение последнего оператора. Так:

(1, eval)принимает значение последнего, которое является ссылкой на eval()функцию. Очевидно, он делает это таким образом, чтобы сделать eval()вызов косвенным вызовом eval, который будет оцениваться в глобальной области видимости в ES5. Подробности объяснены здесь .

Q2: Должна быть некоторая среда, которая не определяет глобальную this, но определяет eval('this'). Это единственная причина, по которой я могу это придумать.

jfriend00
источник
Возможно, кто-то пытается избежать ловушки, которая запрещает регистрацию /eval\(/g?
Stoive 02
@Stoive - да, я тоже думал о таком. Если не хук проверки, то какой-то фильтр где-то в процессе (возможно, минимизация).
jfriend00 02
7
Это как-то связано со строгим режимом ES5. AFAIK в строгом режиме ES5 любой evalкод d выполняется в собственном контексте, а не в глобальном контексте или окружающем контексте. Один из способов обойти это - косвенно ссылаться на него, как на рассматриваемый код.
Кристиан Санчес
1
Взгляните на « Global eval. Какие есть варианты? ».
Saxoier 02
Обновил мой ответ, чтобы включить информацию из CDSanchez и @Saxoier. Спасибо.
jfriend00 02