en.javascript.info/01-js/05-functions-closures/07-with/article.md
Ilya Kantor f301cb744d init
2014-10-26 22:10:13 +03:00

6.2 KiB
Raw Blame History

Устаревшая конструкция "with"

Конструкция with позволяет использовать в качестве области видимости для переменных произвольный объект.

В современном JavaScript от этой конструкции отказались. С use strict она не работает, но её ещё можно найти в старом коде, так что стоит познакомиться с ней, чтобы если что -- понимать, о чём речь.

[cut] Синтаксис:

with(obj) {
  ... код ...
}

Любое обращение к переменной внутри with сначала ищет её среди свойств obj, а только потом -- вне with.

Пример

В примере ниже переменная будет взята не из глобальной области, а из obj:

//+ run
var a = 5;

var obj = { a : 10 };

*!*
with(obj) {
  alert(a); // 10, из obj
}
*/!*

Попробуем получить переменную, которой в obj нет:

//+ run
var b = 1;

var obj = { a : 10 };

*!*
with(obj) {
  alert(b); // 1, из window
}
*/!*

Здесь интерпретатор сначала проверяет наличие obj.b, не находит и идет вне with.

Особенно забавно выглядит применение вложенных with:

//+ run
var obj = {
  weight: 10,
  size: {
    width: 5,
    height: 7
  }
};

with(obj) {
  with(size) { // size будет взят из obj
*!*
    alert( width*height / weight ); // width,height из size, weight из obj
*/!*
  }
}

Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: size => obj => window

Изменения переменной

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

Например:

//+ run
var obj = { a : 10 }

*!*
with(obj) {
  a = 20;
}
*/!*
alert(obj.a); // 20, переменная была изменена в объекте

Почему отказались от with?

Есть несколько причин.

  1. В современном стандарте `JavaScript` отказались от `with`, потому что **конструкция `with` подвержена ошибкам и непрозрачна.**

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

    Например:

    //+ run
    var obj = { weight: 10 };
    
    with(obj) {
      weight = 20; // (1)
      size = 35;   // (2)
    }
    
    alert(obj.size);
    alert(window.size);
    

    В строке (2) присваивается свойство, отсутствующее в obj. В результате интерпретатор, не найдя его, создает новую глобальную переменную window.size.

    Такие ошибки редки, но очень сложны в отладке, особенно если size изменилась не в window, а где-нибудь во внешнем LexicalEnvironment.

  2. Еще одна причина -- **алгоритмы сжатия JavaScript не любят `with`**. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Если вкратце -- они либо сжимают код с `with` с ошибками, либо оставляют его частично несжатым.
  3. Ну и, наконец, **производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы**. Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`.

    Вот, к примеру, запустите этот код в современном браузере. Производительность функции fast существенно отличается slow с пустым(!) with. И дело тут именно в with, т.к. наличие этой конструкции препятствует оптимизации.

    //+ run
    var i = 0;
    
    function fast() {
      i++;
    }
    
    function slow() { 
      with(i) {}
      i++;
    }
    
    
    var time = new Date();
    while(i < 1000000) fast();
    alert(new Date - time);
    
    var time = new Date();
    i=0;
    while(i < 1000000) slow();
    alert(new Date - time);
    

Замена with

Вместо with рекомендуется использовать временную переменную, например:

/* вместо
with(elem.style) {
  top = '10px';
  left = '20px';
}
*/

var s = elem.style;

s.top = '10px';
s.left = '0';

Это не так элегантно, но убирает лишний уровень вложенности и абсолютно точно понятно, что будет происходить и куда присвоятся свойства.

Итого

  • Конструкция `with(obj) { ... }` использует `obj` как дополнительную область видимости. Все переменные, к которым идет обращение внутри блока, сначала ищутся в `obj`.
  • Конструкция `with` устарела и не рекомендуется по ряду причин. Избегайте её.