Является ли применение частных методов в Javascript хорошей идеей?

У меня была дискуссия с другим разработчиком о том, имеет ли смысл хакерство в частных функциях javascript или нет.

Альтернативы:

  • Конструктор и прототип со всеми функциями в не-API-методах (private) будут просто названы с подчеркиванием _function_name, чтобы разработчики знали, что они могут назвать и что они не могут назвать.
  • Конструктор и прототип функций API и свободные функции как частные функции внутри частного пространства имен, которое скрывает их от остальных пространств имен, кроме этого.

Мы не рассматриваем другие подходы, такие как создание частных функций в конструкторе формы var private_var= function(){}, потому что это вызовет создание всех этих функций каждый раз, когда объект будет создан, и каждый объект будет иметь свой собственный набор.

Причины, которые мы имели для них:

1

  • Javascript не поддерживает частные функции как таковые, на самом деле нет концепции частной/защищенной/общедоступной видимости, так что это в основном хак
  • Использование подчеркивания в именах меток четко определяет границы границ данного класса/прототипа, нет необходимости "принудительно" его использовать, на самом деле языки, такие как Python, не имеют частных методов, а пользователи-питоны никогда не заботятся об этом, кроме использования символов подчеркивания
  • Даже если частный получает принудительное исполнение, какой смысл он делает на языке, где вы можете просто динамически заменить общедоступные методы?
  • Влияет на читаемость, довольно много, частные функции разделяются на другой набор фигурных скобок для их области видимости, они не могут использовать this или их нужно вызывать с помощью function.call(object) или function.apply(object)

2

  • Обеспечивает четкое ограничение путем инкапсуляции личных методов далеко от рук пользователя класса/прототипа.
  • Это более или менее отраслевой стандарт, многие разработчики javascript используют его как

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

Поскольку наши знания Javascript весьма ограничены, мы решили опубликовать это в stackoverflow, чтобы узнать, какой подход лучше и почему.

Ответ 1

Как бы вы взломали частные функции javascript и с какой целью?

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

Контракт подчеркивания помогает при попытке unit test, и вы действительно хотите разбить то, что может выглядеть как гигантский метод извне.

Я чувствую, что в каждом другом случае вы должны попытаться сделать вещи действительно частными. Если вы пытаетесь открыть хороший, чистый API для других, чтобы работать с ними, им не нужно будет видеть, что за кулисами. Зачем выставлять это? Это приводит к общим сведениям о частных и публичных: Почему методы "private" в объектно-ориентированном?

У вас есть несколько вариантов приватизации методов, и некоторые из этих вариантов влияют на производительность.

Соглашение подчеркивания:

function Pizza() {
   this._pepperoni = function () {};
}

или

function Pizza() {
}

Pizza.prototype._pepperoni = function () {};

Обзорное

function Pizza() {
    function pepperoni() {};
}

Пространство имен/Модули

var pizza = pizza || {};
(function() {
    function pepperoni() {};
    function create() {
        pepperoni();
    }
    window.pizza.create = create; // or module.export = pizza or both
}());

Шаблон модуля

(function(){
    function pepperoni() {};
    function Pizza() {
        pepperoni();
    }

    window.Pizza = Pizza;
}());

О воссоздании ваших функций и определении их один раз. Прежде всего, если вы хотите использовать внутренние частные члены и по-прежнему использовать "this", просто создайте новую переменную с именем self и назначьте ей this:

function Pizza() {
    var self = this;
    function pep() {
       self.x = 1;
    }
}

Затем я попытался проверить разницу в производительности между переопределением и записью функций спереди: http://jsperf.com/private-methods Я думаю, что это немного сэкономит вам менее 20% на ops/sec, чтобы ваши функции воссоздавались каждый раз.

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

Ответ 2

  • Это не хак: Я думаю, что это фундаментально для преодоления этой идеи. Когда вы используете основную легитимную функцию языка - замыкания - чтобы достичь цели скрытия деталей ваших объектов, тогда вы не взламываете.

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

    Кстати, я пришел с фона С#, и мне очень трудно было преодолеть этот барьер "взлома", когда я начал с JavaScript, до недавнего времени я рассматривал закрытие, литералы объектов, функции конструктора, функции обратного вызова... Я рассматривал их все хаки. Только когда я преодолел этот ментальный барьер, я начал оценивать JavaScript и писать хороший код, который использует его сильные стороны.
  • Что касается того, как скрыть ваши функции "private", вы можете скрыть их в пространстве имен, как вы предполагали, или инициализировать в качестве переменных с помощью Шаблон модуля или один из его вариантов, который будет моим предпочтение. Вы сказали, что это будет означать, что эти переменные будут воссозданы с каждым новым экземпляром, но это то же самое для частных переменных в классе. Если вы ищете эквивалент статики, вы можете просто сделать MyClass.StaticVariable = "myStaticVariable", и это должно быть так.

  • Какая разница, чтобы скрыть детали на динамическом языке, когда вы можете изменить публичный API? Ну, это возвращает нас к моему первому пункту - глядя на JavaScript с неправильными взглядами на классные языки - с динамической языковой точки зрения, представьте всю гибкость, которую вы получаете из-за этой динамичной природы, я просто смотрел на библиотеку Sinon который обеспечивает способы издеваться над такими вещами, как таймеры (в основном потому, что вы можете захватить публичный api почти всего), представьте, насколько сложно это было бы на нединамическом языке, увидеть все, что нужно для насмешливых фреймворков, контейнеров IoC, генераторов прокси, отражения, вы получаете все это бесплатно на динамическом языке, таком как JavaScript. Поэтому, хотя ваш аргумент правилен технически, когда он помещается в полный контекст, тогда он становится неуместным.

    Опять же, скрывая детали реализации, мы не делаем этого, чтобы имитировать функцию С#/Java/С++, мы делаем это, потому что во-первых, это хороший дизайн и хорошая концепция для повторного использования и обслуживания, но во-вторых - и так же важно в моей view - мы делаем это, потому что это языковой шаблон, и так оно и делается в JavaScript. Не стоит недооценивать это, сегодня вы пишете код, который завтра будет прочитан кем-то другим, поэтому в соответствии с общими шаблонами (дизайна OO, как при скрытии деталей и инкапсуляции, а также языковых шаблонов, как в шаблоне модуля) значительно улучшаются коммуникации, - сам по себе - достаточно, чтобы охватить его, на мой взгляд. Если вы сделаете это "JavaScript-путь", то вы уже хорошо общаетесь с другими членами команды, с экспертом JavaScript, который присоединится к команде в будущем, а также к сообществу.

  • Что касается читаемости, вы делаете читателей своего кода в пользу, следуя шаблонам и общим соглашениям. Если вы пойдете и попытаетесь сделать то же самое на С++, только тогда вы повлияете на читаемость.

  • Символ подчеркивания: соглашения хороши, особенно при работе с командами, и одна из самых больших библиотек jQuery UI использует это соглашение, и оно действительно работает и сделать вещи намного яснее. Так и сделайте это, даже если ваши переменные не будут действительно скрыты (в пользовательском интерфейсе jQuery вы все равно можете получить доступ и изменить переопределенные переменные), ясность и последовательность, которые он приносит на вашу кодовую базу, неоценимы.

Наконец, я продолжаю упоминать "сделай это способом JavaScript", но само это выражение обманчиво. Я думаю, что для проблемы, о которой вы упоминаете, есть JavaScript, но в большем масштабе, особенно на уровне фреймворков и библиотек, вряд ли существует один способ сделать что-то. Но эта часть волнения и красоты JavaScript. Обнимите его.

Ответ 3

Какой подход лучше во многом зависит от того, чего вы пытаетесь достичь.

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

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

Также существует некоторая путаница в javascript на вашей стороне. Вы правы в javascript, но не поддерживаете частные атрибуты. Но javascript также не поддерживает "классы" в качестве языковой конструкции и не поддерживает "методы класса". Все в javascript - это (просто) объект. Конструктор класса - это всего лишь шаблон для создания объекта, а методы - это просто атрибуты объектов, которые имеют тип "функция". Объектами в javascript являются не экземпляры классов, как в традиционных объектно-ориентированных языках.

Итак, как вы правильно заявляете, после создания объекта в javascript, каждый атрибут этого объекта, включая атрибуты функции aka methods, может быть изменен (кроме атрибутов, заключенных в закрытие). Это сила javascript, но в случае защиты объектов от изменения это недостаток, а не что-то, что javascript (пока) предназначен для. Вы не сможете запретить пользователям переписывать свои объекты, но с закрытием вы можете сделать это труднее.

Я надеюсь, что это поможет в вашем решении.

Ответ 4

Да, это плохая идея, основанная на недоразумении. Обычно неправильно понимают, что такие языки, как Java, С# и т.д. Имеют конфиденциальность, которая применяется или не может быть обойдена. Но на самом деле это полностью ложно, а private и другие модификаторы доступа в Java, С# и т.д. вплоть до консультаций и общения, а не для обеспечения соблюдения.

Мартин Фаулер:

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

Это именно то, что делает префикс подчеркивания. И с префиксом подчеркивания, вы не боретесь с моделью объектов языка.

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

Поскольку вы используете переменные, а не свойства объекта, вы не можете

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

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

Ответ 5

Я бы рекомендовал использовать инструмент из следующей версии JavaScript, ES6: WeakMap. Я реализовал это в IE8 +, и я написал об этом и широко использую его.

function createStorage(creator){
  creator = creator || Object.create.bind(null, null, {});
  var map = new WeakMap;
  return function storage(o, v){
    if (1 in arguments) {
      map.set(o, v);
    } else {
      v = map.get(o);
      if (v == null) {
        v = creator(o);
        map.set(o, v);
      }
    }
    return v;
  };
}


var _ = createStorage(function(o){ return new Backing(o) });

function Backing(o){
  this.facade = o;
}
Backing.prototype.doesStuff = function(){
  return 'real value';
}

function Facade(){
  _(this);
}
Facade.prototype.doSomething = function doSomething(){
  return _(this).doesStuff();
}

Дополнительная информация:

Ответ 6

Где я работаю, рекомендуемый шаблон использует шаблон имен и рассматривает пространства имен, как и на любом языке OO.

Вы можете видеть, как в этом ответе

Ответ 7

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

пожалуйста, прочитайте статью Crockford , особенно часть ООП и private члены

надеюсь, что эта помощь

Ответ 8

Я предпочитаю использовать раскрывающий шаблон модуля. Проверьте здесь

Ответ 9

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

В основном это трюк var foo=function, но он не создает новую функцию для каждого экземпляра объекта. Способ сделать это:

// Create a closure that we can share with our constructor
var MyConstructor = (function(){

    // Within this closure you can declare private functions
    // and variables:
    var private_function = function () {};
    var private_var = {};        

    // Declare the actual constructor below:
    return function () {

    }
})()

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

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

В конце концов, оба способа работают. Большинство модулей в Perl CPAN репозитории очень стабильны и работают прямо из коробки даже без реальной конфиденциальности. С другой стороны, я работал над проектами, из-за которых люди вводили ошибки. Я думаю, это сводится к тому, как ваша команда работает вместе. Если принципы кодирования и культура соблюдаются всеми, тогда трюк стиля "Perl" отлично работает. Если, с другой стороны, вы не можете доверять тому, как будущие сопровождающие будут калечить ваш код, а затем более строго соблюдать конфиденциальность. Вероятно, лучше.

Ответ 10

Существует несколько подходов к вашей проблеме, и один из них зависит от нескольких факторов, включая (1) целевую поддержку браузера, (2) цели проекта и (3) личные предпочтения. Я остановлюсь на нескольких подходах, на которых у меня есть мнения в частности.

Символы

Я подчеркиваю это первым, потому что это самое надежное решение, и это будущее JavaScript: оно будет включено в будущую версию стандарта ECMAScript. Сегодня это возможно с помощью прокладки в браузерах, совместимых с ES5. Это означает, что он работает в основном во всех браузерах класса A, выпущенных за последние 2 года. Основная нижняя сторона не поддерживается в IE 8, но поддерживается в IE 9 и 10 (и, конечно, все современные версии FF, Chrome и Safari). Если IE8 по-прежнему имеет значение для вас, это еще не вариант для вас.

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

Вот пример использования символов для частных членов:

var x = { };
var a = new Symbol();
x[a] = 5;
console.log(x[a]); // => 5

Пока у вас есть доступ к объекту a Symbol, вы можете прочитать свойство из объекта x, но если кто-либо, у кого нет доступа к a, не может его прочитать. Это позволяет вам действительно иметь частных членов:

var Person = (function() {

    var firstName = new Symbol(),
        lastName = new Symbol();

    function Person(first, last) {
        this[firstName] = first;
        this[lastName] = last;
    }

    Person.prototype.getFullName = function() {
        return this[firstName] + ' ' + this[lastName];
    };

    return Person;

})();

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

Object.getOwnPropertyNames(john); // => [ ]

Использование НЕ ДОСТУПИТЬ Символ

Как вы уже упоминали, вы всегда можете префикс свойства с символом (например, подчеркивание), чтобы указать, что он не должен быть доступен внешним кодом. Главный недостаток - свойство все еще доступно. Однако есть несколько хороших преимуществ: (1) эффективная память (2) полная возможность использования прототипального наследования (3) проста в использовании (4) легко отлаживается (поскольку свойства отображаются в отладчике) (5) работает во всех браузерах.

Я использовал этот метод широко в прошлом для больших успехов. В качестве альтернативы подчеркиванию, в одной структуре, которую я разработал (joi), я использовал символ #, потому что это затрудняет доступ к свойству (вместо этого вам нужно получить к нему нотацию с квадратной скобкой), что служит нежным напоминанием всем, кто пытается получить к нему доступ, что он, вероятно, должен остаться один:

function Person(first, last) {
    this['#firstName'] = first;
    this['#lastName'] = last;
}

Person.prototype.getFullName = function() {
    return this['#firstName'] + ' ' + this['#lastName'];
};

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

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

Приоритет внутри конструктора

Я знаю, что вы сказали, что решили не использовать эту технику из-за соображений памяти/производительности, но если что-то близкое к истинным частным лицам - это то, что вы хотите, и вы не можете использовать символы, потому что вам нужна устаревшая поддержка браузера, это хороший вариант. Компания, над которой я работаю, использует эту модель для крупномасштабных приложений, а потребляемая память действительно не проблема. Кроме того, я слышал об исследованиях, которые обнаружили способы оптимизации памяти и производительности в этой модели, и я считаю, что современные браузеры (по крайней мере, V8/Chrome, если не другие) начинают реализовывать эти оптимизации. (Эта информация исходит из разговора, который я слышал от Брендана Эйха, извините, я не знаю, о каком разговоре это было с моей головы).

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

Для полноты он выглядит следующим образом:

function Person(first, last) {
    this.getFullName = function() {
        return first + ' ' + last;
    };
}

var john = new Person('John', 'Smith');
john.getFullName(); // => 'John Smith'

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

WeakMaps

В другом ответе benvie (упомянутые WeakMaps) [http://stackoverflow.com/a/12955077/1662998]. Я бы рассмотрел эту альтернативу, если нужна поддержка IE8, и вы хотите, чтобы свойства действительно моделировали частных членов.