Классы 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")
- Активация (может иметь несколько экземпляров "класса", каждый со своим собственным состоянием)
Ответы
Ответ 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"