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


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

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



Answers



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

Задний план

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

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 block, введенный в более ранних версиях браузеров Mozilla, но не широко принятый в других местах.




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

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




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

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

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

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

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

Современные языки программирования заполнены функциями. Некоторые функции, после многих лет использования, считаются плохими, и их следует избегать. Javascript 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);" +
    "}" +
"}}}}";



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

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

и т.д...




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

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

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



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

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



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

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

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




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




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

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

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, выбирают последний.




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




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




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

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

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




Оператор 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 5. Рекомендуемая альтернатива - назначить объект, свойства которого вы хотите получить во временную переменную.

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




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




Я думаю, что полезность 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 его альтернативами здесь .




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

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




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

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, поэтому мне нравится, когда в литературу объекта передается, вы можете точно видеть, что будет в добавленном контексте кода.




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



For some short code pieces, I would like to use the trigonometric functions like sin , cos etc. in degree mode instead of in radiant mode. For this purpose, I use an AngularDegree object:

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

Then I can use the trigonometric functions in degree mode without further language noise in a with block:

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

This means: I use an object as a collection of functions, which I enable in a limited code region for direct access. I find this useful.




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.




It's good for putting code that runs in a relatively complicated environment into a container: I use it to make a local binding for "window" and such to run code meant for a web browser.




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



CoffeeScript's Coco fork has a with keyword, but it simply sets this (also writable as @ in CoffeeScript/Coco) to the target object within the block. This removes ambiguity and achieves ES5 strict mode compliance:

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



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



You can use with to avoid having to explicitly manage arity when using require.js:

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

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

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



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 .




I am working on a project that will allow users to upload code in order to modify the behavior of parts of the application. In this scenario, I have been using a with clause to keep their code from modifying anything outside of the scope that I want them to mess around with. The (simplified) portion of code I use to do this is:

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

This code ensures (somewhat) that the user-defined code neither has access to any globally-scoped objects such as window nor to any of my local variables through a closure.

Just as a word to the wise, I still have to perform static code checks on the user-submitted code to ensure they aren't using other sneaky manners to access global scope. For instance, the following user-defined code grabs direct access to window :

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



My

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

Can you trust so low-quality code? No, we see that it was made absolutely unreadable. This example undeniably proves that there is no need for with-statement, if I am taking readability right ;)