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


Answers

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

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

Question

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

Где вы нашли полезное заявление?




I created a "merge" function which eliminates some of this ambiguity with the with statement:

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

I can use it similarly to with , but I can know it won't affect any scope which I don't intend for it to affect.

Применение:

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



You got to see the validation of a form in javascript at W3schools http://www.w3schools.com/js/js_form_validation.asp where the object form is "scanned" through to find an input with name 'email'

But i've modified it to get from ANY form all the fields validate as not empty, regardless of the name or quantity of field in a form. Well i've tested only text-fields.

But the with() made things simpler. Вот код:

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



На самом деле я нашел последнее заявление с невероятной полезностью. Этот метод никогда не приходил мне в голову, пока я не начал свой текущий проект - консоль командной строки, написанная на 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);" +
    "}" +
"}}}}";



As Andy E pointed out in the comments of Shog9's answer, this potentially-unexpected behavior occurs when using with with an object literal:

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]"
  }
}

Not that unexpected behavior wasn't already a hallmark of with .

If you really still want to use this technique, at least use an object with a null prototype.

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"
  }
}

But this will only work in ES5+. Also don't use with .




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

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




Оператор 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" );

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




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

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

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




I just really don't see how using the with is any more readable than just typing object.member. I don't think it's any less readable, but I don't think it's any more readable either.

Like lassevk said, I can definitely see how using with would be more error prone than just using the very explicit "object.member" syntax.




Here's a good use for with : adding new elements to an Object Literal, based on values stored in that Object. Here's an example that I just used today:

I had a set of possible tiles (with openings facing top, bottom, left, or right) that could be used, and I wanted a quick way of adding a list of tiles which would be always placed and locked at the start of the game. I didn't want to keep typing types.tbr for each type in the list, so I just used 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");



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




Я думаю, что очевидное использование - это ярлык. Если вы, например, инициализируете объект, вы просто сохраняете множество «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», но все же.




мой

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
}

boils down to

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

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




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

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

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 или C #, выбирают первый способ (object.member), а те, кто предпочитает Ruby или Python, выбирают последний.




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

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

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