Подтвердить что ты не робот

Классы JavaScript

Я понимаю базовые псевдоклассы JavaScript:

function Foo(bar) {
    this._bar = bar;
}

Foo.prototype.getBar = function() {
    return this._bar;
};

var foo = new Foo('bar');
alert(foo.getBar()); // 'bar'
alert(foo._bar); // 'bar'

Я также понимаю шаблон модуля, который может эмулировать инкапсуляцию:

var Foo = (function() {
    var _bar;

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
})();

Foo.setBar('bar');
alert(Foo.getBar()); // 'bar'
alert(Foo._bar); // undefined

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

Я хотел бы знать, есть ли какой-либо шаблон, который позволяет:

  • Наследование
  • Инкапсуляция (поддержка свойств/методов "private")
  • Активация (может иметь несколько экземпляров "класса", каждый со своим собственным состоянием)
4b9b3361

Ответ 1

что об этом:

var Foo = (function() {
    // "private" variables 
    var _bar;

    // constructor
    function Foo() {};

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return _bar;
    };
    Foo.prototype.setBar = function(bar) {
        _bar = bar;
    };

    return Foo;
})();

И теперь у нас есть инстанцирование, инкапсуляция и наследование.
Но все еще есть проблема. Переменная private static, потому что она делится во всех экземплярах Foo. Быстрая демонстрация:

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'b' :(    

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

var Foo = (function() {
    // constructor
    function Foo() {
        this._bar = "some value";
    };

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return this._bar;
    };
    Foo.prototype.setBar = function(bar) {
        this._bar = bar;
    };

    return Foo;
})();

Теперь у нас есть экземпляр, наследование, но мы потеряли инкапсуляцию в пользу условностей:

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'a' :) 
alert(b.getBar()); // alerts 'b' :) 

но частные вары доступны:

delete a._bar;
b._bar = null;
alert(a.getBar()); // alerts undefined :(
alert(b.getBar()); // alerts null :(

Ответ 2

Я думаю, что вы ищете "Выявление шаблона прототипа".

Дэн Уахлин имеет отличное сообщение в блоге: http://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-structuring-javascript-code-revealing-prototype-pattern.aspx

и даже лучший курс Pluralsight по этой и другим связанным структурам JavaScript: http://pluralsight.com/training/courses/TableOfContents?courseName=structuring-javascript&highlight=dan-wahlin_structuring-javascript-module1!dan-wahlin_structuring-javascript-module2!dan-wahlin_structuring-javascript-module5!dan-wahlin_structuring-javascript-module4!dan-wahlin_structuring-javascript-module3#structuring-javascript-module1

Ответ 3

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

Попробуйте это, чтобы просто освежить ваши варианты. http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ Также старый вопрос, который я добавил в закладки: Является объектно-ориентированным JavaScript?

Ответ 4

Закрытие - ваш друг!

Просто добавьте следующую крошечную функцию в пространство имен верхнего уровня, и вы готовы к ООП, в комплекте с

  • инкапсуляция со статическими и экземплярами, частными и общедоступными переменными и методы
  • наследование
  • инъекция уровня класса (например, для служб Singleton)
  • нет ограничений, нет фреймворка, просто старый Javascript

function clazz(_class, _super) {
    var _prototype = Object.create((_super || function() {}).prototype);
    var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop();
    _deps.push(_super);
    _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor;
    _prototype.constructor.prototype = _prototype;
    return _prototype.constructor;
}

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

Теперь вы можете естественным образом объявить свои базовые классы (т.е. расширять {}) в нескольких строках кода, в комплекте со статическими, экземплярами, общедоступными и частными свойствами и методами:

MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions declared here are private static variables and methods
    // properties of 'this' declared here are public static variables and methods
    return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) {
        // local variables and functions declared here are private instance variables and methods
        // properties of 'this' declared here are public instance variables and methods
    };
});

Расширение класса? Тем более естественно:

MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions are private static variables and methods
    // properties of this are public static variables and methods
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        // local variables and functions are private instance variables and methods
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // properties of 'this' are public instance variables and methods
    };
}, MyBaseClass); // extend MyBaseClass

Другими словами, передайте конструктор родительского класса в функцию clazz и добавьте _super.call(this, arg1, ...) в конструктор дочернего класса, который вызывает конструктор родительского класса с необходимыми аргументами. Как и в любой стандартной схеме наследования, вызов родительского конструктора должен быть первым в дочернем конструкторе.

Обратите внимание, что вы можете явно указывать конструктор с помощью this.constructor = function(arg1, ...) {...} или this.constructor = function MyBaseClass(arg1, ...) {...}, если вам нужен простой доступ к конструктору из кода внутри конструктора или даже просто вернуть конструктор с return function MyBaseClass(arg1, ...) {...} как в приведенном выше коде. В зависимости от того, что вам больше всего нравится.

Просто создайте объекты из таких классов, как обычно, из конструктора: myObj = new MyBaseClass();

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

О, и если вы хотите вложить однолистовые зависимости (например, сервисы) в свой класс (т.е. прототип), clazz сделает это для вас на la AngularJS:

DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype
    // the injected _service dependency is available anywhere in this class
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // the injected _service dependency is also available in the constructor
    };
}], MyBaseClass); // extend MyBaseClass

Как видно из приведенного выше кода, для инъекции синглетонов в класс просто поместите закрытие класса в качестве последней записи в массив со всеми его зависимостями. Также добавьте соответствующие параметры к закрытию класса перед параметром _super и в том же порядке, что и в массиве. clazz будет вставлять зависимости из массива в качестве аргументов в закрытие класса. Зависимости затем доступны в любом месте закрытия класса, включая конструктор.

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

Проверьте эту скрипту: http://jsfiddle.net/5uzmyvdq/1/

Обратная связь и предложения наиболее приветствуются!

Ответ 5

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

Кажется, что он решает проблемы с наследованием, инстанцированием и ecapsulation (по крайней мере, из тестов в Google Chrome v.24), хотя, вероятно, стоит затрат на использование памяти.

function ParentClass(instanceProperty) {
  // private
  var _super = Object.create(null),
      privateProperty = "private " + instanceProperty;
  // public
  var api = Object.create(_super);
  api.constructor = this.constructor;
  api.publicMethod = function() {
     console.log( "publicMethod on ParentClass" );
     console.log( privateProperty );
  };
  api.publicMethod2 = function() {
     console.log( "publicMethod2 on ParentClass" );
     console.log( privateProperty );
  };
  return api;
}

function SubClass(instanceProperty) {
    // private
    var _super = ParentClass.call( this, instanceProperty ),
        privateProperty = "private sub " + instanceProperty;
    // public
    var api = Object.create(_super);
    api.constructor = this.constructor;
    api.publicMethod = function() {
       _super.publicMethod.call(this); // call method on ParentClass
        console.log( "publicMethod on SubClass" );
        console.log( privateProperty );
    }
    return api;
}

var par1 = new ParentClass(0),
    par2 = new ParentClass(1),
    sub1 = new SubClass(2),
    sub2 = new SubClass(3);

par1.publicMethod();
par2.publicMethod();
sub1.publicMethod();
sub2.publicMethod();
par1.publicMethod2();
par2.publicMethod2();
sub1.publicMethod2();
sub2.publicMethod2();

Ответ 6

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

function Class(){
    var name="Luis";
    var lName="Potter";
}

Class.prototype.changeName=function(){
    this.name="BOSS";
    console.log(this.name);
};

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced");
};
test.changeName();
test.changeName();

выведет:

ugly
BOSS
replaced 
replaced 

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

function Class(){
    var name="Luis";
    var lName="Potter";

    function getName(){
         console.log("called getter"); 
         return name;
    };

    function setName(val){
         console.log("called setter"); 
         name = val
    };

    function getLName(){
         return lName
    };

    function setLName(val){
        lName = val;
    };

    Object.defineProperties(this,{
        name:{
            get:getName, 
            set:setName, 
            enumerable:true, 
            configurable:false
        },
        lastName:{
            get:getLName, 
            set:setLName, 
            enumerable:true, 
            configurable:false
        }
    });
}

Class.prototype.changeName=function(){
    this.name="BOSS";
};   

Object.defineProperty(Class.prototype, "changeName", {
    writable:false, 
    configurable:false
});

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced")
};
test.changeName();
test.changeName();

Выводится:

called getter
Luis
called setter 
called getter 
ugly 
called setter 
called setter 
called setter 

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

Ответ 7

JavaScript-классы вводятся в ECMAScript 6 и являются синтаксическим сахаром над существующим наследованием на основе прототипов JavaScript. Синтаксис класса не представляет для JavaScript новую объектно-ориентированную модель наследования. Классы JavaScript обеспечивают гораздо более простой и понятный синтаксис для создания объектов и обработки наследования.

Вы можете увидеть больше в этой ссылке Сообщество Mozilla

Github

Ответ 8

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

function Foo(){
    var _bar = "foo";

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
};

a = Foo();
b = Foo();

a.setBar("bar");
alert(a.getBar()); // "bar"
alert(b.getBar()); // "foo"