Существуют ли законные варианты использования оператора «with» в JavaScript? - PullRequest
361 голосов
/ 14 сентября 2008

комментарии Алана Сторма в ответ на мой ответ относительно заявления with заставили меня задуматься. Я редко находил причину использовать эту особенность языка и никогда не задумывался над тем, как это может вызвать проблемы. Теперь мне интересно, как я мог бы эффективно использовать with, избегая при этом его ловушек.

Где вы нашли выражение with полезным?

Ответы [ 31 ]

516 голосов
/ 09 октября 2008

Сегодня мне пришло в голову другое использование, поэтому я взволнованно искал в Интернете и нашел упоминание об этом: Определение переменных внутри области действия .

Фон

JavaScript, несмотря на внешнее сходство с C и C ++, не включает переменные в блок, в котором они определены:

var name = "Joe";
if ( true )
{
   var name = "Jack";
}
// name now contains "Jack"

Объявление замыкания в цикле - обычная задача, которая может привести к ошибкам:

for (var i=0; i<3; ++i)
{
   var num = i;
   setTimeout(function() { alert(num); }, 10);
}

Поскольку цикл for не вводит новую область видимости, все три функции будут совместно использовать один и тот же num со значением 2.

Новая сфера: let и with

С введением оператора let в ES6 становится легко вводить новую область видимости, когда необходимо избежать этих проблем:

// variables introduced in this statement 
// are scoped to each iteration of the loop
for (let i=0; i<3; ++i)
{
   setTimeout(function() { alert(i); }, 10);
}

Или даже:

for (var i=0; i<3; ++i)
{
   // variables introduced in this statement 
   // are scoped to the block containing it.
   let num = i;
   setTimeout(function() { alert(num); }, 10);
}

До тех пор, пока ES6 не станет универсально доступным, это использование будет ограничено новейшими браузерами и разработчиками, желающими использовать транспортеры. Однако мы можем легко смоделировать это поведение, используя with:

for (var i=0; i<3; ++i)
{
   // object members introduced in this statement 
   // are scoped to the block following it.
   with ({num: i})
   {
      setTimeout(function() { alert(num); }, 10);
   }
}

Цикл теперь работает так, как задумано, создавая три отдельные переменные со значениями от 0 до 2. Обратите внимание, что переменные, объявленные в блоке, не ограничиваются им, в отличие от поведения блоков в C ++ (в C переменные должны быть объявлены в начале блока, так что это похоже). Это поведение на самом деле очень похоже на let блочный синтаксис , представленный в более ранних версиях браузеров Mozilla, но не получивший широкого распространения в других местах.

159 голосов
/ 22 сентября 2009

Я использовал оператор with как простую форму импорта по областям. Допустим, у вас есть какой-то компоновщик разметки. Вместо того чтобы писать:

markupbuilder.div(
  markupbuilder.p('Hi! I am a paragraph!',
    markupbuilder.span('I am a span inside a paragraph')
  )
)

Вместо этого вы можете написать:

with(markupbuilder){
  div(
    p('Hi! I am a paragraph!',
      span('I am a span inside a paragraph')
    )
  )
}

Для этого варианта использования я не выполняю никаких заданий, поэтому у меня нет проблемы с неоднозначностью, связанной с этим.

81 голосов
/ 15 сентября 2008

Как указывалось в моих предыдущих комментариях, я не думаю, что вы можете безопасно использовать with независимо от того, насколько заманчивым оно может быть в любой конкретной ситуации. Поскольку проблема здесь не рассматривается напрямую, я повторю это. Рассмотрим следующий код

user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);

with(user){
    name = 'Bob';
    age  = 20;
}

Без тщательного изучения этих вызовов функций невозможно определить состояние вашей программы после запуска этого кода. Если user.name уже был установлен, теперь он будет Bob. Если он не был установлен, глобальный name будет инициализирован или изменен на Bob, а объект user останется без свойства name.

Ошибки случаются. Если вы используете с , вы в конечном итоге сделаете это и увеличите вероятность того, что ваша программа потерпит неудачу. Хуже того, вы можете столкнуться с рабочим кодом, который устанавливает глобал в блоке with, либо намеренно, либо через автора, не знающего об этой особенности конструкции. Это очень похоже на столкновение с провалом на коммутаторе, вы не представляете, задумал ли автор это, и нет способа узнать, приведет ли «исправление» кода к регрессии.

Современные языки программирования переполнены функциями. Некоторые функции, после многих лет использования, оказались плохими, и их следует избегать. Javascript with является одним из них.

65 голосов
/ 20 октября 2010

Я действительно нашел, что выражение with было невероятно полезным в последнее время. Эта техника никогда не приходила мне в голову, пока я не начал свой текущий проект - консоль командной строки, написанную на JavaScript. Я пытался эмулировать API-интерфейсы консоли Firebug / WebKit, где в консоль можно вводить специальные команды, но они не переопределяют никакие переменные в глобальной области видимости. Я думал об этом, пытаясь преодолеть проблему, о которой упоминал в комментариях к превосходному ответу Shog9 .

Чтобы достичь этого эффекта, я использовал два оператора with, чтобы «наслоить» область действия за глобальной областью действия:

with (consoleCommands) {
    with (window) {
        eval(expression); 
    }
}

Самое замечательное в этом методе заключается в том, что, кроме недостатков производительности, он не страдает от обычных опасений оператора with, потому что мы все равно оцениваем в глобальном масштабе - нет опасности для переменных снаружи наш псевдоскоп от изменения.

Я был вдохновлен опубликовать этот ответ, когда, к моему удивлению, мне удалось найти ту же технику, что и в других местах - исходный код Chromium !

InjectedScript._evaluateOn = function(evalFunction, object, expression) {
    InjectedScript._ensureCommandLineAPIInstalled();
    // Surround the expression in with statements to inject our command line API so that
    // the window object properties still take more precedent than our API functions.
    expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
    return evalFunction.call(object, expression);
}

РЕДАКТИРОВАТЬ: Только что проверил источник Firebug, они цепочка 4 с операторами вместе для еще большего количества слоев. Сумасшедший!

const evalScript = "with (__win__.__scope__.vars) { with (__win__.__scope__.api) { with (__win__.__scope__.userVars) { with (__win__) {" +
    "try {" +
        "__win__.__scope__.callback(eval(__win__.__scope__.expr));" +
    "} catch (exc) {" +
        "__win__.__scope__.callback(exc, true);" +
    "}" +
"}}}}";
54 голосов
/ 22 июня 2009

Да, да и да. Существует очень законное использование. Смотреть:

with (document.getElementById("blah").style) {
    background = "black";
    color = "blue";
    border = "1px solid green";
}

В основном любые другие DOM или CSS-хуки - фантастическое применение с. Это не значит, что «CloneNode» будет неопределенным и вернется к глобальной области видимости, если вы не вышли из своего пути и решили сделать это возможным.

Жалоба Крокфорда на скорость заключается в том, что новый контекст создается с помощью. Контексты, как правило, дороги. Согласен. Но если вы только что создали div и у вас нет какой-либо платформы для настройки вашего css и вам нужно вручную установить около 15 свойств CSS, тогда создание контекста, вероятно, будет дешевле, чем создание переменных и 15 разыменований:

var element = document.createElement("div"),
    elementStyle = element.style;

elementStyle.fontWeight = "bold";
elementStyle.fontSize = "1.5em";
elementStyle.color = "#55d";
elementStyle.marginLeft = "2px";

и т.д ...

34 голосов
/ 14 сентября 2008

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

var with_ = function (obj, func) { func (obj); };

with_ (object_name_here, function (_)
{
    _.a = "foo";
    _.b = "bar";
});
26 голосов
/ 15 сентября 2008

Вряд ли это того стоит, так как вы можете делать следующее:

var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";
18 голосов
/ 23 сентября 2009

Я никогда не пользуюсь, не вижу причин и не рекомендую.

Проблема с with заключается в том, что предотвращает многочисленные лексические оптимизации , которые может выполнять реализация ECMAScript. Учитывая появление быстрых двигателей на базе JIT, эта проблема, вероятно, станет еще более важной в ближайшем будущем.

Может показаться, что with допускает более чистые конструкции (когда, скажем, вводится новая область вместо обычной обертки анонимной функции или замена многословного псевдонима), но это действительно не стоит . Помимо снижения производительности, всегда существует опасность присвоения свойству неправильного объекта (когда свойство не найдено в объекте во введенной области видимости) и, возможно, ошибочно вводить глобальные переменные. IIRC, последняя проблема, которая побудила Крокфорда рекомендовать избегать with.

15 голосов
/ 14 сентября 2008

Visual Basic.NET имеет аналогичный оператор With. Один из наиболее распространенных способов, которыми я пользуюсь, - это быстро установить ряд свойств. Вместо:

someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''

, я могу написать:

With someObject
    .Foo = ''
    .Bar = ''
    .Baz = ''
End With

Это не просто вопрос лени. Это также делает для более читабельного кода. И, в отличие от JavaScript, он не страдает от двусмысленности, так как все, на что влияет оператор, ставят префикс . (точка). Итак, следующие два четко различимы:

With someObject
    .Foo = ''
End With

против

With someObject
    Foo = ''
End With

Первый - someObject.Foo; последний - Foo в объеме за пределами someObject.

Я считаю, что отсутствие различий в JavaScript делает его гораздо менее полезным, чем вариант Visual Basic, так как риск неоднозначности слишком высок. Кроме этого, with по-прежнему мощная идея, которая может улучшить читаемость.

8 голосов
/ 03 сентября 2010

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

...