Как стать автором
Обновить

Комментарии 50

определяемый этой библиотекою конструктор называется (очевидно, для краткости) просто «L» — а не «Leaflet», например. Шесть букв экономятся. Но ведь можно, можно было бы сэкономить ещё четыре символа

Зачем, зачем подменять собою функции минификатора?
В данном случае я вёл речь не об экономии того объёма джаваскрипта, который отдаётся во браузер, а об экономии усилий самогó того программиста, которому набирать этот код. Разве оператор «new» в свете вышеизложенных соображений не начинает выглядеть как своего рода boilerplate code?
У создания «инстанса класса» и у вызова функции — разная семантика. Различая эти конструкции на письме, мы сделаем наш код более читабельным. Как по мне, так 4 символа — небольшая плата за ясность.
Если автор джаваскриптовой библиотеки делает свои конструкторы самовызывающимися, то он не принуждает пользователя библиотеки (другого программиста на джаваскрипте) к отказу от «new»: если тот пожелает делать вызов конструктора более заметным при помощи «new», то имеет полное право.

Внедрение самовызывающихся конструкторов, однако же, даёт выбор, то есть обеспечивает возможность отказа от «new», если будет на то воля и желание пользователя библиотеки.

Программист, использующий библиотеку, может даже использовать «new» только перед частью вызовов конструкторов, а перед другою частью конструкторов от «new» воздерживаться — и это поведение также всецело будет поддерживаться библиотекою…
А потом придет другой программист, и будет недоумевать — почему тут new L(…), а там — просто L(…). Мне не нравится эта идея.
Поддерживаю.
Делать нужно либо так либо так. Если речь о библиотечной функции (jQuery, Leaflet), зачем вообще делать из неё конструктор — сделайте фабрику и вызывайте без new.

Более того, утверждение: «здесь и далее мы гарантированно находимся внутри конструктора» ошибочно:
var instance = new Constructor;
Consctructor.call(instance);

Constructor.call(instance) это в общем-то такое извращение, на которое можно пойти только сознательно и с пониманием того, что происходит.
Вы не правы.
Новичок способен пойти на любое извращение, подсмотренное им в чужом коде и показавшееся ему интересным.
Логика у него при этом примерно следующая: «Другие так делают, значит так делать можно».
На этот случай должен быть coding guideline.
Да, и еще надо чтобы они соблюдались. А для этого нужно code review как минимум.
На данный момент поддерживаю k12th.
Необходимо находить баланс в удобности использования библиотеки.
Как по мне, важней правильно именовать методы и в целом заботиться о хорошей структуре библиотеки, чем о «дурачках», которые не понимают что такое ООП.
Да простите меня за столь вольное название новичков. :) Я любя.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Минификатором же, как раз напротив, будут обработаны именно условия «if (!(this instanceof имяКонструктора)) return new имяКонструктора(аргумент1, аргумент2, );» внутри библиотеки.
Попутно прошу также прощения за некорректность, я несколько оговорился: «L» не конструктор, а корневой объект, чьими методами являются конструкторы. Оговорку исправил во блогозаписи.
Это просто для удобства, чтобы не загромождать код одним и тем же постоянно встречающимся словом. Я позаимствовал идею из jQuery с его $: вместо того, чтобы рекомендовать конструкции вроде
(function () { var L = Leaflet; ... }());
в каждом месте, где используется библиотека, я решил сделать наоборот — создавать переменную L безопасно, сохраняя старое значение (если есть), и дать возможность восстановить его, объявив для Leaflet любой другой неймспейс:
var Leaflet = L.noConflict(); // после этого L указывает на старое значение

Или писать безопасно в стиле jQuery Plugins:
(function (L) {
    ...
}(L.noConflict()));
Я понимаю, чем вы руководствовались. Но меня просто отпугивают однобуквенные переменные еще со времен копания в кишках tinyMCE. До определенной степени это вкусовщина, и мне лень расписывать, в чем опасности такого подхода.
Ну если кто-то чувствует опасность, можно воспользоваться описанным в комментарии методом и заменить на что душе угодно. :) Главное, что есть выбор.
В atom.Class есть статический метод invoke. Как-то так:
var MyClass = atom.Class({
	Static: function () {
		invoke: function () {
			// source here
		}
	}
});


Эта функция отвечает за то, что произойдёт при вызове класса как функции без «new». По-умолчанию это создание класса, такое поведение, как описанное в топике, но иногда поведение альтернативное.
Но иногда поведение меняется. Вспомним оригинальный JavaScript и разницу между следующими строчками:
var number =     Number(arg);
var number = new Number(arg);

var string =     String(arg);
var string = new String(arg);


Интересно, что вызов метода без new ознатает приведение, а не создание. Я часто пользуюсь этой же техникой. Например, когда метод может получить объект, похожий на точку. Это может быть массив [x, y], может быть объект с полями {x, y}, а может быть и собственно Point. Если пришёл объект или массив, то из него создаётся точка, а если точка, то она не создаётся, а возвращается существующая. Таким образом имеем код:

var source = new Point(3, 4);
 
var clone  = new Point(source);
var same   =     Point(source);

console.log(
	clone == source, // false
	 same == source  // true
);


Идея, в общем, в том, что вызов с new и вызов без new могут оба использоваться для создания изящного api библиотеки.
Долго думал над заметкой и каментами — и, кажется, этот вариант логичнее.
Все-таки создание объект и выхов функции — разные вещи…
В ключе архитектуры jquery — там самовызов очень кстати и он во многом определяет и её парадигму и её популярность. Но это скорее частный случай…
Хотя, конечно, как-то гложет неуверенность все-равно
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
приводить собственные типы отдельными функциями, названными через lower-case (point(...) или toPoint, неважно)

Я изначально делал так: Point.from. Но в моей реализации были недостатки с контекстом, необходимо было биндить
[ [1,1], [2,2], [3,3] ].map( Point.from );
[ [1,1], [2,2], [3,3] ].map( Point.from.bind(Point) );
[ [1,1], [2,2], [3,3] ].map( Point );


Последнее мне нравится особо =)
пс. это TheShock, с компа Нюточки зашёл, забыл перелогиниться =)
Да какая разница :)
НЛО прилетело и опубликовало эту надпись здесь
Или не получится сделать Extends или надо мутить с контекстами. В invoke ссылка на this всегда одна — текущий вызываемый контекст. Как-то так:
LibCanvas.Geometry = atom.Class({
	Static: {
		from : function (obj) {
			return obj instanceof this ? obj : new this(obj);
		}
	}
});

LibCanvas.Point = atom.Class({
	Extends: LibCanvas.Geometry,
});

[ [0,1], [1,0], [1,1] ].map( LibCanvas.Point.from ); // fail


LibCanvas.Geometry = atom.Class({
	Static: {
		invoke : function (obj) {
			return obj instanceof this ? obj : new this(obj);
		}
	}
});

LibCanvas.Point = atom.Class({
	Extends: LibCanvas.Geometry,
});

[ [0,1], [1,0], [1,1] ].map( LibCanvas.Point ); // success
НЛО прилетело и опубликовало эту надпись здесь
Ну, на самом деле в рамках прототипной модели как раз this должен ссылать на сам класс. Другое дело, что ребёнку оно не унаследуется, если не привязать метод вручную

var Parent = function() {};
Parent.method = function () {
  // this is link to Parent
};

var Child = function() {};
Child.method = Parent.method;


Я понимаю ваше убеждение на счёт toPoint. но учтите, что:
1. он не так прост — там должно быть не просто создание нового инстанса, так что минимум:
function toPoint(obj) {
    return obj instanceof Point ? obj : new Point(obj);
}

2. такой метод должна иметь не только точка, но и прямоугольник, круг, полигон и все остальные фигуры:
function toRectangle(obj) {
    return obj instanceof Rectangle ? obj : new Rectangle(obj);
}
function toCircle(obj) {
    return obj instanceof Circle ? obj : new Circle(obj);
}


Единственный вариант соблюсти столь любимый мной принцип DRY — это создать фабрику функций для приведения к классу.
function factory (Class) {
  return function (obj) {
    return obj instanceof Class ? obj : new Class(obj);
  }
};
var toRectangle = factory(Rectangle);
var toCircle = factory(Circle);


Но вариант с наследованием мне кажется более изящным…
НЛО прилетело и опубликовало эту надпись здесь
пользователь не дурак, он вряд ли будет приводить уже обозначенный тип к самому себе или сам проверит instanceof

мне больше эта функциональность нужна внутри библиотеки, чтобы пользователь мог передавать любые аргументы и они прозрачно подставлялись.
var rect = new Rectangle(0,5,10,15);
var rect = new Rectangle({ from:[0,5], to:[10,15] });
var rect = new Rectangle({ from:{x:0,y:5}, to:{x:10,y:15} });
var rect = new Rectangle({ from:new Point(0, 5), to:new Point(10, 15) });

Хотя я согласен, что реально такая функциональность нужна действительно редко.
Отсутствие arguments.callee — совершенно не проблема. Просто нужно использовать named function expression:

function makeClass(){
return function Constructor(args){
if ( this instanceof Constructor ) {
if ( typeof this.init == "function" )
this.init.apply( this, arguments );
} else
return new Constructor( arguments );
};
}
Эх, не дружу я с формой комментариев. Вот отформатированный код: pastie.org/3051845
Рассказываю: используйте пару псевдоэлементов <source> и </source>, окаймляя ими код на Хабрахабре — и тем невозбранно достигнете желаемого.
Спасибо, а я -то понадеялся на <code></code>, для которого есть кнопка.
Все равно без new есть проблема, если мы создаем новый объект внутри объекта с тем же прототипом. Это, конечно, не проблема, если мы используем чью-то библиотеку, но может привести к проблемам при написании новых библиотек если мы привыкли писать без new везде. Хотя это тоже можно обойти, добавив флажок initializeStarted или что-нибудь в таком духе
Мицгол, а что удивительного в реализации unzip в js?
Пара простых правил, которые искореняют всякие непонятки:
1. JavaScript надо не просто знать, его надо понимать.
2. Долой привычки, надо читать докуметацию/код, раз уж решили использовать чужую работу.
3. Надо думать головой а не одним местом.
Кстати, интересно было почитать про опыт разных разработчиков.
В защиту new надо сказать такие слова.
В программистской среде существуют «мемы» — уловимые глазом сокращения понятий. Мемов для понятия «создать экземпляр класса» существуют 2 основных: оператор new и функция или метод create(). Причём, программисты-идеологи типа Крокфорда более склонны поддерживать второе. Это упрощает работу: если в тексте увидел new или create, то здесь встретится конструктор. Если мы введём допустимость конструктора без этих слов, мы усложним работу для других по чтению своего кода в дальнейшем. Правда же, код будет иезуитским, если j7() — конструктор, а j91() — функция?

По arguments.callee — совсем другая история. Её неперевариваемость лучше всего, хотя и не досконально, объясняют эти комментарии: habrahabr.ru/blogs/javascript/130713/#comment_4334433, stackoverflow.com/questions/103598/why-was-the-arguments-callee-caller-property-deprecated-in-javascript — в 2 словах — нет явного вызова функции как самой себя. Это также усложняет авто-оптимизацию, хотя это другая сторона проблем.

Сокращение корневых объектов — третья история. Здесь совсем не о чем спорить — корневые объекты стараются сделать покороче, потому что их чаще всего в коде приходится писать. $, ext, qx, GA, GP_… (последнее — от Google+). Но это не повод сокращать мемы и вообще сокращать (не в целях обфускации) нечто узнаваемое (document, window, addListener, .next()...).
А потом куча народу пишет на JQuery и не знает, что
$(this) !== $(this);

это два разных обьекта.
На самом деле эта проблема не специфична для JavaScript, она универсальна: стоит ли пытаться предотвращать некоторые типы ошибок и какой ценой это допустимо делать? Философский вопрос без правильного ответа — нужно искать идеальный баланс, а он в разных случаях будет разным.

Мой ответ на этот вопрос звучит так: я разделяю интерфейсы на внешние (публичные) и внутренние.

На внешних должна быть реализована максимально возможная защита «от дурака» (a.k.a. «от хакера» a.k.a. полная валидация входных данных), при этом предпочтительнее не исправлять молча ошибки (создавая объект на лету при вызове без new) а отказываться работать и выдавать ошибку.

На внутренних интерфейсах реализуется только та защита, которую можно сделать не написав ни одной лишней (нужной только для защиты, а не для основной функциональности) строки кода — т.е. то, чего можно достичь архитектурой, стилем написания кода, соглашениями, ….
Мне эта проблема кажется проблемой, стоящей рядом с проблемой нулевого указателя в Си, т.е. делают кучу обёрток, механизмов, усложнений. А ведь это по сути дело стиля написания и аккуратности.
Простое отличие: конструкторы называть с большой буквы, методы с маленькой. Ошибся — сам себе бяка.
Единственный механизм защиты, который должен быть — выброс исключения при вызове конструктора без new.
Конечно есть исключение — внешний интерфейс сложных библиотек. Там может быть оправданным показанный в статье механизм, но я предпочту этому хорошую документацию.
В книге «JavaScript шаблоны» все эти мелочи разжованы.
Иногда мне хочется думать, что Джон Резиг поступил бы дальновидно, кабы вовсе воздержался от arguments.callee, ограничившись только наглядным примером

Аналог можно реализовать и без arguments.callee:

'use strict';
function makeClass() {
  var newClass;
  var factoryIndicator = {};
  return newClass = function(isFactory, args) {
    if (this instanceof newClass) {
      if (typeof this.init == 'function') {
        this.init.apply(
          this, (isFactory == factoryIndicator) ? args : arguments);
      }
    } else {
      return new newClass(factoryIndicator, arguments);
    }
  };
}
Кстати тот самый 'use strict' как раз препятствует засорению глобального пространства имён. Потому, что this внутри «свободных» функций == undefined.
И в строгом режиме ошибка быстро всплывёт.
Спасибо за упоминание Leaflet. :)

Насчёт L вместо Leaflet — как уже прокомментировали выше, тут различие только в названии переменной, в случае же с new меняется семантика кода. Я предпочитаю, чтобы различие между созданием объекта с помощью определённой функции в качестве конструктора и просто вызовом функции было явным, а не скрытым какими-то магическими конструкциями — как по мне, это делает код более читабельным и понятным, особенно людям, привычным к активному применению ООП.
Я уверен, что настоящие ООПшники никогда не используют методы Array.concat и Array.slice, ведь они неявно создают новый объект и не обозначены как конструкторы. И в своих программах никогда не возвращают из функции объекты, созданные в этой функции, а всегда принимают в параметрах объекты, которые нужно модифицировать, чтобы пользователь в явном виде вызывал new и никогда не забывал, где чего и сколько он создал.

Наверное, мне должно быть жаль, что я не настоящий ООПшник и никогда не видел ни одного вживую.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации