Правильная конструкция класса в JavaScript - PullRequest
4 голосов
/ 03 апреля 2012

Я изучаю ОО-программирование на Javascript, и кажется, что есть много способов создать класс. Мне удалось перевести большую часть того, что я прочитал, в общую структуру, но я чувствую, что, возможно, упускаю цель создания прототипов, чтобы сэкономить на раздутии. Делает ли включение определения прототипа в этот «класс» бессмысленным (то есть не уменьшает ли размер объекта)? Есть ли недостатки в этой общей структуре?

Спасибо

var Car = (function () {

    //Private fields
    var _make;
    var _model;

    //Constants (protected by encapsulation)    
    var NUM_OF_WHEELS = 4;

    //Private methods
    function getDesc() {
        return _make + " " + _model;
    }

    //Public constructor
    function thisCar(make, model, color) {
        _make = make;
        _model = model;

        //public properties
        thisCar.prototype.color = color;
    }

    //static properties
    thisCar.carsInTheWorld = 50;

    //static methods
    thisCar.getNumberOfWheels = function () {
        return NUM_OF_WHEELS * thisCar.carsInTheWorld;
    };

    //public properties
    thisCar.prototype.color = "";

    //public method
    thisCar.prototype.startEngine = function () {
        console.log(getDesc() + " engine started");
    };

    return thisCar;
})();

EDIT После прочтения комментариев и ссылок, приведенных здесь, я понимаю следующее.

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

var Car = function (make, model, color) {//Public constructor
    //Private fields
    var _make = make;
    var _model = model;

    //Public fields 
    this.color = color; 

    //Private methods
    function getDesc() {
        return _make + " " + _model;
    }

    //Privileged functions (requiring private members) 
    this.startEngine = function () {
        console.log(getDesc() + " engine started");
    };
};

//public method
Car.prototype.sprayPaint = function (newColor) {
    var oldColor = this.color;
    this.color = newColor;
    console.log("Your car has changed from " + oldColor + " to " + newColor);
};

//Public fields  (if not declared in constuctor)
Car.prototype.fuel = 0; 

//static properties
Car.carsInTheWorld = 0;

//static methods
Car.getNumberOfWheels = function () {               
    var NUM_OF_WHEELS = 4;
    return NUM_OF_WHEELS * Car.carsInTheWorld;
};

Спасибо за все ваши ответы, они действительно помогли.

Ответы [ 4 ]

3 голосов
/ 03 апреля 2012

Вместо использования прототипа, обычным шаблоном является возвращение объекта из конструктора, который содержит ваши общедоступные методы доступа / мутаторы:

jsfiddle

function Car() {
  var myPrivateVar = "foo";
  var myPublicVar = "bar";

  function myPrivateFunc() {console.log(myPrivateVar);};
  myPrivateFunc();      

  return {
    getMyPublicVar: function() {
      return myPublicVar;
    }
  }
}
var honda = Car();
console.log(honda.getMyPublicVar());
honda.myPrivateFunc();    // fail

Используйте прототип вне определения класса, чтобы добавить к нему свойства. Вы должны использовать Car.prototype.myFunc = function() {}; после того, как Car уже определен, чтобы добавить myFunc к Car объектам, созданным в будущем. Эти добавленные участники являются публичными.

Вот вопрос, который должен вам помочь

Вот стандартная статья Крокфорда о наследовании таким образом.

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

2 голосов
/ 03 апреля 2012

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

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

Главным образом потому, что этот подход имеет тенденцию вонять.Он негибкий, трудный для чтения и не очень подходит для проблем, с которыми JS справляется в первую очередь.(на самом деле такие схемы перестарались, и на любом языке их рекомендуют использовать против).Я не говорю, что наследование плохое, просто то, что святой Грааль JavaScript не в том, чтобы писать плохую Java, и под этим я подразумеваю 20-ти уровневые наборы каскадного наследования, которые полностью не расшифровываются и не могут быть изменены любым, кто не потратил пару дней на просмотрих недавно.

Не беспокойтесь об инкапсуляции.Нет, правда.Когда вы в последний раз слышали о внешнем интерфейсе или разработчике пользовательского интерфейса, переписывающем ключевые элементы jQuery на сложном сайте электронной коммерции, или об непосредственном изменении методов объекта JQ и других свойств, чтобы было полезно что-то, над чем они работали, без заботы обо всехдругие вещи, которые могут сломаться?«Доверьтесь пользователю», как говорят дети Python.Частные экземпляры могут помочь выяснить, что является интерфейсом, а что нет.Помимо этой утилиты, выбросьте ее из головы и в Интернете, где каждый всегда сможет увидеть наш код.

И узнать все, что можно, о функциях в JS.Там много чего происходит, что может быть полезно в JS OOP, где основное внимание уделяется экземплярам, ​​а не конструкторам.Функции, контекст и методы call / apply являются ключом к созданию структур, которые работают для вас.

2 голосов
/ 03 апреля 2012

Вы перепутали замыкание и возражающие вещи конструктора. Обычно вы начинаете без закрытия. В JS вы определяете свой «класс» с помощью функции конструктора:

var Car = function Car(model) { //Public constructor

    // Private fields of one instance
    // model is an argument
    var make;

    // Private methods of one instance
    function getDesc() {
        return make + " " + model;
    }

    // Public fields of one instance
    this.color = "#555";

    // Public methods of one instance, privileged the access private fields and functions
    this.setMake = function(m) {
        make = m;
    };
    this.desc = function() {
        return getDesc();
    };
} // end of constructor

/* now the prototype things: */   

// public "default" fields, may get overwritten per instance
Car.prototype.color = "";

// public methods, can only access other public things (not "privileged")
thisCar.prototype.startEngine = function () {
    console.log(this.desc() + " engine started");
};

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

var carsInTheWorld = 50;
// or
Car.livingInstances = 50;
// or something

Car.NUM_OF_WHEELS = 4; // constants are uppercase

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

0 голосов
/ 03 апреля 2012

Большая часть этого в порядке.Этот бит неверен:

function thisCar(make, model, color) {
    // [ ... ]
    //public properties
    thisCar.prototype.color = color;
}

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

Вместо этого сделайте следующее:

    this.color = color;

И чтобы ответить на общий вопрос, использовать прототип таким способом не бессмысленно,Внешняя функция будет запускаться только один раз, сразу после разбора скрипта.Внутреннему конструктору thisCar будет присвоен Car.Свойства прототипа правильно размещены, за исключением отмеченного выше.

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

РЕДАКТИРОВАТЬ:

Здесь больше неправильного, чем я видел вначале.Это также неправильно

var _make;
var _model;

// [ ... ]
function thisCar(make, model, color) {
    _make = make;
    _model = model;
}

Это не свойства экземпляра.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...