Имеются ли законные применения для выражения JavaScript с инструкцией?

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

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

Ответ 1

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

Фон

JavaScript, несмотря на его поверхностное сходство с 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 переменные должны быть объявлены в начало блока, поэтому в некотором роде оно похоже). Это поведение на самом деле очень похоже на синтаксис блока let, который был представлен в более ранних версиях браузеров Mozilla, но не получил широкого распространения в других местах.

Ответ 2

Я использую оператор 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')
    )
  )
}

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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);
}

EDIT: просто проверили источник 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);" +
    "}" +
"}}}}";

Ответ 5

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

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

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

Жалоба на скорость в Crockford заключается в том, что новый контекст создается с помощью. Контексты обычно дороги. Согласен. Но если вы только что создали 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";

и т.д...

Ответ 6

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

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

with_ (object_name_here, function (_)
{
    _.a = "foo";
    _.b = "bar";
});

Ответ 7

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

var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";

Ответ 8

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

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

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

Ответ 9

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 по-прежнему является мощной идеей, которая может улучшить читаемость.

Ответ 10

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

Ответ 11

Использование "с" может сделать ваш код более сухим.

Рассмотрим следующий код:

var photo = document.getElementById('photo');
photo.style.position = 'absolute';
photo.style.left = '10px';
photo.style.top = '10px';

Вы можете высушить его до следующих значений:

with(document.getElementById('photo').style) {
  position = 'absolute';
  left = '10px';
  top = '10px';
}

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

Первый пример является более разборчивым и, вероятно, рекомендуется для большинства кодов. Но в большинстве случаев код очень укрощен. Второй из них немного более неясен, но использует выразительный характер языка для сокращения размера кода и избыточных переменных.

Я предполагаю, что люди, которые любят Java или С#, выбирают первый способ (object.member), а те, кто предпочитает Ruby или Python, выбирают последний.

Ответ 12

Имея опыт работы с Delphi, я бы сказал, что использование с должно быть оптимизацией размера последнего объекта, возможно, выполняемой каким-то алгоритмом минимизации javascript с доступом к статическому анализу кода для проверки его безопасности.

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

Утверждение VB лучше, потому что ему нужны точки, чтобы устранить неоднозначность, но выражение Delphi - это загруженный пистолет с приводом от волос, и мне кажется, что javascript один достаточно похож, чтобы оправдать то же предупреждение.

Ответ 13

Я думаю, что очевидное использование - это ярлык. Если вы, например, инициализируя объект, вы просто сохраняете множество "ObjectName". Вид как lisp "с-слотами", который позволяет писать

(with-slots (foo bar) objectname
   "some code that accesses foo and bar"

что совпадает с записью

"some code that accesses (slot-value objectname 'foo) and (slot-value objectname 'bar)""

Более очевидно, почему это ярлык, когда ваш язык позволяет "Objectname.foo", но все же.

Ответ 14

Использование с не рекомендуется и запрещено в строгом режиме ECMAScript 5. Рекомендуемая альтернатива - назначить объект, свойства которого вы хотите получить во временную переменную.

Источник: Mozilla.org

Ответ 15

Оператор with может использоваться для уменьшения размера кода или для частных членов класса, например:

// demo class framework
var Class= function(name, o) {
   var c=function(){};
   if( o.hasOwnProperty("constructor") ) {
       c= o.constructor;
   }
   delete o["constructor"];
   delete o["prototype"];
   c.prototype= {};
   for( var k in o ) c.prototype[k]= o[k];
   c.scope= Class.scope;
   c.scope.Class= c;
   c.Name= name;
   return c;
}
Class.newScope= function() {
    Class.scope= {};
    Class.scope.Scope= Class.scope;
    return Class.scope;
}

// create a new class
with( Class.newScope() ) {
   window.Foo= Class("Foo",{
      test: function() {
          alert( Class.Name );
      }
   });
}
(new Foo()).test();

Оператор with очень полезен, если вы хотите изменить область действия, что необходимо для того, чтобы иметь собственную глобальную область действия, которую вы можете манипулировать во время выполнения. Вы можете поместить на него константы или некоторые вспомогательные функции, которые часто используются, например, "toUpper", "toLower" или "isNumber", "clipNumber" aso..

О плохой производительности я часто читал: определение функции не окажет никакого влияния на производительность, ведь в моем FF функция с ограниченным доступом работает быстрее, чем без полномочий:

var o={x: 5},r, fnRAW= function(a,b){ return a*b; }, fnScoped, s, e, i;
with( o ) {
    fnScoped= function(a,b){ return a*b; };
}

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnRAW(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnScoped(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

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

Ответ 16

Использование с помощью также делает ваш код более медленным во многих реализациях, поскольку все теперь обернуто в дополнительную область для поиска. В JavaScript нет законных оснований для использования.

Ответ 17

Я думаю, что с-выражение может пригодиться при преобразовании языка шаблона в JavaScript. Например JST в base2, но я видел это чаще.

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

Ответ 18

Я думаю, что использование объектного литерала интересно, например, замена замены для использования закрытия

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       (function(info)
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       })(data[i]);
}

или оператор с равенством замыкания

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       with({info: data[i]})
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       }        
}

Я считаю, что реальный риск - это непреднамеренно minipulating переменные, которые не являются частью оператора with, поэтому мне нравится, когда объект передается в литерал, вы можете точно увидеть, что будет в добавленном контексте кода.

Ответ 19

Я создал функцию "слияния", которая устраняет некоторую эту двусмысленность с помощью инструкции with:

if (typeof Object.merge !== 'function') {
    Object.merge = function (o1, o2) { // Function to merge all of the properties from one object into another
        for(var i in o2) { o1[i] = o2[i]; }
        return o1;
    };
}

Я могу использовать его аналогично with, но я знаю, что это не повлияет на какую-либо область, на которую я не намерен повлиять.

Использование:

var eDiv = document.createElement("div");
var eHeader = Object.merge(eDiv.cloneNode(false), {className: "header", onclick: function(){ alert("Click!"); }});
function NewObj() {
    Object.merge(this, {size: 4096, initDate: new Date()});
}

Ответ 20

Для некоторых коротких фрагментов кода я хотел бы использовать тригонометрические функции, такие как sin, cos и т.д. в режиме градуса, а не в лучистом режиме. Для этой цели я использую объект AngularDegree:

AngularDegree = new function() {
this.CONV = Math.PI / 180;
this.sin = function(x) { return Math.sin( x * this.CONV ) };
this.cos = function(x) { return Math.cos( x * this.CONV ) };
this.tan = function(x) { return Math.tan( x * this.CONV ) };
this.asin = function(x) { return Math.asin( x ) / this.CONV };
this.acos = function(x) { return Math.acos( x ) / this.CONV };
this.atan = function(x) { return Math.atan( x ) / this.CONV };
this.atan2 = function(x,y) { return Math.atan2(x,y) / this.CONV };
};

Тогда я могу использовать тригонометрические функции в режиме степени без дополнительных языковых шумов в блоке with:

function getAzimut(pol,pos) {
  ...
  var d = pos.lon - pol.lon;
  with(AngularDegree) {
    var z = atan2( sin(d), cos(pol.lat)*tan(pos.lat) - sin(pol.lat)*cos(d) );
    return z;
    }
  }

Это означает: я использую объект как набор функций, которые я разрешаю в ограниченной области кода для прямого доступа. Я считаю это полезным.

Ответ 21

Я просто не вижу, как использование справки более читаемо, чем просто набирать object.member. Я не думаю, что это менее читаемо, но я не думаю, что это более читаемо.

Как и lassevk сказал, я определенно вижу, как использование с будет более склонным к ошибкам, чем просто использование очень явного синтаксиса "object.member".

Ответ 22

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

var sHeader = object.data.header.toString();
var sContent = object.data.content.toString();
var sFooter = object.data.footer.toString();

то вы можете утверждать, что with улучшит читабельность кода, сделав это:

var sHeader = null, sContent = null, sFooter = null;
with(object.data) {
    sHeader = header.toString();
    sContent = content.toString();
    sFooter = content.toString();
}

И наоборот, можно утверждать, что вы нарушаете Закон Деметры, но, опять же, возможно, нет. Я отвлекся =).

Прежде всего, знайте, что Дуглас Крокфорд рекомендует не с помощью with. Я настоятельно рекомендую вам проверить его сообщение в блоге относительно with и его альтернатив здесь.

Ответ 23

Это полезно для размещения кода, который выполняется в относительно сложной среде в контейнере: я использую его для создания локальной привязки для "окна" и для запуска кода, предназначенного для веб-браузера.

Ответ 24

Вы должны увидеть валидацию формы в javascript в W3schools http://www.w3schools.com/js/js_form_validation.asp, где форма объекта "сканируется", чтобы найти вход с именем "электронная почта"

Но я изменил его, чтобы получить от ЛЮБОЙ формы, все поля проверяются как не пустые, независимо от имени или количества поля в форме. Ну, я тестировал только текстовые поля.

Но с() сделал вещи проще. Здесь код:

function validate_required(field)
{
with (field)
  {
  if (value==null||value=="")
    {
    alert('All fields are mandtory');return false;
    }
  else
    {
    return true;
    }
  }
}

function validate_form(thisform)
{
with (thisform)
  {
    for(fiie in elements){
        if (validate_required(elements[fiie])==false){
            elements[fiie].focus();
            elements[fiie].style.border='1px solid red';
            return false;
        } else {elements[fiie].style.border='1px solid #7F9DB9';}
    }

  }
  return false;
}

Ответ 25

CoffeeScript Coco fork имеет ключевое слово with, но он просто устанавливает this (также записывается как @ в CoffeeScript/Coco) к целевому объекту внутри блока. Это устраняет двусмысленность и обеспечивает соблюдение строгого режима ES5:

with long.object.reference
  @a = 'foo'
  bar = @b

Ответ 26

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

У меня был набор возможных фрагментов (с отверстиями, обращенными сверху, снизу, слева или справа), которые я мог бы использовать, и мне нужен быстрый способ добавления списка плиток, которые всегда будут помещены и заблокированы в начале игры. Я не хотел набирать types.tbr для каждого типа в списке, поэтому я просто использовал with.

Tile.types = (function(t,l,b,r) {
  function j(a) { return a.join(' '); }
  // all possible types
  var types = { 
    br:  j(  [b,r]),
    lbr: j([l,b,r]),
    lb:  j([l,b]  ),  
    tbr: j([t,b,r]),
    tbl: j([t,b,l]),
    tlr: j([t,l,r]),
    tr:  j([t,r]  ),  
    tl:  j([t,l]  ),  
    locked: []
  };  
  // store starting (base/locked) tiles in types.locked
  with( types ) { locked = [ 
    br,  lbr, lbr, lb, 
    tbr, tbr, lbr, tbl,
    tbr, tlr, tbl, tbl,
    tr,  tlr, tlr, tl
  ] } 
  return types;
})("top","left","bottom","right");

Ответ 27

Вы можете использовать, чтобы избежать явного управления arity при использовании require.js:

var modules = requirejs.declare([{
    'App' : 'app/app'
}]);

require(modules.paths(), function() { with (modules.resolve(arguments)) {
    App.run();
}});

Реализация requirejs.declare:

requirejs.declare = function(dependencyPairs) {
    var pair;
    var dependencyKeys = [];
    var dependencyValues = [];

    for (var i=0, n=dependencyPairs.length; i<n; i++) {
        pair = dependencyPairs[i];
        for (var key in dependencyPairs[i]) {
            dependencyKeys.push(key);
            dependencyValues.push(pair[key]);
            break;
        }
    };

    return {
        paths : function() {
            return dependencyValues;
        },

        resolve : function(args) {
            var modules = {};
            for (var i=0, n=args.length; i<n; i++) {
                modules[dependencyKeys[i]] = args[i];
            }
            return modules;
        }
    }   
}

Ответ 28

Как заметил Энди Е в комментариях ответа Shog9, это потенциально неожиданное поведение возникает при использовании with с литералом объекта:

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with ({num: i}) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "[object Object]"
  }
}

Не то, что неожиданное поведение уже не было признаком with.

Если вы действительно хотите использовать этот метод, по крайней мере, используйте объект с нулевым прототипом.

function scope(o) {
  var ret = Object.create(null);
  if (typeof o !== 'object') return ret;
  Object.keys(o).forEach(function (key) {
    ret[key] = o[key];
  });
  return ret;
}

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with (scope({num: i})) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "a"
  }
}

Но это будет работать только в ES5+. Также не используйте with.

Ответ 29

Я работаю над проектом, который позволит пользователям загружать код, чтобы изменить поведение частей приложения. В этом сценарии я использую предложение with, чтобы его код не изменял ничего вне области видимости, с которой я хочу, чтобы они возились. (Упрощенная) часть кода, которую я использую для этого:

// this code is only executed once
var localScope = {
    build: undefined,

    // this is where all of the values I want to hide go; the list is rather long
    window: undefined,
    console: undefined,
    ...
};
with(localScope) {
    build = function(userCode) {
        eval('var builtFunction = function(options) {' + userCode + '}');
        return builtFunction;
    }
}
var build = localScope.build;
delete localScope.build;

// this is how I use the build method
var userCode = 'return "Hello, World!";';
var userFunction = build(userCode);

Этот код гарантирует (несколько), что пользовательский код не имеет доступа к каким-либо объектам с глобальным охватом, таким как window, ни к какой-либо из моих локальных переменных через закрытие.

Как слово мудрым, мне все же приходится выполнять статические проверки кода на представленном пользователем коде, чтобы убедиться, что они не используют другие подлые манеры для доступа к глобальной области. Например, следующий пользовательский код захватывает прямой доступ к window:

test = function() {
     return this.window
};
return test();

Ответ 30

Мой

switch(e.type) {
    case gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED: blah
    case gapi.drive.realtime.ErrorType.CLIENT_ERROR: blah
    case gapi.drive.realtime.ErrorType.NOT_FOUND: blah
}

сводится к

with(gapi.drive.realtime.ErrorType) {switch(e.type) {
    case TOKEN_REFRESH_REQUIRED: blah
    case CLIENT_ERROR: blah
    case NOT_FOUND: blah
}}

Можете ли вы доверять этому низкокачественному коду? Нет, мы видим, что он был абсолютно нечитаемым. Этот пример неоспоримо доказывает, что нет никакой необходимости с-выражение, если я беру читаемость право;)