Почему eval не имеет доступа к переменным с областью видимости в операторе with?

Почему вы не можете получить доступ к переменным области действия, используя eval в инструкции with?

Например:

(function (obj) { 
   with (obj) {
      console.log(a); // prints out obj.a
      eval("console.log(a)"); // ReferenceError: a is not defined
   }
})({ a: "hello" })

РЕДАКТИРОВАТЬ: как указала знающая CMS, это похоже на ошибку браузера (браузеры, использующие консоль WebKit).

Если кому-то интересно, какую мерзость я пытался придумать, для чего потребуются и «злые» eval и with, я пытался увидеть, смогу ли я получить функцию (используемую как обратный вызов), выполняемую в другом контексте, а не в тот, в котором он был определен. И нет, я вероятно (кашель) не буду использовать это нигде.. любопытнее всего.

(function (context,fn) { 
    with (context) 
       eval("("+fn+")()"); 
})({ a: "hello there" }, function () { console.log(a); })

person Cristian Sanchez    schedule 07.08.2010    source источник
comment
В каком браузере у вас такое поведение? Вы запускаете код на какой-то консоли?   -  person Christian C. Salvadó    schedule 07.08.2010
comment
@CMS: бета-версия Chrome 5.0.375.125 с использованием встроенной консоли разработчика. Редактировать: я только что попробовал это с Firefox (firebug), и это сработало, как и ожидалось. Должно быть, ошибка браузера, как вы сказали.   -  person Cristian Sanchez    schedule 07.08.2010
comment
@Daniel - он правильно работает в Chrome 6.0.472.22, если это кому-то поможет.   -  person Nick Craver    schedule 07.08.2010
comment
@Nick, @Daniel, проблема присутствует только в консоли. Я получаю такое же поведение в Chrome 6.0.472.25, я почти уверен, что эта проблема как-то связана с консолью WebKit, потому что она также воспроизводится в Safari 5.0. 1 и в сборке WebKit Nightly   -  person Christian C. Salvadó    schedule 07.08.2010
comment
Похоже, что это не ограничивается оператором with. Простой случай: (function (a) { eval("console.log(a)"); })("hello") тоже не работает. Думаю, я отправлю вопрос, если кто-то еще не захочет.   -  person Cristian Sanchez    schedule 07.08.2010
comment
@CMS - Вы должны добавить это ниже, получите +1 от меня   -  person Nick Craver    schedule 07.08.2010
comment
Оба примера у меня работают с Chrome 5.0.375.55. eval и with: два неприятных вкуса, которые отвратительны вместе!   -  person bobince    schedule 07.08.2010
comment
Отлично работает в бета-версии Chrome 5.0.375.125.   -  person David Titarenco    schedule 07.08.2010
comment
@bobince, @David, попробуйте на консоли, я тоже ReferenceError с Chrome 5.0.375   -  person Christian C. Salvadó    schedule 07.08.2010
comment
function testfunc(a) { eval("alert(a)"); } testfunc("hello"); отлично работает в консоли Chrome И консоли Firebug, (function (a) { eval("alert(a)"); })("hello") работает только в консоли Firebug (скорее всего, из-за контекста вызова)   -  person David Titarenco    schedule 08.08.2010
comment
При включении в соответствующий глобальный контекст оба варианта прекрасно работают в Chrome/Firefox/Safari... даже в IE;) polyfx.com /jstest.html   -  person David Titarenco    schedule 08.08.2010
comment
Странно... Код eval, кажется, не видит переменные аргументов в функции, когда функция была определена в FunctionExpression в консоли! Функции, определенные FunctionDeclaration или определенные в документе или URL-адресе javascript:, не затрагиваются, даже если затем выполняются из консоли. Версия с with у меня до сих пор работает даже в консоли, могу только на аргументы сбить. Странная ошибка.   -  person bobince    schedule 08.08.2010
comment
@Nick, спасибо, я оставил ответ, чтобы использовать его в качестве ссылки для регистрации ошибки.   -  person Christian C. Salvadó    schedule 08.08.2010


Ответы (4)


Это ошибка, воспроизводимая только из консоли WebKit, у нее есть проблемы с привязкой контекста вызывающей стороны, когда eval вызывается из FunctionExpression.

Когда выполняется прямой вызов eval, оцениваемый код, как вы ожидаете, должен совместно использовать обе переменные среды:

(function (arg) {
  return eval('arg');
})('foo');
// should return 'foo', throws a ReferenceError from the WebKit console

А также лексическое окружение:

(function () {
  eval('var localVar = "test"');
})();

typeof localVar; // should be 'undefined', returns 'string' on the Console

В приведенном выше примере функция localVar должна быть объявлена ​​в лексическом окружении вызывающего объекта, а не в глобальном контексте.

Для FunctionDeclarations поведение совершенно нормальное, если попробовать:

function test1(arg) {
  return eval('arg');
}
test1('foo'); // properly returns 'foo' on the WebKit console

А также

function test2() {
  eval('var localVarTest = "test"');
}
test2();
typeof localVarTest; // correctly returns 'undefined'

Мне удалось воспроизвести проблему в следующих браузерах, работающих в Windows Vista SP2:

  • Хром 5.0.375.125
  • Chrome 6.0.472.25 для разработчиков
  • Сафари 5.0.1
  • Ночная сборка WebKit r64893
person Christian C. Salvadó    schedule 07.08.2010

(function (obj) {
   with (obj) {
      alert(a); // prints out obj.a
      eval("alert(a)"); // ReferenceError: a is not defined
   }
})({ a: "hello from a with eval" })

function testfunc(a) { eval("alert(a)"); } testfunc("hello from a testfunc eval");

(function (a) { eval("alert(a)"); })("hello from a function constructor eval")

Все работает нормально: http://polyfx.com/jstest.html в FF/Chrome/Safari/ IE.

Проблема с запуском фрагментов кода с разных консолей заключается в том, что консоли обычно не соответствуют контексту. (т. е. консоль Chrome, похоже, неправильно оборачивает вещи в глобальном контексте, тогда как консоль Firebug делает). Это может быть ошибка или (что более вероятно) она может работать по назначению.

person David Titarenco    schedule 07.08.2010

Eval всегда работает в глобальной области видимости, не так ли?

person Eric    schedule 07.08.2010
comment
Нет, прямой вызов eval будет использовать контекст вызова (как лексическое, так и переменное окружение вызывающей стороны), косвенный вызов eval в ECMAScript 5, например: var foo = eval; foo('code'); будет использовать глобальный контекст, а также конструктор функций. - person Christian C. Salvadó; 07.08.2010

Если оставить в стороне eval и, новые баузеры включают метод ecma5 Function.prototype.bind для вызова функции в области действия некоторого выбранного объекта.

Для старых браузеров вы можете подделать его-

Function.prototype.bind= Function.prototype.bind || function bind(scope){
    var method= this;
    return function(){
        method.apply(scope, arguments);
    }
}
person kennebec    schedule 07.08.2010
comment
Обратите внимание, что резервная функция не соответствует стандартам, она не может предварительно заполнить или каррировать функцию с известными аргументами. Эта реализация ближе всего к вам может соответствовать спецификации ES5, работая на движке ES3. Кроме того, связывание функции не даст доступа к переменной или лексическому окружению вызывающей стороны (что, кажется, требуется OP в конце), она может только гарантировать, что значение thiscurried аргументы) будут сохранены. - person Christian C. Salvadó; 08.08.2010