Считается ли «новое» ключевое слово JavaScript вредным? [закрыто]


568

В другом вопросе пользователь указал, что newключевое слово опасно для использования, и предложил решение для создания объекта, которое не использовалось new. Я не верил, что это правда, в основном потому, что я использовал Prototype, Scriptaculous и другие прекрасные библиотеки JavaScript, и все использовали newключевое слово.

Несмотря на это, вчера я смотрел выступление Дугласа Крокфорда в театре YUI, и он сказал то же самое, что он больше не использовал newключевое слово в своем коде ( Крокфорд на JavaScript - Акт III: Функция Предельного - 50:23 минут ).

Это «плохо» использовать newключевое слово? Каковы преимущества и недостатки его использования?


90
Это не «плохо» использовать новое ключевое слово. Но если вы забудете об этом, вы будете вызывать конструктор объекта как обычную функцию. Если ваш конструктор не проверяет контекст выполнения, он не заметит, что «this» указывает на другой объект (обычно глобальный объект) вместо нового экземпляра. Поэтому ваш конструктор будет добавлять свойства и методы к глобальному объекту (окну). Если вы всегда проверяете, что this является экземпляром вашего объекта в объектной функции, у вас никогда не возникнет этой проблемы.
Кристиан Б

5
Я не понимаю этого. С одной стороны, Дуг препятствует использованию new. Но когда вы смотрите на библиотеку YUI. Вы должны использовать newвезде. Такие как var myDataSource = new Y.DataSource.IO({source:"./myScript.php"}); .
aditya_gaur

2
@aditya_gaur Это потому, что если вам нужна некоторая инициализация объекта, вам придется взломать initметод, если вы используете Object.createподход, и вызвать его после. Гораздо проще просто использовать, newкоторый выполняет обе функции, устанавливает цепочку прототипов и вызывает некоторый код инициализатора.
Хуан Мендес

67
Я не думаю, что это должно было быть закрыто. Да, это, вероятно, вдохновит Крокфордского яда против фанатов, но мы говорим о популярном совете, чтобы в основном избегать основной языковой функции здесь. Механизм, который заставляет каждый объект JQuery почти ничего не весить (когда речь идет о памяти), включает использование ключевого слова «new». Предпочитаю, чтобы фабричные методы постоянно вызывали новые, но при этом не уменьшайте свои архитектурные возможности и потенциал производительности, используя только литералы объектов. У них есть свое место, и у конструкторов есть свое место. Это устаревший и паршивый совет.
Эрик Реппен

2
TLDR: Использование newне опасно. Пропускать newопасно, а значит и плохо . Но в ES5 вы можете использовать строгий режим , который защищает вас от этой опасности и многие другие.
jkdev

Ответы:


603

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

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

  1. Наследование прототипа . Несмотря на то, что те, кто привык к ОО-языкам на основе классов, часто воспринимаются со смесью подозрений и насмешек, родная технология наследования JavaScript является простым и удивительно эффективным средством повторного использования кода. И новое ключевое слово является каноническим (и только доступным кроссплатформенным) средством его использования.
  2. Представление. Это побочный эффект # 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не беспокоясь о проблемах, вызванных случайным неправильным использованием. Вы могли бы даже добавить утверждение к проверке, если мысль о неработающем коде, молча работающем, беспокоит вас. Или, как некоторые прокомментировали, используйте проверку, чтобы ввести исключение времени выполнения:

if ( !(this instanceof arguments.callee) ) 
   throw new Error("Constructor called as a function");

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

Джон Резиг подробно описывает эту технику в своем посте «Простое создание класса» , а также о том, как по умолчанию включить это поведение в ваши «классы». Определенно стоит читать ... как его предстоящая книга Секреты JavaScript Ninja , который находит скрытые золота в этом и многих других «вредных» особенности языка JavaScript ( раздел на withспециально просветить для тех из нас , кто первоначально отклонил это очень клеветническая особенность как уловка).


96
if (! (this instanceof arguments.callee)) throw Error ("Конструктор вызывается как функция"); // Более общий, не требующий знания имени конструкторов, заставляет пользователя исправлять код.
некоторые

5
Если вы беспокоитесь о производительности - и на самом деле у вас есть для этого причина - не проверяйте ее вообще. Либо не забывайте использовать new, либо используйте функцию-обертку, чтобы запомнить ее для себя. Я подозреваю, что если вы находитесь в точке, где это имеет значение, вы уже исчерпали другие оптимизации, такие как запоминание, так что ваши вызовы создания будут в любом случае локализованы ...
Shog9

65
Использование arguments.calleeдля проверки того, что вам звонили, newможет быть не очень хорошим, поскольку arguments.calleeнедоступно в строгом режиме. Лучше использовать имя функции.
Шон Макмиллан

69
Если я неправильно опишу идентификатор, мой код сломается. Я не должен использовать идентификаторы? Не использовать, newпотому что вы можете забыть написать это в своем коде, так же смешно, как мой пример.
Томас Эдинг

16
Если вы включите строгий режим, если вы забудете использовать новый, вы получите исключение при попытке использовать это: yuiblog.com/blog/2010/12/14/strict-mode-is-coming-to-town То есть проще, чем добавить экземпляр проверки в каждый конструктор.
Стивенбез

182

Я только что прочитал некоторые части его книги Крокфордса "Javascript: The Good Parts". У меня такое ощущение, что он считает все, что когда-либо укусило его, вредным:

О переключателе провалиться:

Я никогда не позволяю случаям переключения переходить к следующему случаю. Однажды я обнаружил в своем коде ошибку, вызванную непреднамеренным провалом сразу же после энергичной речи о том, почему провал иногда был полезен. (стр. 97, ISBN 978-0-596-51774-8)

О ++ и -

Операторы ++ (увеличение) и - (уменьшение), как известно, вносят свой вклад в плохой код, поощряя чрезмерную хитрость. Они уступают только неисправной архитектуре в обеспечении вирусов и других угроз безопасности. (стр. 122)

О новом:

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

Есть еще, но я надеюсь, что вы получите картину.

Мой ответ на ваш вопрос: нет, это не вредно. но если вы забудете использовать его, когда у вас возникнут проблемы, у вас могут возникнуть проблемы. Если вы развиваетесь в хорошей среде, вы это замечаете.

Обновить

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


3
Я полностью согласен. Решение: всегда документируйте, как пользователи должны создавать экземпляры ваших объектов. Используйте пример, и пользователи, скорее всего, будут вырезать / вставить. В каждом языке есть конструкции / функции, которые могут использоваться неправильно, что приводит к необычному / неожиданному поведению. Это не делает их вредными.
nicerobot

45
Существует соглашение всегда начинать конструкторы с заглавной буквы, а все остальные функции с заглавной буквы.
некоторые

61
Я просто понял , что Крокфорд не считают ПОКА вредна ... Я не знаю , сколько времени я создал инфинитивную петлю , потому что я забыл , чтобы увеличить переменную ...
некоторые

5
То же самое касается ++, -. Они выражают именно то, что я намерен, самым понятным языком. Я люблю их! некоторые переключатели могут быть понятны, но я устал от них. Я использую их, когда они явно более ясны (извините за каламбур, не смог устоять).
PEZ

30
Мой ответ Крокфорду: программирование сложно, пойдем по магазинам. Sheesh!
Джейсон Джексон

91

Javascript - это динамический язык, и существует множество способов запутаться, когда другой язык остановит вас.

Избегать фундаментальной языковой особенности, например, newиз-за того, что вы можете испортить это, все равно что снимать свои блестящие новые туфли перед тем, как идти по минному полю, на случай, если вы можете испачкать свои туфли.

Я использую соглашение, в котором имена функций начинаются со строчной буквы, а «функции», которые на самом деле являются определениями классов, начинаются с заглавной буквы. Результатом является действительно весьма убедительный визуальный признак того, что «синтаксис» неверен:

var o = MyClass();  // this is clearly wrong.

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

var o = chair() // Executing chair is daft.
var o = createChair() // makes sense.

Интересно, как раскраска синтаксиса SO интерпретировала код выше.


8
Да, я просто думал о раскраске синтаксиса.
BobbyShaftoe

4
Однажды я забыл ввести слово «функция». Слово следует избегать любой ценой. В другой раз я не использовал фигурные скобки в многострочном операторе if / then. Так что теперь я не использую фигурные скобки и просто пишу условные выражения в одну строку.
Hal50000

«Это явно неправильно» . Почему? Для моих классов (которые просто делают if (! this instanceof MyClass) return new MyClass()) я фактически предпочитаю newсинтаксис -less. Почему? Потому что функции являются более общими, чем функции конструктора . Отсутствие newупрощает преобразование функции конструктора в обычную функцию ... Или наоборот. Добавление в newделает код более конкретным, чем он должен быть. И, следовательно, менее гибким. Сравните с Java, где рекомендуется принимать Listвместо того, ArrayListчтобы вызывающий мог выбрать реализацию.
Стейн де Витт

41

Я новичок в Javascript, так что, возможно, я не слишком опытен в предоставлении хорошей точки зрения на это. И все же я хочу поделиться своим взглядом на эту «новую» вещь.

Я пришел из мира C #, где использование ключевого слова «new» настолько естественно, что мне кажется странным, что это шаблон фабричного дизайна.

Когда я впервые пишу код в Javascript, я не понимаю, что есть «новое» ключевое слово и код, подобный коду в шаблоне YUI, и мне не понадобится много времени, чтобы столкнуться с катастрофой. Я теряю представление о том, что должна делать конкретная строка при просмотре кода, который я написал. Более хаотичным является то, что мой разум не может действительно переходить между границами экземпляров объектов, когда я «пробую» код.

Затем я нашел «новое» ключевое слово, которое для меня это «отдельные» вещи. С новым ключевым словом он создает вещи. Без нового ключевого слова, я знаю, я не буду путать его с созданием вещей, если функция, которую я вызываю, не дает мне ясного представления об этом.

Например, у var bar=foo();меня нет никаких подсказок о том, какой бар может быть ... Это возвращаемое значение или это вновь созданный объект? Но var bar = new foo();я точно знаю, что бар - это объект.


3
Согласен, и я считаю, что фабричный шаблон должен следовать соглашению об именах, например makeFoo ()
pluckyglen

3
+1 - наличие «нового» дает более четкое изложение намерений.
Белугабоб

4
Этот ответ довольно странный, когда почти все в JS является объектом. Почему вам нужно точно знать, что функция является объектом, когда все функции являются объектами?
Джошуа Рамирес

@JoshuaRamirez Дело не в этом typeof new foo() == "object". Это то, что newвозвращает экземпляр foo, и вы знаете, что можете позвонить foo.bar()и foo.bang(). Однако это можно легко исправить с помощью @turn JsDoc. Не то, чтобы я выступал за процедурный кодекс (избегая слова new)
Хуан Мендес,

1
@JuanMendes Хмм ... Ваше сообщение показало, что вам понравилось новое из-за его явного ключевого слова в вашей кодовой базе. Я могу копать это. Вот почему я использую шаблон модуля. У меня будет функция с именем createFoo или NewFoo или MakeFoo, не имеет значения, если она явная. Внутри этого я объявляю переменные, которые используются как замыкания внутри литерала объекта, который возвращается из функции. Этот литерал объекта в конечном итоге становится вашим объектом, а функция была просто конструктивной функцией.
Джошуа Рамирес

39

Другой случай для нового - это то, что я называю Pooh Coding . Винни-Пух следует за своим животиком. Я говорю иди с языком, который ты используешь, а не против него.

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

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

РЕДАКТИРОВАТЬ: И это выходит далеко за рамки производительности. Я не могу сосчитать раз я слышал (или сказал) : «Почему, черт возьми , они делали что ?» при поиске странно выглядящего кода. Часто оказывается, что в то время, когда код был написан, для этого была какая-то «хорошая» причина. Следование Дао языка - ваша лучшая гарантия того, что ваш код не будет высмеян через несколько лет.


3
+1 для ссылки на кодирование Пуха - теперь мне нужно найти оправдания, чтобы включить это в мои разговоры ...
Shog9

ЛОЛ! У меня есть многолетний опыт работы в этой конкретной области, и я могу заверить вас, что вы не найдете никаких трудностей. Они часто называют меня Пухом на robowiki.net. =)
PEZ

1
Не знаю, будете ли вы когда-нибудь видеть этот комментарий, но эта ссылка на Pooh Coding не работает.
Calvin

Спасибо за внимание. Мы перенесли robowiki.net в новую вики на прошлой неделе, и старый контент в данный момент недоступен. Я потороплюсь с тем, чтобы заставить работать эти старые ссылки.
PEZ

24

Я написал пост о том, как решить проблему вызова конструктора без ключевого слова 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);

6
избегать arguments.calleeэто здорово!
Грегг Линд

2
surrogateConstructorдолжно быть surrogateCtor(или наоборот).
Дэвид Конрад

Я сохранил коллекцию похожих утилит, вдохновленных Crockford, и обнаружил, что я всегда запускал поиск реализаций при запуске нового проекта. И это оборачивает такую ​​фундаментальную часть программирования: создание объектов. Вся эта работа, просто чтобы включить случайный код инстанцирования? Честно говоря, я ценю изобретательность этой обертки, но, похоже, она порождает небрежное кодирование.
Джо Кодер

1
@joecoder Я упоминал, что это было только для дидактических целей, я не поддерживаю этот стиль кодирования паранойи. Однако, если бы я писал библиотеку классов, то да, я бы добавил эту функцию так, чтобы это было прозрачно для вызывающих абонентов
Хуан Мендес

22

Причина, по которой не используется ключевое слово new, проста:

Не используя его вообще, вы избегаете ловушки, которая возникает из-за случайного его пропуска. Шаблон построения, который использует YUI, является примером того, как можно полностью избежать нового ключевого слова "

var foo = function () {
    var pub= { };
    return pub;
}
var bar = foo();

В качестве альтернативы вы могли бы так это:

function foo() { }
var bar = new foo();

Но при этом вы рискуете, если кто-то забудет использовать ключевое слово new , а оператор this - fubar. AFAIK нет никакого преимущества для этого (кроме того, что вы привыкли к этому).

В конце дня: речь идет о защите. Можете ли вы использовать новое утверждение? Да. Это делает ваш код более опасным? Да.

Если вы когда-либо писали на C ++, это похоже на установку указателей на NULL после их удаления.


6
Нет. С помощью «new foo ()» некоторые свойства устанавливаются для возвращаемого объекта, например конструктор.
некоторые

34
Итак, просто чтобы прояснить это: вы не должны использовать «новый», потому что вы можете забыть это? Вы шутите, верно?
Бомба

16
@Bombe: в других языках забывание «нового» приведет к ошибке. В Javascript он просто продолжает грузиться. Вы можете забыть и никогда не осознавать. И просто смотреть на ошибочный код не будет очевидно, что происходит не так.
Кент Фредрик

3
@ Грег: Я не понимаю, как первый метод позволяет использовать цепочки прототипов - объектные литералы великолепны, но отбрасывать из-за страха производительность и другие преимущества, предоставляемые прототипами, немного глупо.
Shog9

5
@Bombe - Вы должны использовать «новый», потому что вы (и любой, кто использует ваш код) никогда не ошибетесь? Вы шутите, верно?
Грег Дин

20

Я думаю, что «новый» добавляет ясности в код. И ясность стоит всего. Приятно знать, что есть подводные камни, но избегать их, избегая ясности, не кажется мне подходящим.


16

Случай 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

5
Следует отметить, что в случае 1 вызов конструктора как функции имеет смысл только в том случае, если вы собираетесь преобразовать тип (и не знаете, есть ли у объекта оболочки метод преобразования, например toString()). Во всех остальных случаях используйте литералы. Не String('asd') , а просто 'asd', и не Number(12) , а просто 12.
PointedEars

1
@PointedEars Единственное исключение из этого правила Array: Array(5).fill(0) очевидно, более читабельно, чем [undefined, undefined, undefined, undefined, undefined].fill(0)и(var arr = []).length = 5; arr.fill(0);
yyny

@YoYoYonnY Мое заявление было сделано в контексте варианта 1 ответа, который относится к использованию конструкторов newwith для типов объектов, соответствующих примитивным типам. Предлагаемый вами литерал не эквивалентен Arrayвызову (try 0 in …), а остальное является синтаксической ошибкой :) В противном случае вы правы, хотя следует также отметить, что это Array(5)может быть неоднозначным, если вы рассматриваете несоответствующие реализации (и поэтому подражал Array.prototype.fill(…) ).
PointedEars

12

Вот краткое изложение двух самых сильных аргументов за и против использования new оператора:

Аргумент против new

  1. Функции, предназначенные для реализации в виде объектов с использованием new оператора, могут иметь катастрофические последствия, если они неправильно вызываются как обычные функции. Код функции в таком случае будет выполняться в области, где вызывается функция, а не в области локального объекта, как предполагалось. Это может привести к тому, что глобальные переменные и свойства будут перезаписаны с катастрофическими последствиями.
  2. Наконец, написание function Func(), а затем вызов Func.prototype и добавление к нему материала, чтобы можно было вызывать new Func()для создания объекта, кажется некрасивым некоторым программистам, которые предпочитают использовать другой стиль наследования объектов по архитектурным и стилистическим причинам.

Более подробно об этом аргументе можно прочитать в замечательной и лаконичной книге Дугласа Крокфорда «Javascript: The Good Parts». На самом деле проверить это в любом случае.

Аргумент в пользу new

  1. Использование newоператора вместе с назначением прототипа быстро.
  2. Эту штуку о случайном запуске кода функции конструктора в глобальном пространстве имен можно легко предотвратить, если вы всегда включаете в свои функции конструктора немного кода, чтобы проверить, правильно ли они вызываются, и, в случаях, когда они не вызываются , обрабатывая вызов соответствующим образом, как требуется.

См . Пост Джона Резига для простого объяснения этой техники и для более глубокого объяснения модели наследования, которую он защищает.


Обновление аргументов против: # 1 может быть смягчено с помощью 'use strict';режима (или метода в вашей ссылке) # 2 Имеет синатический сахар в ES6 , однако поведение такое же, как и раньше.
ninMonkey 11.11.15

9

Я согласен с Pez и некоторые здесь.

Мне кажется очевидным, что «новое» - это самоописательное создание объекта, где шаблон YUI, описанный Грегом Дином, полностью скрыт .

Возможность того, что кто-то может написать var bar = foo;или var bar = baz();где baz не является методом создания объекта, кажется гораздо более опасной.


8

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

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 () . Способ решает проблему прототипа.


7
Честно говоря, я не в восторге от этой техники - она ​​не добавляет ничего нового, и, как показывает ваш ответ, она может даже оказаться опорой. ИМХО, понимание цепочек прототипов и создание экземпляров объектов имеет решающее значение для понимания JavaScript ... Но если вам неудобно использовать их напрямую, тогда использование вспомогательной функции для обработки некоторых деталей - это хорошо, если вы помните, что вы делает. FWIW: (несколько более полезный) вариант этого является частью ECMAScript 5-е изд. std, и уже доступен в некоторых браузерах - так что вы должны быть осторожны, чтобы не переопределить его вслепую!
Shog9

КСТАТИ: Я не уверен, почему вы сделали это CW, но если вы хотите повторно опубликовать его с поправками форматирования, которые я сделал, будьте осторожны, чтобы избежать этого флажка ...
Shog9

2
Излишне сложно понять? var c = new Car()это то же самое, что делатьvar c = Object.create(Car.prototype); Car.call(c)
Хуан Мендес,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.