Комментарии 22
Это нехорошо:
Если уж так делать, то можно:
А лучше так:
if (this.constructor.name !== 'Calculator') { throw “No way, buddy!”; }
Если уж так делать, то можно:
function Calculator() {
if (this.constructor !== Calculator) { throw “No way, buddy!”; }
А лучше так:
function Calculator() {
if (! (this instanceof Calculator)) { throw new TypeError(“No way, buddy!”); }
У всех трех способов есть свои уязвимости. Вот смотрите.
Для начала давайте для ясности определимся какая цель была у этого кода.
Я понял его, как желание определить, вызывается ли функция в качестве конструктора. Все три способа так или иначе делают это, но первый полагается на некоторое строковое значение, в то время как второй и третий на ссылку. С моей точки зрения лучше полагаться на объекты выполняемого приложения, доступные по ссылкам, нежели на строки, которые являются данными.
Впрочем, как вы верно заметили, все три способа можно поломать. Я бы вообще отказался от этого кода, потому как если кто-то захочет ломать, то он будет ломатьего полностью.
Кстати, в вашем примере вот эта строка:
Я понял его, как желание определить, вызывается ли функция в качестве конструктора. Все три способа так или иначе делают это, но первый полагается на некоторое строковое значение, в то время как второй и третий на ссылку. С моей точки зрения лучше полагаться на объекты выполняемого приложения, доступные по ссылкам, нежели на строки, которые являются данными.
Впрочем, как вы верно заметили, все три способа можно поломать. Я бы вообще отказался от этого кода, потому как если кто-то захочет ломать, то он будет ломать
Кстати, в вашем примере вот эта строка:
NamedFunction.name = "Good bye";
не сработает, потому как name это readonly-свойство функции.Спасибо, вы правы. Действительно read-only. Но мы подошли к тому, что мой способ и поломать-то нельзя. (фиддл подправил).
Можно создавать сколько угодно функций с заданным именем.
jsfiddle.net/66Xdu/
jsfiddle.net/66Xdu/
Сенсей, вы не находите, что тогда все три способа тождественны, и ни один из них ни хуже, ни лучше?
У instanceof есть преимущества:
- проходится по цепочке прототипов
- работает даже когда
this == null
Сенсей, вы не находите, что тогда все три способа тождественны, и ни один из них ни хуже, ни лучше?
Нет, я не нахожу. Объявление надо врапнуть в анонимную функцию, тогда точно никто не переопределит.
var IsInstance = (function () {
return function IsInstance() {
console.log(1);
if (! (this instanceof IsInstance)) throw "No way!";
}
})();
var A = IsInstance;
function IsInstance() {};
var a = new A;
Способ с именем — дурацкий.
Ништяк. Я срочно побежал везде оборачивать все конструкторы лямбдами. Теперь никто из команды точно не сможет ни случайно пропустить кейворд
new
, ни нацистким способом похачить и без того обернутые в лямбду модули.Основная ваша проблема в том, что вы не соблюдаете DRY. Каждый раз повторять проверку — явный признак быдлокодинга.
Кстати, бросать строку вместо эксепшина — это плохая практика. Никакого стектрейса, просто голая ошибка. Кто так делает вообще?
Кстати, бросать строку вместо эксепшина — это плохая практика. Никакого стектрейса, просто голая ошибка. Кто так делает вообще?
Как мило. Разговор, вижу, пошел по понятиям. Самоутверждатесь, товарищ? — Предлагаю минусануть еще и этот коментарий и успокоиться.
Кстати, есть ещё интересная конструкция, которая позволяет вызывать функцию как конструктор без ключевого слова new (она тоже основана на проверке instanceof).
Быстрым загугливанием нашёл здесь: js-bits.blogspot.ru/2010/08/constructors-without-using-new.html
Может быть и на хабре освещался этот вопрос.
Это своеобразный «иной» подход к той же проблеме: вместо ошибки мы просто позволяем вызывать функцию без слова new с тем же эффектом.
Быстрым загугливанием нашёл здесь: js-bits.blogspot.ru/2010/08/constructors-without-using-new.html
Может быть и на хабре освещался этот вопрос.
Это своеобразный «иной» подход к той же проблеме: вместо ошибки мы просто позволяем вызывать функцию без слова new с тем же эффектом.
Защита нужна не от вторжения, а от невнимательности.
Не могли бы вы объяснить, почему же так нехорошо одно и чем же так прекрасно другое и третье?
Для выборки нескольких элементов приходится в начале строки указывать нестандартный селектор "[]", возвращающий массив, а не jQuery-объект. В обычных селекторах тоже возвращается DOM element. Возможно я не прав, но если уж работать с jQuery, то почему бы не использовать все возможности этой библиотеки?
Чем плодить кучу проверок и exception-ов, лучше переложить большую часть на плечи jQuery, а обработку самих ошибок оставить пользователю. Это даст намного больше гибкости и возможностей.
Например с теми же селекторами: пользователь указывает любые селекторы, которые в скрипте пытаются найтись внутри элемента-контейнера
можно легко использовать
То же самое и с проверками $.isPlainObject — достаточно использовать $.extend, чтобы гарантировать, что в результате всегда будет объект.
Чем плодить кучу проверок и exception-ов, лучше переложить большую часть на плечи jQuery, а обработку самих ошибок оставить пользователю. Это даст намного больше гибкости и возможностей.
Например с теми же селекторами: пользователь указывает любые селекторы, которые в скрипте пытаются найтись внутри элемента-контейнера
jQuery('selector', container)
. Если селектор неправильный, либо такого элемента не существует — вернётся пустой объект jQuery, а не ошибка. И программист, при необходимости, сам проверит есть ли элемент. Тогда вместо кодаСкрытый текст
// Extract jQuery DOM parts from a container using a map of selectors.
priv.extractParts = function (sourceContainer, selectors) {
var domParts = {};
if (!priv.isElement(sourceContainer)) {
throw "POE10: arguments should be 1st — HTML DOM container and 2nd — selectors map";
}
if (!$.isPlainObject(selectors)) {
selectors = {};
}
$.each(selectors, function (name, selector) {
var found,
findMultiple = false;
if (!/^[A-z\.]+$/.test(name)) {
throw "POE12: incorrect selector name `" + name + "`";
}
if ($.isArray(selector) &&
selector.length === 2 &&
$.type(selector[0]) === 'string' &&
$.isFunction(selector[1])) {
domParts[name] = {};
$(sourceContainer).find(selector[0]).each(function () {
var id = selector[1].call(this, this);
if (id) {
if (domParts[name][id]) {
throw "POE13: duplicate identifier `" + id + "` in DOM part namespace `"+ name +"`";
}
domParts[name][id] = this;
}
});
}
else if ($.isPlainObject(selector)) {
domParts[name] = priv.extractParts(sourceContainer, selector);
}
else if (typeof selector === 'string') {
findMultiple = selector.indexOf('[]') === 0;
if (findMultiple) {
selector = selector.replace('[]', '');
}
found = $(sourceContainer).find(selector);
if (found.length === 0) {
throw "POE14: DOM parts weren't found for selector `" + name + "`";
} else if (found.length > 1) {
if (findMultiple) {
found = Array.prototype.slice.call(found);
} else {
throw "POE15: multiple DOM parts found for selector `" + name + "`";
}
} else {
found = found[0];
}
domParts[name] = found;
}
else {
throw "POE16: invalid selector value";
}
});
return domParts;
};
можно легко использовать
Скрытый текст
priv.extractParts = function(container, selectors){
var domParts = {};
$.each(selectors, function(name, selector){
domParts[name] = (typeof name == 'string') ?
$(selector, container) : priv.extractParts(container, selector);
});
return domParts;
};
То же самое и с проверками $.isPlainObject — достаточно использовать $.extend, чтобы гарантировать, что в результате всегда будет объект.
Небольшая переделка Demo
<script type="text/html" id="tmplCalculator">
<table cellspacing="0" cellpadding="0">
<caption><%= caption %></caption>
<tr>
<td colspan="5"><p></p></td>
</tr>
<tr>
<td><input class="calc-button" type="button" value="1"/></td>
<td><input class="calc-button" type="button" value="2"/></td>
<td><input class="calc-button" type="button" value="3"/></td>
<td><input class="calc-button" type="button" value="/"/></td>
<td><input class="calc-func calc-cancel" type="button" title="Cancel" value="C"/></td>
</tr>
<tr>
<td><input class="calc-button" type="button" value="4"/></td>
<td><input class="calc-button" type="button" value="5"/></td>
<td><input class="calc-button" type="button" value="6"/></td>
<td><input class="calc-button" type="button" value="*"/></td>
<td><input class="calc-func calc-undo" type="button" title="Undo" value="←"/></td>
</tr>
<tr>
<td><input class="calc-button" type="button" value="7"/></td>
<td><input class="calc-button" type="button" value="8"/></td>
<td><input class="calc-button" type="button" value="9"/></td>
<td><input class="calc-button" type="button" value="-"/></td>
<td rowspan="2"><input class="calc-func calc-equals" type="button" value="="/></td>
</tr>
<tr>
<td colspan="2"><input class="calc-button calc-0" type="button" value="0"/></td>
<td><input class="calc-button" type="button" value="."/></td>
<td><input class="calc-button" type="button" value="+"/></td>
</tr>
</table>
</script>
;
(function ($) {
function Calculator() {
$.turnToPageObject(this, {
template: $('#tmplCalculator').html(),
containerClass: 'calc',
context: {
caption: "Demo Calculator"
},
selectors: {
"cancel": '.calc-cancel',
"equals": '.calc-equals',
"undo": '.calc-undo',
"led": 'p'
}
});
var history = [],
led = $(this.DOM.led);
$(this.DOM.container).on('click', '.calc-button', function(){
history.push(led.text());
led.append(' ' + $(this).val());
});
$(this.DOM.cancel).click(function(){
history = [];
led.empty();
});
$(this.DOM.equals).click(function(){
history.push(led.text());
try {
led.text((eval(led.text().replace(/ /g, '')) + '').replace(/./g, "$& "));
} catch(e) {
led.text(history.pop());
}
});
$(this.DOM.undo).click(function(){
led.text(history.pop() || '');
});
}
window.Calculator = Calculator;
})(jQuery);
небольшая опечатка:
Скрытый текст
priv.extractParts = function(container, selectors){
var domParts = {};
$.each(selectors, function(name, selector){
domParts[name] = (typeof selector == 'string') ?
$(selector, container) : priv.extractParts(container, selector);
});
return domParts;
};
Я понял вашу идею. Смотрите, extractParts сделана такой для того, чтобы 1) возвращать не jQuery объекты, а DOM-элементы и 2) не допустить невалидные селекторы, чтобы разработчик как-раз не возился с обработкой ошибок. То есть, в текущей реализации результат предсказуем.
Недостаток способа, который вы предлагаете также в том, что в нем нет фичи
Недостаток способа, который вы предлагаете также в том, что в нем нет фичи
selectorName: [ realSelector, getKeyFromElementFunction ]
.1) возвращать не jQuery объекты, а DOM-элементы
а чем плохи jQuery объект? Всё равно используется библиотека jQuery, так зачем обращаться к DOM элементам? К тому же объект jQuery может работать и как массив — в большинстве случаев работа происходит не с единичными объектами, а именно с множеством объектов, полученных по определённым селекторам. Даже в вашем демо калькулятора работу с кнопками удобнее было бы организовать, если бы возвращался jQuery объект.
2) не допустить невалидные селекторы, чтобы разработчик как-раз не возился с обработкой ошибок
хотя я придерживаюсь другого взгляда, но это тоже несложно. достаточно внутри кода сделать проверку:
Скрытый текст
priv.extractParts = function(container, selectors){
var domParts = {};
$.each(selectors, function(name, selector){
if(typeof selector == 'string'){
domParts[name] = $(selector, container);
if(domParts[name].length == 0){
throw 'Incorrect selector "'+selector+'" or element "'+name+'" is not found';
}
} else {
domParts[name] = priv.extractParts(container, selector);
}
});
return domParts;
};
Обычно, все баги фронт-енда выявляются ещё на этапе разработки. На своей практике я редко сталкивался с ошибками в хардкодном хтмл-е и селекторах для него.
нет фичи selectorName: [ realSelector, getKeyFromElementFunction ]
а какой смысл в этой функции?
а чем плохи jQuery объект?Скажу вам, что изначально, когда придумалась
extractParts
, она возвращала именно jQ объекты, но постепенно мы пришли к тому, что у этого есть недостатки.jQ объекты вовсе неплохи, но все же они занимают больше памяти. Да и мы сами можем в любой момент обернуть дом-элемент в jQ (имеем выбор) и выполнить требуемые действия. Когда же функция, в которой это происходит, добегает до конца, то память освобождается. Так экономнее.
Еще, мы пришли к тому, что форма записи
$(calc.DOM.container).show()
лучше, так как явно указывает человеку, вникающему в код, что вот он обернутый в jQuery дом-элемент.а какой смысл в этой функции?Это инструмент для определенного случая. Например, нужно создать обновляемую таблицу-виджет, столбцы которой — года, а строки — значения метрик.
<script type="text/html" id="tmplTableWidget">
<table>
<tr>
<th></th>
<th>2011</th>
<th>2012</th>
<th>2013</th>
</tr>
<tr data-metric="newUsers">
<th>New Users</th>
<td data-year="2011"></td>
<td data-year="2012"></td>
<td data-year="2013"></td>
</tr>
<tr data-metric="uniqueUsers">
<th>Unique Users</th>
<td data-year="2011"></td>
<td data-year="2012"></td>
<td data-year="2013"></td>
</tr>
<!-- и т.д. -->
</table>
</script>
После превращения
$.turnToPageObject(this, {
template: $('#tmplTableWidget').html(),
selectors: {
newUsers: [ 'tr[data-metric=newUsers]>td', function () { return $(this).attr('data-year'); } ],
uniqueUsers: [ 'tr[data-metric=uniqueUsers]>td', function () { return $(this).attr('data-year'); } ]
}
});
this.DOM
будет выглядет вот так:this.DOM.newUsers.2011
this.DOM.newUsers.2012
this.DOM.newUsers.2013
this.DOM.uniqueUsers.2011
this.DOM.uniqueUsers.2012
this.DOM.uniqueUsers.2013
Когда понадобится обновить метрики согласно какому-то вновь прибывшему json, мы все-равно пробежимся по нему циклом, но вместо jQuery-пасты а-ля
$(this).find('tr[data-metric='+metric+']>td[data-year='+year+']).text(value)
запишем $(this.DOM[metric][year]).text(value)
, что отработает куда быстрее.Возможно Ваш способ действительно удобнее, но, имхо, не стоит подобных усилий. Проверять на существование элемента/ключа массива придётся в любом случае, иначе Ваш вариант кода сгенерирует exception. Так что я бы не стал лишний раз усложнять код. Однако моё дело лишь подсказать возможное «другое» решение.
Сорри, ответил не в ту ветку.
Сорри, ответил не в ту ветку.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Объекты страницы: описание одной техники