Считается ли "новое" ключевое слово JavaScript вредным?
В другом вопросе пользователь указал, что new
Ключевое слово было опасно для использования и предложил решение для создания объекта, который не использовал new
, Я не верил, что это правда, в основном потому, что я использовал Prototype, Scriptaculous и другие отличные библиотеки JavaScript, и все они использовали new
ключевое слово.
Несмотря на это, вчера я смотрел выступление Дугласа Крокфорда в театре YUI, и он сказал то же самое, что он не использовал new
Ключевое слово больше в его коде ( Крокфорд на JavaScript - Акт III: Функция Ultimate - 50:23 минут).
Это "плохо" использовать new
ключевое слово? Каковы преимущества и недостатки его использования?
13 ответов
Крокфорд много сделал для популяризации хороших методов JavaScript. Его самоуверенная позиция по ключевым элементам языка вызвала много полезных дискуссий. Тем не менее, существует слишком много людей, которые воспринимают каждое провозглашение "плохого" или "вредного" как Евангелие, отказываясь выходить за пределы мнения одного человека. Это может быть немного расстраивающим время от времени.
Использование функций, предоставляемых new
Ключевое слово имеет несколько преимуществ по сравнению с созданием каждого объекта с нуля:
- Наследование прототипа. Несмотря на то, что те, кто привык к ОО-языкам на основе классов, часто воспринимаются со смесью подозрений и насмешек, родная технология наследования JavaScript является простым и удивительно эффективным средством повторного использования кода. И новое ключевое слово является каноническим (и только доступным кроссплатформенным) средством его использования.
- Спектакль. Это побочный эффект #1: если я хочу добавить 10 методов для каждого объекта, который я создаю, я мог бы просто написать функцию создания, которая вручную назначает каждый метод каждому новому объекту... Или я мог бы назначить их для функция создания
prototype
и использоватьnew
уничтожать новые объекты. Это не только быстрее (не требуется кода для каждого метода в прототипе), но и позволяет избежать раздувания каждого объекта с отдельными свойствами для каждого метода. На более медленных машинах (или особенно медленных интерпретаторах JS), когда создается много объектов, это может означать значительную экономию времени и памяти.
И да, new
имеет один существенный недостаток, умело описываемый другими ответами: если вы забудете его использовать, ваш код сломается без предупреждения. К счастью, этот недостаток легко устранить - просто добавьте немного кода в саму функцию:
function foo()
{
// if user accidentally omits the new keyword, this will
// silently correct the problem...
if ( !(this instanceof foo) )
return new foo();
// constructor logic follows...
}
Теперь вы можете иметь преимущества new
без необходимости беспокоиться о проблемах, вызванных случайным неправильным использованием. Вы можете даже добавить утверждение к проверке, если мысль о неработающем коде, молча работающем, беспокоит вас. Или, как some прокомментировали, используйте проверку, чтобы ввести исключение времени выполнения:
if ( !(this instanceof arguments.callee) )
throw new Error("Constructor called as a function");
(Обратите внимание, что этот фрагмент позволяет избежать жесткого кодирования имени функции конструктора, так как в отличие от предыдущего примера он не нуждается в создании экземпляра объекта - следовательно, он может быть скопирован в каждую целевую функцию без изменения.)
Джон Резиг подробно рассказывает об этой технике в своем посте "Простое создание класса", а также о том, как встроить это поведение в ваши "классы" по умолчанию. Определенно стоит прочитать... как и его будущая книга, " Секреты ниндзя JavaScript", которая находит скрытое золото в этой и многих других "вредных" функциях языка JavaScript (глава о with
особенно поучительно для тех из нас, кто вначале отклонил эту клеветническую функцию как трюк).
Я только что прочитал некоторые части его книги Крокфордса "Javascript: The Good Parts". У меня такое ощущение, что он считает все, что когда-либо укусило его, вредным:
О переключателе провалиться:
Я никогда не позволяю переключать дела на следующий случай. Однажды я обнаружил в своем коде ошибку, вызванную непреднамеренным провалом сразу же после энергичной речи о том, почему провал иногда был полезен. (стр. 97, ISBN 978-0-596-51774-8)
О ++ и -
Операторы ++ (увеличение) и - (уменьшение), как известно, вносят свой вклад в плохой код, поощряя чрезмерную хитрость. Они уступают только неисправной архитектуре в обеспечении вирусов и других угроз безопасности. (стр. 122)
О новом:
Если вы забудете включить новый префикс при вызове функции конструктора, это не будет связано с новым объектом. К сожалению, это будет привязано к глобальному объекту, поэтому вместо расширения вашего нового объекта вы будете загромождать глобальные переменные. Это действительно плохо. Нет предупреждения компиляции и предупреждения во время выполнения. (страница 49)
Есть еще, но я надеюсь, что вы получите картину.
Мой ответ на ваш вопрос: нет, это не вредно. но если вы забудете использовать его, когда у вас возникнут проблемы, у вас могут возникнуть проблемы. Если вы развиваетесь в хорошей среде, вы это замечаете.
Обновить
Примерно через год после написания этого ответа была выпущена 5-я редакция ECMAScript с поддержкой строгого режима. В строгом режиме, this
больше не привязан к глобальному объекту, но к undefined
,
Javascript - это динамический язык, и существует множество способов запутаться, когда другой язык остановит вас.
Избегать фундаментальной языковой функции, такой как new
на основании того, что вы можете испортить, это все равно, что снимать новые блестящие ботинки перед тем, как идти по минному полю, на случай, если вы испачкаете свои ботинки.
Я использую соглашение, в котором имена функций начинаются со строчной буквы, а "функции", которые на самом деле являются определениями классов, начинаются с заглавной буквы. Результатом является действительно весьма убедительный визуальный признак того, что "синтаксис" неверен:
var o = MyClass(); // this is clearly wrong.
Вдобавок к этому хорошие привычки именования помогают. В конце концов, все функции делают что-то, и поэтому в его названии должен быть глагол, тогда как классы представляют объекты и являются существительными и прилагательными без глагола.
var o = chair() // Executing chair is daft.
var o = createChair() // makes sense.
Интересно, как раскраска синтаксиса SO интерпретировала код выше.
Я новичок в Javascript, так что, возможно, я не слишком опытен в предоставлении хорошей точки зрения на это. И все же я хочу поделиться своим мнением об этой "новой" вещи.
Я пришел из мира C#, где использование ключевого слова "new" настолько естественно, что мне кажется странным, что это шаблон фабричного дизайна.
Когда я впервые пишу код в Javascript, я не понимаю, что есть "новое" ключевое слово и код, подобный коду в шаблоне YUI, и мне не понадобится много времени, чтобы столкнуться с катастрофой. Я теряю представление о том, что должна делать конкретная строка при просмотре кода, который я написал. Более хаотичным является то, что мой разум не может действительно переходить между границами экземпляров объектов, когда я "пробую" код.
Затем я нашел "новое" ключевое слово, которое для меня это "отдельные" вещи. С новым ключевым словом он создает вещи. Без нового ключевого слова я знаю, что не буду путать его с созданием вещей, если только вызываемая мной функция не даст мне ясного представления об этом.
Например, с var bar=foo();
У меня нет никаких подсказок о том, какой бар может быть... Это возвращаемое значение или это вновь созданный объект? Но с var bar = new foo();
Я точно знаю, что бар - это объект.
Другой случай для нового - то, что я называю Пухом Кодированием. Винни-Пух следует за своим животиком. Я говорю иди с языком, который ты используешь, а не против него.
Скорее всего, разработчики языка оптимизируют язык под те идиомы, которые они пытаются поощрять. Если они вводят новое ключевое слово в язык, они, вероятно, думают, что имеет смысл быть ясным при создании нового экземпляра.
Код, написанный в соответствии с намерениями языка, будет повышаться с каждым выпуском. И код, избегающий ключевых конструкций языка, пострадает со временем.
РЕДАКТИРОВАТЬ: И это выходит далеко за рамки производительности. Я не могу сосчитать, сколько раз я слышал (или говорил) "какого черта они это сделали?" при поиске странно выглядящего кода. Часто оказывается, что в то время, когда код был написан, для этого была какая-то "хорошая" причина. Следование Дао языка - ваша лучшая гарантия того, что ваш код не будет высмеян через несколько лет.
Я написал пост о том, как решить проблему вызова конструктора без ключевого слова new.
Это в основном дидактический, но он показывает, как вы можете создавать конструкторы, которые работают с или без new
и не требует добавления шаблонного кода для тестирования this
в каждом конструкторе.
http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html
Вот суть техники:
/**
* Wraps the passed in constructor so it works with
* or without the new keyword
* @param {Function} realCtor The constructor function.
* Note that this is going to be wrapped
* and should not be used directly
*/
function ctor(realCtor){
// This is going to be the actual constructor
return function wrapperCtor(){
var obj; // object that will be created
if (this instanceof wrapperCtor) {
// Called with new
obj = this;
} else {
// Called without new. Create an empty object of the
// correct type without running that constructor
surrogateCtor.prototype = wrapperCtor.prototype;
obj = new surrogateCtor();
}
// Call the real constructor function
realCtor.apply(obj, arguments);
return obj;
}
function surrogateCtor() {}
}
Вот как это использовать:
// Create our point constructor
Point = ctor(function(x,y){
this.x = x;
this.y = y;
});
// This is good
var pt = new Point(20,30);
// This is OK also
var pt2 = Point(20,30);
Причина, по которой не используется ключевое слово new, проста:
Не используя его вообще, вы избегаете ловушки, которая возникает из-за случайного его пропуска. Шаблон построения, который использует YUI, является примером того, как можно полностью избежать нового ключевого слова "
var foo = function () {
var pub= { };
return pub;
}
var bar = foo();
В качестве альтернативы вы могли бы так это:
function foo() { }
var bar = new foo();
Но при этом вы рискуете, если кто-то забудет использовать ключевое слово new, а оператор this - fubar. AFAIK нет никакого преимущества в этом (кроме того, что вы привыкли к этому).
В конце дня: речь идет о защите. Можете ли вы использовать новое утверждение? Да. Это делает ваш код более опасным? Да.
Если вы когда-либо писали на C++, это похоже на установку указателей на NULL после их удаления.
Я думаю, что "новый" добавляет ясности в код. И ясность стоит всего. Приятно знать, что есть подводные камни, но избегать их, избегая ясности, не кажется мне подходящим.
Случай 1: new
не требуется и его следует избегать
var str = new String('asd'); // type: object
var str = String('asd'); // type: string
var num = new Number(12); // type: object
var num = Number(12); // type: number
Случай 2: new
требуется, в противном случае вы получите ошибку
new Date().getFullYear(); // correct, returns the current year, i.e. 2010
Date().getFullYear(); // invalid, returns an error
Вот краткое изложение двух самых сильных аргументов за и против использования new
оператор:
Аргумент против new
- Функции, предназначенные для создания экземпляров в виде объектов с использованием
new
Оператор может иметь катастрофические последствия, если они неправильно вызываются как обычные функции. Код функции в таком случае будет выполняться в области, в которой вызывается функция, а не в области локального объекта, как предполагалось. Это может привести к тому, что глобальные переменные и свойства будут перезаписаны с катастрофическими последствиями. - Наконец, написание
function Func()
, а затем позвонивFunc.prototype
и добавив вещи к нему, чтобы вы могли позвонитьnew Func()
Некоторым программистам кажется неудобным создавать свой объект, который предпочел бы использовать другой стиль наследования объектов по архитектурным и стилистическим соображениям.
Более подробно об этом аргументе можно узнать из замечательной и краткой книги Дугласа Крокфорда "Javascript: The Good Parts". На самом деле проверить это в любом случае.
Аргумент в пользу new
- С использованием
new
Оператор вместе с прототипом назначения быстро. - Эту вещь о случайном запуске кода функции конструктора в глобальном пространстве имен можно легко предотвратить, если вы всегда включаете немного кода в свои функции конструктора, чтобы проверить, правильно ли они вызываются, и, в случаях, когда они не вызываются, обрабатывая вызов соответствующим образом, как хотелось бы.
См . Пост Джона Резига для простого объяснения этой техники и для более глубокого объяснения модели наследования, которую он защищает.
Я согласен с Pez и некоторые здесь.
Мне кажется очевидным, что "новое" - это самоописательное создание объекта, где шаблон YUI, описанный Грегом Дином, полностью скрыт.
Возможность кто-то может написать var bar = foo;
или же var bar = baz();
где baz не метод создания объекта, кажется гораздо более опасным.
Я думаю, что новое - зло, не потому, что если вы забудете использовать его по ошибке, это может вызвать проблемы, а потому, что оно испортит цепочку наследования, делая язык более сложным для понимания.
JavaScript основан на прототипах объектно-ориентированных. Следовательно, каждый объект ДОЛЖЕН быть создан из другого объекта, например var newObj=Object.create(oldObj)
, Здесь oldObj называется прототипом newObj (следовательно, "на основе прототипа"). Это подразумевает, что если свойство не найдено в newObj, то оно будет найдено в oldObj. Таким образом, по умолчанию newObj будет пустым объектом, но из-за цепочки прототипов он, похоже, имеет все значения oldObj.
С другой стороны, если вы делаете var newObj=new oldObj()
прототипом newObj является oldObj.prototype, который излишне сложен для понимания.
Хитрость заключается в использовании
Object.create=function(proto){
var F = function(){};
F.prototype = proto;
var instance = new F();
return instance;
};
Именно внутри этой функции и только здесь должно использоваться новое. После этого просто используйте метод Object.create(). Способ решает проблему прототипа.
IMNSHO «новый» - это ошибочная концепция JavaScript 2021 года. Он добавляет слова там, где они не нужны. Он делает возвращаемое значение функции / конструктора неявным и заставляет использовать его в функции / конструкторе. Добавление шума в код - всегда плохо.
// With new
function Point(x, y) {
this.x = x
this.y = y
}
let point = new Point(0,0)
Против.
// Without new
function Point(x, y) {
return { x, y }
}
let point = Point(0,0)