Оператор with и почему его не стоит использовать

http://www.2ality.com/2011/06/with-statement.html
  • Перевод
Эта статья объясняет как работает with в JavaScript и почему его не рекомендуется использовать.

Синтаксис оператора with


  with (object)
        statement

with создает новую область видимости «scope» и представляет свойства объекта «object» как локальные переменные выражения «statement». Пример (скобки не обязательны для одного выражения, но их рекомендуется использовать):
with({ first: "John" }) { console.log("Hello " + first); } // Hello John

Существует похожий объект свойства которого одновременно являются глобальными переменными — этот объект называется global (в браузерах это window). Но в отличии от глобального объекта, переменные, которые объявлены в выражении with (блок statement) не добавляются к объекту «object», а просачиваются во внешний scope и существуют дальше.
with({}) { var x = "abc"; }
console.log(x) // 'abc'

Оператор with является устаревшим


Использование выражения with не желательно. Его использование запрещено в Strict Mode:
function foo() { "use strict"; with({}); }
// SyntaxError: strict mode code may not contain 'with' statements

Чем же заменить with:
with(foo.bar.baz) {
    console.log("Hello "+first+" "+last);
}

Вместо передачи переменной в with используйте короткое имя:
var b = foo.bar.baz;
console.log("Hello "+b.first+" "+b.last);

Если вы не желаете объявлять временную переменную в вашем scope, то используйте IIFE:
(function() {
    var b = foo.bar.baz;
    console.log("Hello "+b.first+" "+b.last);
}());

Вы также можете переслать этот объект в качестве параметра IIFE:
(function(b) {
    console.log("Hello "+b.first+" "+b.last);
}(foo.bar.baz));

Так почему же with — это плохо


Чтобы понять почему with является устаревшим, давайте рассмотрим пример и увидим как аргумент функции меняет её поведение. Вот эта функция:
function foo(arg) {
    with(arg) {
        console.log("arg: " + arg)
    }
}

Запустим функцию с разными параметрами:
foo("Hello");
// arg: Hello  - параметр arg
    
foo({});
// arg: [object Object]  - параметр arg
    
foo({ arg: "Hello" });
// arg: Hello  - свойство arg.arg!

Есть две причины по которым with является устаревшим:
Производительность: оператор with невозможно оптимизировать автоматически, потому что заранее не известно куда будет ссылаться arg: на реальную переменную или на свойство переменной внутри with. Это может измениться при каждом вызове.

Безопасность: нельзя определить куда ссылается переменная глядя на её окрестности (её лексическое окружение). По словам Brendan Eich именно из-за этого with считается устаревшим, а не из-за соображений производительности.
Поделиться публикацией

Похожие публикации

Комментарии 26
    +8
    Как по мне, так
    with({}) { var x = "abc"; }
    console.log(x) // 'abc'
    существенный недостаток
      0
      with({}) { var this.x = "abc"; }
      console.log(x); // undefined

      чуете где подвох?
        0
        var this.x = "abc";

        Вот тут действительно есть подвох. Это невалидное выражение.
        Если же речь таки шла о
        with({}) { this.x = "abc"; }
        console.log(x);

        То будет возвращен «abc»
          0
          var забыл выкинуть.
          То будет возвращен «abc»

          Нет, не будет.
          И ещё в догонку:
          var x='a';
          with({x:'b'}) {
          	console.log('1:'+x); // 1:b
          	x = 'c';
          	console.log('2:'+x); // 2:c
          }
          console.log('3:'+x); // 3:a
          0
          Понял, но всё равно у меня х = 123
          123
          with ({}) { this.x = "abc" }
          "abc"
          x
          "abc"

          Chromium 11
            0
            Довольно странно, т.к. я проверял на node.js, что по сути есть почти тот же v8, что и в хромиуме
              0
              with ({}) { console.log(this) }
              DOMWindow
              undefined

              То есть this в контексте with ({}) используется глобальный
                0
                this.a=1;
                with ({x:'1'}) {
                	console.log(this); // { a: 1 }
                }
                

                то есть тот же эффект, что есть довольно странно и неправильно.
                Однако код приведенный выше:
                var x='a';
                with({x:'b'}) {
                	console.log('1:'+x); // 1:b
                	x = 'c';
                	console.log('2:'+x); // 2:c
                }
                console.log('3:'+x); // 3:a

                ведёт себя вполне корректно.
                  0
                  Видимо в момент перехода «мапятся» существующие свойства, но несуществующие внутри with не создаются и относятся к вышележащему контексту. И, в принципе, логика в этом есть, иначе доступ к вышележащему был бы затруднён, код
                  a = 0;
                  with ({b: 1, c: 2}) {
                      a = b + c;
                  }
                  a == 3;
                  

                  возвращал бы false. Правда конфликты имён могут возникнуть. Так что правильно depricated :)
                    0
                    а вас не смущает, что конфликты могут возникнуть внутри и снаружи for? ;)
                    a = 0;
                    for (var i = 4; i--;) {
                      a += i;
                    }
                    
                      0
                      Ни капли, for не меняет контекст выполнения :)
                        0
                        точно так же, как with)
                          0
                          Неправильно выразился, не выполнения, а цепочку распознавания идентификаторов или как там её. А with её частично меняет, но не очевидно, если не вдаваться в нюансы или их забыть.
                          var o = {};
                          
                          with (o) {
                            y = 1;
                            console.log(y); // 1
                          }
                          console.log(y); // 1
                          
                          with (o) {
                            y = 1;
                            console.log(y); // 1
                            o.y = 2;
                            console.log(y); // 2
                            y = 3;
                            console.log(y); // 3
                          }
                          console.log(y); // 1
                          


                          Присваивание y ведёт себя по разному в зависимости от того было ли присваивание y перед ним. А если бы было o[someLongExpressionThatReturnObject().toString] = 2; было бы ещё менее очевидно.
                            0
                            * o.y перед ним
                              0
                              with очень похож на let, imho
            0
            Как по мне, так это не недостаток, а вполне логичное поведение.
            Во-первых, есть разница между var и let.
            Во-вторых, вы можете использовать такой подход:
            with({ x: 'abc' }) { }
            console.log(x) // undefined
            
            +1
            У меня есть только одно место где используется with:
             function exec(__code__, scope) { with(scope || global) return eval(__code__); } 
            Да, я еретик и отступник, горетть мне в аду!
              0
              Интерпретатор жаваскрипта на жаваскрипте?
              0
              Джон Резиг использует with в своей функции для шаблонизирования. Не подскажете зачем?
                0
                Это треш конечно…
                В принципе если бы не было доступа к global, то было бы весьма юзабельно. В т.ч. для всяких песочниц и eval-ов. А так да, всю малину портит.

                Блин, что-ж JavaScript так медленно развивается то? За последние лет 5 наверное ни одной фичи в язык не добавили. Все зациклились на дополнительных библиотеках/API и разгоне, а то, что приходится писать на языке 10-летней давности никого не волнует…
                Только вон в Mozilla есть подвижки с поддержкой JavaScript 1.7-1.8 хоть какая-то радость
                  0
                  Погуглите Хабр на тему «JavaScript.next», в частности статьи dsCode и azproduction
                  0
                  любая конструкция плоха в неумелых руках
                  я его использую для импорта объектов из неймспейса. гадить в глобалы не камильфо, а писать неймспейс перед каждым классом — запарно. поэтому:

                  $ns= {}

                  with($ns){
                  $ns.$Clock=…
                  }

                  with($ns){
                  $ns.$mainClock= new $Clock(… )
                  }
                    0
                    и как IIFE с производительностью в цикле?
                    эх когда в JS будет что-то типа using(var f=a.b.c.d[i]){alert(f.x);}
                    ну и блочная область видимости переменных соответственно…
                      0
                      Let мене блочную видимость
                        0
                        спасибо, нашел топик про него тут
                        жаль что кроме FF он нигде не работает, проверял IE9,Chrome, Opera, Safari
                      +1
                      Между прочим. Хотя я никогда не использовал with, но теперь жалею, что его запретили. Ведь это была бы отличная замена IIFE и временная мера для let.
                      ArtemSmirnov писал об этом, но он не сказал, где именно это выгодно использовать.
                      Решить проблемы «Производительность» и «Безопасность» можно очень легко — запретить использовать в with переменную, только объект:
                      with(obj); // Error
                      with({ x: 123 }); // Success
                      


                      Это можно использовать вместо IIFE:
                      <span>0</span>
                      <span>1</span>
                      <span>2</span>
                      <span>3</span>
                      


                      for (var i = 4; i--;) {
                        $$('span')[i].onclick = function () {
                          alert(i); // fail
                        };
                      }
                      
                      // IIFE
                      for (var i = 4; i--;) {
                        $$('span')[i].onclick = function (i) {
                          return function () {
                            alert(i); // success
                          };
                        }(i);
                      }
                      
                      // with
                      for (var i = 4; i--;) with({ i:i }) {
                        $$('span')[i].onclick = function () {
                          alert(i); // success
                        };
                      }
                      


                      Между прочим, у такого подхода есть даже некоторые преимущества по сравнению с let

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое