Комментарии Алана Сторма в ответ на мой ответ относительно Заявление with заставил меня задуматься. Я редко нахожу причину использовать именно эту языковую функцию и никогда особо не задумывался о том, как это может вызвать проблемы. Теперь мне любопытно, как я могу эффективно использовать with, избегая при этом его подводных камней.
В чем вы нашли заявление with полезным?
Когда-то у него могло быть много правильных применений. Но это спорный вопрос. ES5 Strict удалил with, так что такого больше нет.
Здесь стоит отметить, что ES5 Strict по-прежнему необязательный.
Вместо удаления «with» в ES5 strict, не лучше ли было бы изменить стандарт, чтобы, если переменная не найдена, любое присвоение, сделанное внутри «with», было привязано к объекту аргумента?
@JussiR: Наверное. Но проблема в том, что это может сломать работу старых браузеров.
Я нахожу (это) очень полезным, поэтому классы похожи на C++.
Это прекрасный список вещей.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


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 по-прежнему является мощной идеей, которая может улучшить читаемость.
Достаточно верно. Однако не отвечает на его вопрос. Так что это не по теме.
Это тоже было у меня в голове. кто-то должен был это сказать. Почему в JavaScript не может быть просто точка?
Согласны, точечная нотация лучше, желаю, чтобы JavaScript использовал ее. +1
Я думаю, что очевидное использование - это ярлык. Если вы, например, инициализируя объект, вы просто экономите, набирая много «ObjectName». Что-то вроде "with-slots" в 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", но все же.
приятно видеть лисп-код! Я думаю, что «with» в javascript, очевидно, вдохновлено его корнями схемы как языка, но, увы, вы получили отрицательное голосование за размещение LISP в вопросе javascript.
Голосование по основному принципу. «with» - феноменально мощная конструкция. Но большинство людей, использующих JS, не понимают замыканий и пишут смехотворно сложные системы наследования классов Java поверх JS - так как же они могли знать о потенциале метапрограммирования, который предлагает «with»?
Конечно, with-slots требует, чтобы вы указали, какие слоты вы используете, тогда как with будет использовать любые слоты, которые будут связаны во время выполнения.
Имея опыт работы с Delphi, я бы сказал, что использование с должно быть последней возможностью оптимизации размера, возможно, выполняемой каким-то алгоритмом минимизатора javascript с доступом к статическому анализу кода для проверки его безопасности.
Проблемы с областью видимости, с которыми вы можете столкнуться при либеральном использовании оператора с, могут стать настоящей головной болью в **, и я бы не хотел, чтобы кто-либо испытал сеанс отладки, чтобы выяснить, что он .. происходит в вашем коде. только для того, чтобы узнать, что он захватил член объекта или неправильную локальную переменную вместо вашей глобальной или внешней переменной области видимости, которую вы намеревались.
Оператор VB с лучше тем, что ему нужны точки для устранения неоднозначности области видимости, но оператор Delphi с - это заряженный пистолет с hairtrigger, и мне кажется, что javascript достаточно похож, чтобы гарантировать такое же предупреждение.
Оператор javascript with хуже, чем оператор Delphi. В Delphi with работает так же быстро (если не быстрее), как нотация object.member. В javascript with необходимо пройти по области видимости для проверки совпадающих членов, что всегда делает его медленнее, чем нотация object.member.
Вы можете определить небольшую вспомогательную функцию, чтобы обеспечить преимущества with без двусмысленности:
var with_ = function (obj, func) { func (obj); };
with_ (object_name_here, function (_)
{
_.a = "foo";
_.b = "bar";
});
Боже, МОЯ ГОЛОВА ВЗРЫВАЛАСЬ! без неоднозначность? Должен проголосовать за это, чувак!
@Jarrod: что в этом смешного (is.gd/ktoZ)? Практически все, кто пользуется этим сайтом, умнее меня, так что простите меня, если я ошибаюсь, но это кажется плохой информацией.
Но это просто дольше и труднее понять, как это сделать: var _ = obj_name_here; _.a = "foo"; _.b = "бар;
Рене: С этим вы откроете переменную "_" для внешней области видимости, что приведет к потенциальным ошибкам. Он также покажет любые временные переменные, используемые при вычислении параметров объекта.
Вы получите больше ошибок из-за того, что with_ является грязной двойной версией (function(_){ _.a = "foo"; })(object_here); (стандартный способ имитации блоков в стиле c / java). Используйте это вместо этого.
Я просто не понимаю, почему использование with более читаемо, чем просто набирать object.member. Я не думаю, что он стал менее читаемым, но и не думаю, что он стал читаемее.
Как сказал lassevk, я определенно вижу, что использование with будет более подвержено ошибкам, чем просто использование очень явного синтаксиса «object.member».
Использование with также замедляет ваш код во многих реализациях, так как теперь все оборачивается дополнительной областью поиска. Нет законной причины для использования with в JavaScript.
Преждевременная оптимизация. Не заявляйте, что «медленнее», если вы не подсчитали; любые накладные расходы, вероятно, тривиальны как для современных, так и для древних js impl.
Я категорически не согласен с вашим выводом, основанным на том, что не может быть проблемой для других разработчиков, кроме вас.
@mk: хорошо, здесь вам не по себе: var obj = {a:0,b:0,c:0};var d=+new Date;with(obj){for(var i=0;i<1000000;++i){a+=1;b+=1;c+=1}}+new Date-d; дает в среднем 2500, а var obj = {a:0,b:0,c:0};var d=+new Date;for(var i=0;i<1000000;++i){obj.a+=1;obj.b+=1;obj.c+=1}+new Date-d; дает в среднем 750, что делает его использование более чем в 3 раза медленнее.
Я просто запустил их в Chrome 23 в консоли, когда увидел это. Результаты, которые я получил, были 1138 для кода with и 903 без. С этой крошечной разницей даже в тесном цикле я бы сделал выбор, основанный на простоте кодирования и легкости рефакторинга в каждом конкретном случае, прежде чем беспокоиться о производительности.
Как указывалось в моих предыдущих комментариях, я не думаю, что вы можете безопасно использовать with, независимо от того, насколько заманчиво это может быть в той или иной ситуации. Поскольку здесь вопрос не освещается, повторюсь. Рассмотрим следующий код
user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);
with(user){
name = 'Bob';
age = 20;
}
Без тщательного исследования этих вызовов функций невозможно определить, в каком состоянии будет находиться ваша программа после выполнения этого кода. Если user.name уже был установлен, теперь это будет Bob. Если он не был установлен, глобальный name будет инициализирован или изменен на Bob, а объект user останется без свойства name.
Ошибки случаются. Если вы используете с, вы в конечном итоге сделаете это и увеличите шансы, что ваша программа потерпит неудачу. Хуже того, вы можете столкнуться с рабочим кодом, который устанавливает глобальное значение в блоке with либо намеренно, либо из-за того, что автор не знает об этой причуде конструкции. Это очень похоже на провал при переключении: вы понятия не имеете, задумал ли автор это, и нет никакого способа узнать, приведет ли «исправление» кода к регрессии.
Современные языки программирования полны функций. Некоторые функции после многих лет использования оказываются плохими, и их следует избегать. Javascript with - один из них.
Эта проблема возникает только тогда, когда вы присваиваете значения атрибуту объекта. Но что, если вы используете его только для чтения значений? Я утверждаю, что в этом случае его можно использовать.
Та же проблема относится и к чтению значений Тоби. В приведенном выше фрагменте кода вы не знаете, установлено ли имя для объекта пользователя, поэтому вы не узнаете, читаете ли вы глобальное имя или имя пользователя.
При чтении значений существует четкое правило приоритета: атрибуты объекта проверяются перед переменными вне области видимости. Это не отличается от определения области видимости переменных в функциях. Настоящая проблема с присваиванием и 'with', насколько я понимаю, заключается в том, что происходит ли присвоение атрибута, зависит от того, существует ли атрибут в текущем рассматриваемом объекте, который является свойством времени выполнения и не может быть легко выведен посмотрев на код.
Я думаю, ты можешь быть здесь, Тоби. Мне достаточно проблемы записи, чтобы полностью отказаться от конструкции.
Вряд ли стоит того, ведь вы можете сделать следующее:
var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";
Для меня это не имело никакого смысла. Хотя я не эксперт в JavaScript
@WmasterJ Для ясности см. Этот пост: yuiblog.com/blog/2006/04/11/with-statement-considered-harmfu l
Длинные имена переменных - не единственный вариант использования with.
Я думаю, что полезность 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 и его альтернативах здесь.
Спасибо за ответ, Том. Я прочитал рекомендацию Крокфорда, и, хотя она имеет смысл, до сих пор далеко. Я прихожу к идее - косвенно затронутой Докманом - что реальная сила with {} заключается в том, как ее можно использовать для управления областью видимости ...
Сегодня мне пришло в голову другое использование, поэтому я взволнованно поискал в Интернете и нашел существующее упоминание о нем: Определение переменных внутри области видимости блока.
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 не станет универсальным, его использование будет ограничено новейшими браузерами и разработчиками, желающими использовать транспилеры. Однако мы можем легко смоделировать это поведение с помощью 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++ (в C переменные должны быть объявлены в начале блок, так что в некотором роде он похож). Это поведение на самом деле очень похоже на Синтаксис блока let, представленное в более ранних версиях браузеров Mozilla, но не получившее широкого распространения в других местах.
Никогда не думал об использовании with с буквальным, кажется законным.
Это действительно действительно круто. Я никогда не думал о том, чтобы так поиграть с областью видимости JavaScript. Полностью расширил новые области моего кодирования. Хотел бы я проголосовать 10 раз!
Для тех, кто все еще против, всегда можно использовать закрытие: for (var i = 0; i < 3; ++i) { setTimeout ((function () { var num = i; return function () { alert (num); }; }) (), 10);}.
+1 за точку, которую я не рассматривал. Хотел бы я дать дополнительный +1 для <3 в вашем коде.
Это отличная идея, но не всегда работает в Chrome. См .: jsbin.com/iyopu/edit
Фактически, указанная выше проблема возникает в большинстве браузеров, отличных от Mozilla (Chrome, Safari, Opera, IE).
@Max: да, JavaScript в Firefox преобразует объявление во втором примере в выражение (см .: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/… - «Объявление функции перестает быть таковым, когда оно либо ...»). По-видимому, другие движки JS неправильно улавливают контекст, когда это происходит. Поскольку необходимо выражение, а не объявление, синтаксис из первого примера следует использовать в любом случае.
Дело в том, что этот пост может предполагать, что with({...}) {...} является универсальной заменой анонимного шаблона закрытия, используемого для скрытия локальных варов. Однако анонимное закрытие правильно захватывает контекст во всех браузерах, в отличие от with.
Эээ, @Max? Анонимное закрытие полностью скрывает определение. Какой бы метод вы ни использовали (with или анонимная функция), вам нужно будет убедиться, что результат вашей функции выражение доступен для внешней области ... при условии, что это то, что вы действительно хотели.
Верно, но с with объявление функции не работает, даже если оно вызывается внутри области: jsbin.com/iyopu/5/edit
@Max: см. Последний абзац в этом ответе: with и let охватывают только те символы, которые определены в их списке аргументов; JS не имеет области видимости блока, кроме той, которая предоставляется объявлениями функций. Chrome и т. д. Не соблюдают область видимости, введенную with, при преобразовании объявления функции в выражение функции; после появления этой ошибки не имеет значения, откуда функция называется.
В моем последнем примере «x» находится в списке аргументов как закрытия выражения функции, так и оператора with. Но в одном область соблюдается и должным образом вложена, а в другом - нет. Я так понимаю Почему проблема возникает. Я просто говорю, что следует пояснить, что with не вводит надлежащую область видимости и не может полностью заменить шаблон анонимного закрытия.
@Max: И снова это ошибка в Chrome, которая возникает, когда вы полагаетесь на неявное преобразование объявления в выражение (явные выражения действительно работают). При этом, конечно, with не заменяет закрытие функций. Средство, с помощью которого он вводит область видимости, является более ограниченным значительно, и, конечно, он не предлагает никаких других функций функций. С другой стороны, with не маскирует существующий объект контекста (this) и предлагает более простой синтаксис. Так что для простых задач, таких как те, которые я описал выше, это может быть полезно; а вот как замену функции вряд ли!
@Max вы можете заставить это работать, используя выражение функции вместо jsbin.com/iyopu/7/edit
var toString = function () { return "Hello"; }; with ({"test":1}) { console.info(toString()); };. In the scope of the с statement, нанизывать() is an inherited property of Объект, so the explicitly defined function isn't called. Still a great answer, though :-)
вам даже не нужно использовать закрытие. Вы можете передать аргумент функции setTimeout: `for (var i = 0; i <3; ++ i) setTimeout (function (num) {alert (num);}, 10, i);`
@goin: IE не позволяет передавать аргументы таким образом. IE позволяет вам указать язык сценариев в третьем аргументе для setInterval и setTimeout, любые дополнительные аргументы после этого игнорируются.
JavaScript 1.7 и ключевое слово let поддерживаются не всеми текущими браузерами, включая Chrome. см. Диаграмма
если внутри вашего оператора with есть строка a=5, чем она закончится? Анонимный объект? Глобальная переменная? Зависит от того, есть ли в функции var a ? Вы можете по-настоящему фантазировать, но, в конце концов, проблемы с записью заставляют меня никогда не трогать его.
Это потрясающе, никогда не думал об использовании с таким, но это имеет много смысла и невероятно полезно!
блокировка области видимости появляется в JavaScript Harmony.
Не собираюсь задерживать дыхание, но ... Будет хорошо, если это произойдет.
fyi я считаю, что 'let', использовавшаяся для создания собственного закрывающего блока, теперь устарела
Да, за 7 лет многое изменилось, @Ben - я добавил несколько примечаний, чтобы, надеюсь, уменьшить путаницу, которую это может вызвать. Удивительно, насколько все это недоступно для производственного использования даже спустя много лет.
Это заставляет меня хотеть, чтобы они сохранили оператор with, потому что, несмотря на его плохую производительность, единственная альтернатива в подобных ситуациях - это исключение строки вашего кода, которая имеет еще худшую производительность.
Да, да и да. Есть вполне законное использование. Смотреть:
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";
так далее...
+1 поскольку я также думаю, что есть много законных применений with. Однако в этом конкретном случае вы можете просто сделать: element.style.cssText = "background: black ; color: blue ; border: 1px solid green"
Вы можете добиться того же в одной строке, используя простой метод extend из jQuery или Underscore.js: $.extend(element.style, {fontWeight: 'bold', fontSize: '1.5em', color: '#55d', marginLeft: '2px'}).
@TrevorBurnham - Если вы собираетесь предположить, что jQuery доступен, вы должны просто использовать его метод .css() ...
Что именно мешает этим переменным перейти в глобальную область видимости? Просто потому, что все стили CSS всегда определены для всех элементов, или что?
@Mark да, они всегда определены с нулями или пустыми строками в качестве значений, если для свойства нет настраиваемого стиля
Обоснуйте, пожалуйста, утверждение, что последний пример работает медленнее.
@GetFree Но если это не новый элемент, вы не можете просто установить cssText, обязательно.
Я использовал оператор 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')
)
)
}
Для этого варианта использования я не выполняю никаких назначений, поэтому у меня нет проблемы неоднозначности, связанной с этим.
Вот как я видел это в VB. (И единственное использование, о котором я знал.)
Обратной стороной этого может быть то, что если вы ссылаетесь на переменную в блоке with, который находится за пределами объекта разметки, движок js в любом случае сначала будет искать ее внутри построителя разметки, снижая производительность.
Это действительно помогает сократить код для тех, кто работает с путями холста.
У меня был пример использования, очень похожий на этот. Мне понадобилось много математических операций. Это: y = Math.root(Math.pow(Match.cos(Math.PI), Math.sin(x))) становится: with (Math) { y = root(pow(cos(PI), sin(x))); }(это, конечно, тупая математика, но для иллюстрации)
Версия этого кода "с" буквально проходит через 240 раз на моей машине медленнее, чем его версия "без". Вот почему люди говорят, что ему нет законного применения. Не потому, что в некоторых местах это не может сделать код красивее. См. Тест: jsfiddle.net/sc46eeyn
@McBrainy - это именно тот тип места, где вы не должны использовать код, который работает намного медленнее (см. Комментарий, который я только что сделал над этим). Если вам нужны ярлыки для суперповторяющегося кода, вы можете объявить их. Например, если вы используете context.bezierCurveTo сто раз подряд, вы можете сказать var bc2 = context.bezierCurveTo;, а затем просто использовать bc2(x,x,etc); каждый раз, когда захотите его вызвать. Это довольно быстро и даже менее многословно, в то время как with очень медленный.
@JimboJonny Проблема с курицей и яйцом ... Я думаю, люди не нашли для этого законного применения. Это только в прошлом приводило к путанице. И именно поэтому движки JS, такие как V8, не имеют для этого оптимизаций.
@JustGoscha - На самом деле это внутренняя проблема, связанная с тем, что вещи внутри оператора with могут ссылаться на разные вещи в зависимости от того, какие переменные / свойства существуют вне оператора with, и это очень затрудняет оптимизацию.
Я никогда не использую, не вижу причин и не рекомендую.
Проблема с with заключается в том, что предотвращает многочисленные лексические оптимизации может выполнять реализация ECMAScript. Учитывая рост быстрых двигателей на основе JIT, этот вопрос, вероятно, станет еще более важным в ближайшем будущем.
Может показаться, что with допускает более чистые конструкции (когда, скажем, вводит новую область видимости вместо общей оболочки анонимной функции или заменяет подробный псевдоним), но это действительно не стоит. Помимо снижения производительности, всегда существует опасность присвоения свойству неправильного объекта (когда свойство не найдено для объекта во внедренной области видимости) и, возможно, ошибочного введения глобальных переменных. IIRC, последняя проблема побудила Крокфорда рекомендовать избегать использования with.
Призрак производительности появляется часто, почти так же часто, как и глобальные вещи ... Всегда кажется мне странным, учитывая, что мы говорим о JavaScript. Вы могли бы предположить, что снижение производительности составляет действительно драматический, чтобы заслужить такое внимание, но ... Если у вас есть какие-либо точные цифры стоимости конструкций with(){}, подобных тем, которые приведены в других ответах здесь, в современных браузерах, мне бы хотелось увидеть их!
Почему это странно в контексте Javascript? :) И да, это драматично. Подумайте об этом - реализация должна оценить выражение в круглых скобках, преобразовать его в объект, вставить его в начало текущей цепочки областей видимости, оценить оператор внутри блока, а затем восстановить цепочку областей видимости до нормального состояния. Это много работы. Намного больше, чем простой поиск свойств, который можно превратить в высокооптимизированный низкоуровневый код. Вот очень простой тест, который я только что сделал (дайте мне знать, если обнаружите ошибки), демонстрирующий разницу - gist.github.com/c36ea485926806020024
@kangax: Я вырос в C++, где для многих программистов традиционно зацикливается на небольшой эффективности в своем коде, даже если они на самом деле не оказывают заметного влияния на производительность более крупной процедуры или программы. Мне это кажется странным в контексте JavaScript, где такая большая часть производительности подпрограммы может зависеть от реализации виртуальной машины. Я видел несколько случаев, когда программисты JS избегали, скажем, анонимной функции из-за опасений по поводу стоимости установки, но это, похоже, исключение, а не правило, зарезервированное для очень чувствительных областей кода.
Тем не менее, вы абсолютно правы в отношении стоимости with(){}: установка нового прицела с with стоит очень дорого для каждого браузера, который я тестировал. Вы бы хотели избежать этого в любом коде, вызываемом очень часто. Кроме того, Chrome продемонстрировал поразительный успех для любого кода, выполняемого в рамках with(). Интересно, что IE имел лучшие характеристики производительности для кода в блоках with(): без учета стоимости установки with() обеспечивает самые быстрые средства доступа к участникам в виртуальных машинах IE6 и IE8 (хотя эти виртуальные машины в целом являются самыми медленными). Хороший материал, спасибо ...
FWIW: вот тот же набор тестов с учетом затрат на установку: jsbin.com/imidu/edit Доступ к переменным для with() почти на порядок медленнее в Chrome и более чем в два раза быстрее в IE ...!
@kangax - Не правда ли, ты немного недальновиден? @ Shog9 - К счастью, мы говорим о более чистом и гибком языке, javascript, а не об уродливом «высокопроизводительном» языке C++. Теперь, прежде чем вы попробуете какие-либо возражения, знайте, что я уже некоторое время использую C++, и я не совсем новичок.
@ Shog9: Падение скорости Chrome - я думаю, - связано с тем, что V8 сразу переходит к интерпретации из JIT-компиляции, когда достигает определенного уровня сложности по объему и т. д., Например, когда eval() использует вещи. Или, в данном случае, очевидно, при использовании 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.info( (e-s)+"ms" );
s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
r+= fnScoped(i,i);
}
e= Date.now();
console.info( (e-s)+"ms" );
Таким образом, при использовании вышеупомянутого способа оператор 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]);
}
или оператор with, эквивалентный закрытию
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) };
}
}
Я думаю, что реальный риск заключается в случайном минипулировании переменных, которые не являются частью оператора with, поэтому мне нравится, что литерал объекта передается с помощью, вы можете точно увидеть, что он будет в добавленном контексте в коде.
Использование «with» может сделать ваш код более сухим.
Рассмотрим следующий код:
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, выберут второй.
К сожалению, я не понимал, что кто-то уже опубликовал в основном этот же пример год назад. Помимо проблем с производительностью, "with" создает хороший СУХИЙ код за счет того, что его немного труднее читать. Я думаю, что для совместной работы с другими разработчиками или с большей частью производственного кода рекомендуется избегать ключевого слова «with». Но если вы работаете с программистами экспертного уровня и понимаете, как избежать потенциальной неэффективности, то непременно отправляйтесь в город с «с».
Я создал функцию «слияния», которая устраняет часть этой двусмысленности с помощью оператора 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()});
}
Вы можете использовать with, чтобы представить содержимое объекта как локальные переменные для блока, как это делается с этим небольшой шаблонизатор.
Этот метод также используется в _.шаблон() underscore.js.
Вы должны увидеть проверку формы в javascript в W3schools http://www.w3schools.com/js/js_form_validation.asp, где форма объекта "сканируется", чтобы найти вход с именем 'email'
Но я изменил его, чтобы из ЛЮБОЙ формы все поля проверялись как непустые, независимо от имени или количества полей в форме. Я тестировал только текстовые поля.
Но with () упростил задачу. Вот код:
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;
}
Недавно я обнаружил, что инструкция with невероятно полезна. Этот метод никогда не приходил мне в голову, пока я не начал свой текущий проект - консоль командной строки, написанную на JavaScript. Я пытался имитировать API консоли Firebug / WebKit, где в консоль можно вводить специальные команды, но они не отменяют никакие переменные в глобальной области. Я подумал об этом, когда пытался решить проблему, о которой упоминал в комментариях к Отличный ответ Shog9.
Чтобы добиться этого эффекта, я использовал два оператора with, чтобы «наслоить» область видимости на глобальную:
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);
}
Обновлено: Только что проверил источник 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);" +
"}" +
"}}}}";
но беспокоиться о том, что ecmascript 5 мешает вам сделать это. Есть ли решение ecmascript 5?
@ Адам: Я не уверен в этом. ES5 выдает ошибку для этого только в строгом режиме, поэтому это не проблема, если строгий режим не объявлен глобально. ES Harmony может представлять большую проблему, но ее можно исправить с помощью некоторых новых вещей, таких как прокси.
@AndyE, извините, это не по теме, но доступна ли где-нибудь ваша «консоль командной строки, написанная на JavaScript»?
@ Адам: нет, это не так. Все это было задумано как набор инструментов разработчика для гаджетов рабочего стола Windows, но я так и не закончил его (хотя консоль работает очень хорошо). Я мог бы закончить это в какой-то момент, даже если у WDG в данный момент нет очень светлого будущего.
@AndyE Я пришел с чем-то похожим по той же причине. Теперь область WDG мертва :(
Несколько недель назад мы переместили нашу консольную реализацию в Chrome с with block на некоторую магию символов, потому что with block заблокировал некоторые функции ES6 :)
@AlexeyKozyatinskiy, о какой символической магии вы имели в виду? 5 лет назад... :)
Форк CoffeeScript Коко имеет ключевое слово with, но он просто устанавливает this (также доступный для записи как @ в CoffeeScript / Coco) для целевого объекта внутри блока. Это устраняет двусмысленность и обеспечивает соответствие строгому режиму ES5:
with long.object.reference
@a = 'foo'
bar = @b
Для некоторых фрагментов короткого кода я хотел бы использовать тригонометрические функции, такие как 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;
}
}
Это означает: я использую объект как набор функций, которые я включаю в ограниченной области кода для прямого доступа. Я считаю это полезным.
это не лучшая идея использовать оператор with таким образом, он просто затрудняет чтение кода, потому что вы не знаете, какая функция является глобальной и какая функция вызывается в области with object, поэтому, если какая-либо функция каким-либо образом не определена в область объекта, тогда он попытается получить к нему доступ в глобальном пространстве имен
Осознавая проблему масштабирования, я все еще считаю это полезным. Математик, читающий код, хочет непосредственно увидеть, что приведенная выше формула является применением «закона четырех последовательных частей» в сферической тригонометрии. Строгая альтернатива запутывает формулу: z = Math.atan2( Math.sin(d * Math.PI / 180), Math.cos( pol.lat * Math.PI / 180) * Math.tan( pos.lat * Math.PI / 180 ) - Math.sin( pol.lat * Math.PI / 180 ) * Math.cos( d * Math.PI / 180) ) * 180 / Math.PI; даст тот же результат, но это ужас.
@rplantiko Следует иметь в виду, что большинству людей это не нравится. Так что, если вы не пишете код, который никто никогда не коснется. Кроме того, я почти уверен, что смогу показать вам пару примеров использования with, которые вас озадачили бы.
Использование with не рекомендуется и запрещено в строгом режиме ECMAScript 5. Рекомендуемая альтернатива - назначить объект, свойства которого вы хотите получить, временной переменной.
Вот хорошее применение 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");
Вы можете использовать with, чтобы избежать явного управления арностью при использовании 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;
}
}
}
Как отметил Энди Э в комментариях к ответу Shog9, это потенциально неожиданное поведение возникает при использовании with с литералом объекта:
for (var i = 0; i < 3; i++) {
function toString() {
return 'a';
}
with ({num: i}) {
setTimeout(function() { console.info(num); }, 10);
console.info(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.info(num); }, 10);
console.info(toString()); // prints "a"
}
}
Но это будет работать только в ES5 +. Также не используйте with.
Я работаю над проектом, который позволит пользователям загружать код для изменения поведения частей приложения. В этом сценарии я использовал предложение 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();
Связанный: stackoverflow.com/questions/543533/…
Просто хотел добавить, что вы можете получить функциональность with () с красивым синтаксисом и без двусмысленности с вашим собственным умным методом ...
//utility function
function _with(context){
var ctx=context;
this.set=function(obj){
for(x in obj){
//should add hasOwnProperty(x) here
ctx[x]=obj[x];
}
}
return this.set;
}
//how calling it would look in code...
_with(Hemisphere.Continent.Nation.Language.Dialect.Alphabet)({
a:"letter a",
b:"letter b",
c:"letter c",
d:"letter a",
e:"letter b",
f:"letter c",
// continue through whole alphabet...
});//look how readable I am!!!!
..или если вы В самом деле хотите использовать "with ()" без двусмысленности и без специального метода, заключите его в анонимную функцию и используйте .call
//imagine a deeply nested object
//Hemisphere.Continent.Nation.Language.Dialect.Alphabet
(function(){
with(Hemisphere.Continent.Nation.Language.Dialect.Alphabet){
this.a = "letter a";
this.b = "letter b";
this.c = "letter c";
this.d = "letter a";
this.e = "letter b";
this.f = "letter c";
// continue through whole alphabet...
}
}).call(Hemisphere.Continent.Nation.Language.Dialect.Alphabet)
Однако, как отмечали другие, это несколько бессмысленно, поскольку вы можете ...
//imagine a deeply nested object Hemisphere.Continent.Nation.Language.Dialect.Alphabet
var ltr=Hemisphere.Continent.Nation.Language.Dialect.Alphabet
ltr.a = "letter a";
ltr.b = "letter b";
ltr.c = "letter c";
ltr.d = "letter a";
ltr.e = "letter b";
ltr.f = "letter c";
// continue through whole alphabet...
Мой
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
}}
Можно ли доверять такому некачественному коду? Нет, мы видим, что его сделали абсолютно нечитаемым. Этот пример неоспоримо доказывает, что нет необходимости в with-statement, если я правильно понимаю удобочитаемость;)
использование оператора with с прокси-объектами
Недавно я хочу написать плагин для вавилон, который включает макросы. Я хотел бы иметь отдельное пространство имен переменных, в котором хранятся мои макропеременные, и я могу запускать свои макрокоды в этом пространстве. Кроме того, я хочу обнаруживать новые переменные, которые определены в кодах макросов (потому что это новые макросы).
Сначала я выбираю модуль vm, но я обнаружил, что глобальные переменные в модуле vm, такие как Array, Object и т. д., Отличаются от основной программы, и я не могу реализовать module и require, которые будут полностью совместимы с этими глобальными объектами (потому что я не могу реконструировать основные модули). В конце концов, я нахожу утверждение «с».
const runInContext = function(code, context) {
context.global = context;
const proxyOfContext = new Proxy(context, { has: () => true });
let run = new Function(
"proxyOfContext",
`
with(proxyOfContext){
with(global){
${code}
}
}
`
);
return run(proxyOfContext);
};
Этот прокси-объект перехватывает поиск всех переменных и говорит: «Да, у меня есть эта переменная». и Если у прокси-объекта действительно нет этой переменной, покажите ее значение как undefined.
Таким образом, если какая-либо переменная определена в макросе code с помощью оператора var, я могу найти ее в объекте контекста (например, в модуле vm). Но переменные, определенные с помощью let или const, доступны только в это время и не будут сохранены в объекте контекста (модуль vm сохраняет их, но не раскрывает их).
Спектакль: Производительность этого метода лучше, чем vm.runInContext.
безопасность: если вы хотите запустить код в песочнице, это ни в коем случае не безопасно, и вы должны использовать модуль vm. Он предоставляет только новое пространство имен.
Связанный мой вопрос: Является ли использование оператора with с прокси плохой практикой?
Никогда не использую. Без этого легче жить, если я сделаю вид, что его не существует.