Альтернативные методы для расширения 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) и решили эту проблему.

  1. Безопасно ли использовать геттеры и сеттеры?

    Да. Но если вы поддерживаете только IE9+.

  2. Безопасно ли изменять 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();
    };
}
Другие вопросы по тегам