Нет способов иметь объекты на основе класса в javascript?

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

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

Я прочитал много учебников и книгу "Javascript: хорошие части", но мне кажется, что нет способа определить классы, которые уважают 1) инкапсуляцию и 2) эффективное объявление методов участников (I mean: методы-члены, которые определяются один раз и распределяются между каждым экземпляром класса).

Чтобы определить частные переменные, используются замыкания:

function ClassA()
{
    var value = 1;
    this.getValue = function()
    {
        return value;
    }
}

Проблема в том, что каждый экземпляр класса ClassA будет иметь свою собственную копию функции-члена getValue, которая неэффективна.

Для эффективного определения функций-членов используется прототип:

function ClassB()
{
    this.value = 1;
}

ClassB.prototype.getValue = function()
{
    return this.value;
}

Проблема здесь в том, что переменная "значение" члена является общедоступной.

Я не думаю, что эта проблема может быть решена легко, так как переменные "private" должны быть определены ВО ВРЕМЯ создания объекта (чтобы объект мог иметь доступ к контексту его создания, не выставляя значения thoses), тогда как прототип- определение членских функций на основе должно выполняться ПОСЛЕ создания объекта, чтобы прототип имел смысл ( "this.prototype" не существует, я проверил).

Или я что-то не хватает?


EDIT:

Прежде всего, спасибо за ваши интересные ответы.

Я просто хотел добавить небольшую точность к моему первоначальному сообщению:

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

Кажется, что простое объявление частных переменных действительно может быть достигнуто только путем закрытия в javascript, что, по сути, я сосредоточился на подходе, основанном на классе. Если есть способ достичь простого объявления частных переменных с помощью прототипа, который подходит для меня, я не являюсь ожесточенным подходным подходом к классу.

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

И я согласен, мой заголовок/первое предложение вводит в заблуждение относительно вопроса, который я хотел обсудить здесь.

Ответ 1

Тсс, иди сюда! Хочешь услышать секрет?

Классическое наследование - это проверенный и проверенный подход.

Полезно часто выполнять его в JavaScript. Классы - это хорошая концепция иметь и иметь шаблоны для моделирования нашего мира после того, как объекты удивительны.

Классическое наследование - всего лишь шаблон. Совершенно нормально реализовать классическое наследование в JavaScript, если это шаблон, который вам нужен для вашего использования.

Прототипное наследование фокусируется на совместном использовании и потрясающем (dinasaur drumstick потрясающе), но в некоторых случаях вы хотите поделиться схемой данных и не функциональность. То, что проблема прототипического наследования вообще не адресуется.

Итак, вы говорите мне, что занятия не злые, как все говорят мне?

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

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

Итак, как классы работают в JavaScript

Ну, вам действительно нужен только конструктор:

function getVehicle(engine){
    return { engine : engine };
}

var v = getVehicle("V6");
v.engine;//v6

Теперь у нас есть класс транспортного средства. Нам не нужно было определять класс Vehicle явно с использованием специального ключевого слова. Теперь некоторые люди не любят так поступать и привыкают к более классическому пути. Для этого JS обеспечивает (глупый imho) синтаксический сахар, делая:

function Vehicle(engine){
     this.engine = engine;
}
var v = new Vehicle("V6");
v.engine;//v6

Это то же самое, что и пример выше по большей части.

Итак, чего мы все еще не видим?

Наследование и частные члены.

Что делать, если я сказал вам, что базовый подтипирование очень просто в JavaScript?

Понятие ввода текста JavaScript отличается от того, к чему мы привыкли на других языках. Что значит быть подтипом некоторого типа в JS?

var a = {x:5};
var b = {x:3,y:3};

Является ли тип b подтипом типа a? Скажем, если это согласно (сильному) поэтическому подтипированию (LSP):

< < < < Начать техническую часть

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

Кроме того,

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

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

→ → > Завершить техническую часть

Тривиально, также можно видеть, что существует структурный подтипирование.

Как это применимо к нашему примеру Car?

function getCar(typeOfCar){
    var v = getVehicle("CarEngine");
    v.typeOfCar = typeOfCar;
    return v;
}
v = getCar("Honda");
v.typeOfCar;//Honda;
v.engine;//CarEngine

Не слишком сложно, не так ли? Как насчет частных членов?

function getVehicle(engine){
    var secret = "Hello"
    return {
        engine : engine,
        getSecret : function() {
            return secret;
        }
    };
}

См. secret - это переменная замыкания. Это отлично "private", он работает по-другому, чем рядовые в таких языках, как Java, но невозможно получить доступ извне.

Как насчет того, чтобы выполнять функции?

Ах! Это большой вопрос.

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

В JavaScript функции являются первоклассными. Это означает, что вы можете передавать функции вокруг.

function getPerson(name){
    var greeting = "Hello " + name;
    return {
        greet : function() {
            return greeting;
        }
    };
}

var a = getPerson("thomasc");
a.greet(); //Hello thomasc

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

var b = a.greet;
b(); //Hello thomasc

Подождите! Как b знал, что имя человека - это thomasc? Это просто магия закрытий. Довольно удивительный ха?

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

На практике наличие таких копий не является большой проблемой. Функции в javascript - это хорошо, функциональность! Закрытие - это удивительная концепция, как только вы поймете и овладеете ими, вы видите, что это того стоит, и производительность действительно не так значима. JS становится все быстрее, не беспокойтесь о таких проблемах с производительностью.

Если вы считаете, что это сложно, следующее также является очень законным. Общий контракт с другими разработчиками просто говорит: "Если моя переменная начинается с _, не трогайте ее, мы оба соглашаемся с взрослыми". Это будет выглядеть примерно так:

function getPerson(name){
    var greeter = {
        greet : function() {
            return "Hello" +greeter._name;
        }
    };
    greeter._name = name;
    return greeter;
}

Или в классическом стиле

function Person(name){
    this._name = name;
    this.greet = function(){
       return "Hello "+this._name;
    }
}

Или если вы хотите кэшировать функцию в прототипе вместо экземпляров экземпляров:

function Person(name){
    this._name = name;
}
Person.prototype.greet =  function(){
       return "Hello "+this._name;
}

Итак, подведем итог:

  • Вы можете использовать классические шаблоны наследования, они полезны для обмена типами данных

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

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

* Умный читатель может подумать: "А? Разве ты не обманывал меня там с правилом истории? Я имею в виду, что доступ к собственности не инкапсулирован. Суб >

<суб > Я говорю "нет", я не был. Даже если вы явно не инкапсулируете поля как частные, вы можете просто определить свой контракт таким образом, чтобы он не обращался к ним. Часто, как и TheifMaster, предлагается _. Кроме того, я думаю, что правило истории не так много для многих сценариев, поскольку мы не изменяем способ доступа свойств к свойствам родительского объекта. Опять же, это до нас. Суб >

Ответ 2

Я не хочу обескураживать, так как вы, кажется, довольно новый член StackOverflow, однако мне придется немного помахать вам и сказать, что Это действительно плохая идея, чтобы попытаться реализовать классическое наследование в JavaScript.

Примечание.. Когда я говорю, что плохая идея реализовать классическое наследование в JavaScript, я имею в виду, что попытка имитировать фактические классы, интерфейсы, модификаторы доступа и т.д. в JavaScript - плохая идея. Тем не менее классическое наследование в качестве шаблона проектирования в JavaScript полезно, поскольку это просто синтаксический сахар для прототипного наследования (например, максимально минимальные классы). Я использую этот шаблон дизайна в своем коде все время (a la augment).

JavaScript - это прототипный объектно-ориентированный язык программирования. Не классический объектно-ориентированный язык программирования. Конечно, может реализовать классическое наследование поверх JavaScript, но прежде чем вы будете помнить следующее:

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

Ради вас я дам два ответа на этот вопрос. Первый покажет вам, как сделать классическое наследование в JavaScript. Второй (который я рекомендую) научит вас охватывать прототипное наследование.

Классическое наследование в JavaScript

Большинство программистов начинают с попытки реализовать классическое наследование в JavaScript. Даже JavaScript-гуру, например Дуглас Крокфорд, попытался реализовать классическое наследование в JavaScript. Я тоже пытался реализовать классическое наследование в JavaScript.

Сначала я создал библиотеку под названием Clockwork, а затем augment. Однако я бы не рекомендовал вам использовать ни одну из этих библиотек, потому что они противоречат духу JavaScript. По правде говоря, я все еще был любителем JavaScript-программиста, когда писал эти классические библиотеки наследования.

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

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

Тем не менее, важны тренировочные колеса. Если вы хотите, есть некоторые классические библиотеки наследования, которые должны сделать вас более удобным для написания кода в JavaScript. Одна из таких библиотек jTypes. Не забывайте снимать тренировочные колеса, когда вы уверены в своих навыках как программист JavaScript.

Примечание: Лично мне не нравится jTypes один бит.

Прототипное наследование в JavaScript

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

Прежде всего, следующая строка неверна:

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

Это неправильно, потому что:

  • Вам никогда не понадобится создавать объекты из класса в JavaScript.
  • Невозможно создать класс в JavaScript.

Да, возможно симуляция моделировать классическое наследование в JavaScript. Однако вы все еще наследуете свойства объектов, а не классов. Например, классы ECMAScript Harmony - это просто синтаксический сахар для классической схемы прототипного наследования.

В том же контексте приведенный вами пример также неверен:

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

Да, вы можете создать новую строку из существующей, даже если рабочая область пуста в начале. Что вам нужно понять, так это то, что линия на самом деле не нарисована.

var line = {
    x1: 0,
    y1: 0,
    x2: 0,
    y2: 0,
    draw: function () {
        // drawing logic
    },
    create: function (x1, y1, x2, y2) {
        var line = Object.create(this);
        line.x1 = x1;
        line.y1 = y1;
        line.x2 = x2;
        line.y2 = y2;
        return line;
    }
};

Теперь вы можете нарисовать свою вышеприведенную строку, просто набрав line.draw или вы можете создать из нее новую строку:

var line2 = line.create(0, 0, 0, 100);
var line3 = line.create(0, 100, 100, 100);
var line4 = line.create(100, 100, 100, 0);
var line5 = line.create(100, 0, 0, 0);

line2.draw();
line3.draw();
line4.draw();
line5.draw();

Линии line2, line3, line4 и line5 формируют квадрат 100x100 при рисовании.

Заключение

Итак, вы видите, что вам действительно не нужны классы в JavaScript. Объектов достаточно. Инкапсуляция может быть легко достигнута с помощью функций.

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

Это не проблема, потому что:

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

Ответ 3

Aight, здесь моя попытка решить эту конкретную проблему, хотя я думаю, что следующие соглашения это лучший подход, т.е. префикс ваших переменных _. Здесь я просто отслеживаю экземпляры в массиве, их можно удалить с помощью метода _destroy. Я уверен, что это можно улучшить, но, надеюсь, это даст вам несколько идей:

var Class = (function ClassModule() {

  var private = []; // array of objects of private variables

  function Class(value) {
    this._init();
    this._set('value', value);
  }

  Class.prototype = {

    // create new instance
    _init: function() {
      this.instance = private.length;
      private.push({ instance: this.instance });
    },

    // get private variable
    _get: function(prop) {
      return private[this.instance][prop];
    },

    // set private variable
    _set: function(prop, value) {
      return private[this.instance][prop] = value;
    },

    // remove private variables
    _destroy: function() {
      delete private[this.instance];
    },

    getValue: function() {
      return this._get('value');
    }
  };

  return Class;
}());

var a = new Class('foo');
var b = new Class('baz');

console.log(a.getValue()); //=> foo
console.log(b.getValue()); //=> baz

a._destroy();

console.log(b.getValue()); //=> baz

Ответ 4

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

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

Хотя вы можете кэшировать prototype, поэтому вам не нужно повторять ClassB.prototype. все время:

//in node.js you can leave the wrapper function out
var ClassB = (function() {
    var method = ClassB.prototype;

    function ClassB( value ) {
        this._value = value;
    }

    method.getValue = function() {
        return this._value;
    };

    method.setValue = function( value ) {
        this._value = value;
    };

    return ClassB;
})();

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

Кроме того, в этом случае даже для регулярного выражения достаточно проверить что свойства "private" используются правильно. Запустите /([a-zA-Z$_-][a-zA-Z0-9$_-]*)\._.+/g через файл и убедитесь, что первое совпадение всегда this. http://jsfiddle.net/7gumy/

Ответ 5

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

(function( context ) {

    'use strict';

    var SOME_CONSTANT = 'Hello World';

    var SomeObject = function() {};

    SomeObject.prototype.sayHello = function() {
        console.log(SOME_CONSTANT);
    };

    context.SomeObject = SomeObject;

})( this );

var someInstance = new SomeObject();
someInstance.sayHello();

Лучшее, что вы можете сделать, это аннотировать, что свойство нельзя трогать с помощью подчеркивания типа this._value вместо this.value.

Обратите внимание, что частные функции возможны, скрывая их в функции:

(function( context ) {

    'use strict';

    var SomeObject = function() {};

    var getMessage = function() {
        return 'Hello World';
    };

    SomeObject.prototype.sayHello = function() {
        console.log(getMessage());
    };

    context.SomeObject = SomeObject;

})( this );

var someInstance = new SomeObject();
someInstance.sayHello();

Вот пример расширения 2 'Классы' и взаимодействия друг с другом: http://jsfiddle.net/TV3H3/

Ответ 6

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

var Bundle = (function(){
  var local = {}, constructor = function(){
    if ( this instanceof constructor ) {
      local[(this.id = constructor.id++)] = {
        data: 123
      };
    }
  };
  constructor.id = 0;
  constructor.prototype.exampleMethod = function(){
    return local[this.id].data;
  }
  return constructor;
})();

Теперь, если вы создаете new Bundle, значение data заблокировано внутри:

var a = new Bundle(); console.log( a.exampleMethod() ); /// 123

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

Есть также скрытые недостатки к вышеуказанному шаблону, кроме того, что они не читаются, или являются неудобными для доступа к значениям "private". Один факт состоит в том, что каждый экземпляр Bundle сохранит ссылку на объект local. Это может означать "mdash", например, если вы создали тысячи пакетов и удалили все, кроме одного, данные, хранящиеся в local, не будут собирать мусор для всех созданных Связок. Вам нужно будет включить код деконструкции, чтобы исправить это... в основном усложняя ситуацию.

Поэтому я бы рекомендовал отказаться от идеи частных объектов/свойств, в зависимости от того, какой шаблон вы решите использовать... object-based или constructor. Преимущество JavaScript заключается в том, что все эти различные подходы возможны: он не так близок, как четкое разделение, как языки на основе классов, и некоторые из них могут утверждать, что это путает вещи, но мне нравится, что свобода JavaScript дает быстрый и выразительный характер./p >

Ответ 7

в отношении этого утверждения в вашем вопросе:

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

Похоже, вы недооцениваете, что объекты в Javascript могут быть созданы только путем клонирования существующих объектов, что могло бы вернуться к проблеме "хорошо, но как насчет самого первого объекта?", который не может быть сделан путем клонирования существующего потому что нет существующих объектов ".

Однако вы можете создавать объекты с нуля, а синтаксис для них так же прост, как var object = {}

Я имею в виду, что самый простой возможный объект - пустой объект. Более полезным, конечно, будет объект с вещами в нем:

var object = {
  name: "Thing",
  value: 0,
  method: function() {
    return true;
  }
}

и так далее, и теперь вы отправились на гонки!

Ответ 8

Есть более умные люди, чем я отвечаю на этот вопрос, но я хотел бы отметить одну часть, в частности, которую вы только что отредактировали - частную переменную.

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

var line = (function() {
  var length = 0;
  return {
    setLen : function (newLen) {
      length = newLen;
    },
    getLen : function () {
      return length;
    }
  };
}());

Это приведет к тому, что строка будет объектом с методами setLen и getLen, но вы не сможете вручную обращаться к length без использования этих методов.