Альтернативные методы для расширения object.prototype при использовании jQuery
Некоторое время назад я пытался продлить Object.prototype
... Я был удивлен, когда позже увидел ошибки в консоли, которая исходит из файла jQuery. Я пытался выяснить, что не так, и, конечно, я нашел информацию, которая распространяется Object.prototype
это "зло", "вы не должны этого делать, потому что JS - динамический язык, и ваш код не скоро будет работать", и информация, которую теперь добавит jQuery hasOwnProperty
метод их for in
петли.
Поскольку я не хотел покидать jQuery, я отказался от идеи о расширении Object.prototype
,
До сих пор. Мой проект расширяется, и я действительно раздражен, потому что мне приходится много раз повторять некоторые части кода. Ниже немного структуры, которую я использую в своих проектах:
charts.js:
CHARTS = {
_init: function () {
this.monthlyChart();
/*
*
* more propertys goes here
*
*/
return this;
},
monthlyChart: function () {
//create my chart
return {
update: function () {
// update chart
}
};
}()
/*
*
* more propertys goes here
*
*/
}._init;
dashboard.js
NAVBAR = {
_init: function () {
/*
*
* more propertys goes here
*
*/
return this;
},
doSomething: function(){
$(document).ready(function(){
$('.myButton').on('click', function(){
var data = [];
// calling property from charts.js
CHARTS.monthlyChart.update(data);
});
});
}
}._init
Как я уже говорил, проект сейчас действительно большой - это более 40 файлов js, а некоторые из них содержат несколько тысяч строк кода. Это действительно раздражает, что я должен повторить _init
раздел каждый раз, а также я много функций, я должен повторить $(document).ready
&& $(window).load
,
Я пытался найти другое решение для моей проблемы. Я пытался создать класс с init
свойство (больше вы можете найти здесь), но я решил, что это решение заставило меня добавить еще один "ненужный" фрагмент кода к каждому файлу, и доступ к другому свойству объекта файла также усложняет его (возвращая нужные объекты везде и т. д.) Как советовали в комментарии, я начал читать о геттеров и сеттеров в JS.
В конце концов я создал что-то вроде этого:
//Auto initialization
if (typeof $document === 'undefined') {
var $document = $(document),
$window = $(window),
$body = $('body');
}
Object.defineProperty(Object.prototype, '_init', {
get: function () {
// if object has no property named `_init`
if (!this.hasOwnProperty('_init')) {
for (var key in this) {
// checking if name of property does starts from '_' and if it is function
if (this.hasOwnProperty(key) && key[0] === '_' && typeof this[key] === 'function') {
if (key.indexOf('_ready_') > -1) {
//add function to document ready if property name starts from '_ready_'
$document.ready(this[key].bind(this));
} else if (key.indexOf('_load_') > -1) {
//add function to window load if property name starts from '_load_'
$window.load(this[key].bind(this));
} else {
// else execute function now
this[key].bind(this)();
}
}
}
return this;
}
}
});
и мой объект:
var DASHBOARD = {
_runMe: function(){
},
_ready_runMeOnReady: function(){
},
_load_runMeOnLoad: function(){
},
iAmAString: ''
}._init
Похоже, что это решение работает с JQuery. Но безопасно ли это использовать? Я не вижу никаких проблем, которые может вызвать код, и я не вижу никаких дальнейших проблем, которые он может вызвать. Я буду очень рад, если кто-нибудь скажет мне, почему я не должен использовать это решение.
Также я пытаюсь понять, как это работает в деталях. Теоретически я определил свойство для Object.prototype
от defineProperty
без присвоения ему значения. Как-то это не вызывает каких-либо ошибок в jQuery fore in
петля, почему? Означает ли это, что свойство _init
не определено в какой-то момент или вообще, потому что я определен только получателем этого?
Любая помощь будет оценена:)
3 ответа
Не включая дескриптор в Object.defineProperty(obj, prop, descriptor)
По умолчанию все атрибуты логического дескриптора JavaScript имеют значение false. а именно writable
, enumerable
, а также configurable
, Ваша новая собственность скрыта от for in
итераторы, потому что ваш _init
свойство enumerable:false
,
Я не фанат JQuery, поэтому не буду комментировать почему в отношении JQuery
Не существует абсолютного правила добавления свойств к базовому типу JavaScript, и оно будет зависеть от среды, в которой работает ваш код. Добавление к базовому типу добавит его в глобальное пространство имен. Если ваше приложение совместно использует пространство имен со сторонними сценариями, вы можете столкнуться с конфликтами, которые могут привести к сбою кода или стороннего кода.
Если вы единственный код, то конфликты не будут проблемой, но добавление в object.prototype повлечет за собой дополнительные издержки на весь код, который использует объект.
Я настоятельно рекомендую вам пересмотреть необходимость глобального _init. Конечно, вы не используете его каждый раз, когда вам нужен новый объект. Я являюсь поклонником подхода add hock к структурам данных JavaScript и стараюсь держаться подальше от формальных парадигм ООП
Ваш вопрос на самом деле содержит два вопроса.
Похоже, что это решение работает с jQuery. Но безопасно ли это использовать? Я не вижу никаких проблем, которые может вызвать код, и я не вижу никаких дальнейших проблем, которые он может вызвать. Я буду очень рад, если кто-нибудь скажет мне, почему я не должен использовать это решение.
Прежде всего, есть три основные причины, по которым следует избегать модификации встроенных прототипов.
Петли
Слишком много кода, использующего for-in
петля без hasOwnProperty
проверять. В вашем случае это код jQuery, который не выполняет проверку.
Решения
- Не использовать
for-in
петля без.hasOwnProperty
проверять.- Не применяется в этом случае, потому что это сторонний код, и вы не можете его изменить.
for-in
цикл перебирает только перечисляемые ключи.- Вы использовали это решение.
Object.defineProperty
по умолчанию создает не перечисляемые свойства ( спецификация ECMAScript 5.1)- Не поддерживается IE8.
- Вы использовали это решение.
конфликты
Существует риск названия объекта недвижимости. Представьте, что вы используете плагин jQuery, который проверяет наличие ._init
свойство объектов - и это может привести к неуловимым и трудным отладкам ошибок. Имена с префиксом подчеркивания широко используются в современных библиотеках JavaScript для обозначения частных свойств.
Нарушение инкапсуляции (плохой дизайн)
Но у тебя проблема хуже. Определение глобального ._init
свойство предполагает, что каждый объект имеет универсальную логику инициализации. Это нарушает инкапсуляцию, потому что ваши объекты не имеют полного контроля над своим состоянием.
Вы не можете полагаться на присутствие _init
метод из-за этого. Ваши коллеги не могут реализовать свой собственный класс с
Альтернативные проекты
Глобальный инициализатор
Вы можете создать глобальную функцию initialize
и оберните все ваши объекты, которые требуют инициализации в нем.
Разделить вид и логику
Ваши объекты не должны объединять логику и представление в одном объекте (это нарушает принцип единой ответственности), и вы стали жертвой спагетти-кода. Более того - инициализация объекта не должна привязывать его к DOM, некоторые объекты контроллера должны быть прокси между вашей логикой и дисплеем.
Это может быть хорошей идеей, чтобы проверить, как популярные клиентские MVC-фреймворки решили эту проблему (Angular, Ember, Backbone) и решили эту проблему.
Безопасно ли использовать геттеры и сеттеры?
Да. Но если вы поддерживаете только IE9+.
Безопасно ли изменять Object.prototype?
Нет. Создайте другой объект для наследования всех объектов вашего приложения.
Почему расширение базовых объектов JavaScript - это зло?
Потому что каждый объект, созданный на веб-странице, где загружен ваш скрипт, будет наследовать это свойство или метод. Есть много минусов, таких как коллизии и снижение производительности, если вы делаете это таким образом.
Есть много способов сделать это лучше, позвольте мне показать вам тот, который я использую.
// Here we create the base object:
var someBaseObject = {};
someBaseObject.someMethod = function () {
// some code here
}
someBaseObject.someProperty = "something";
// And inherit another object from the someBaseObject
someObject = Object.create(someBaseObject);
someObject.someAnotherMethod = function () {
// some code here
}
Этот подход позволяет нам оставить прототип Object в покое и создать цепочку прототипов, где someObject наследуется от someBaseObject, а someBaseObject наследуется от Object.
Единственное, что я хочу сказать в этом посте: оставьте базовые объекты в покое и постройте свои собственные, чтобы у вас было намного меньше головной боли.
Замечания: Object.create
поддерживается в IE9+. Вот шим для IE8 и ниже Дугласа Крокфорда:
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}