Популярные шаблоны наследования JavaScript JavaScript
Я работаю над ebook на GitHub на TDD JavaScript, и мне интересно, не хватает ли мне каких-либо популярных шаблонов наследования. Если вы знаете какие-либо дополнительные шаблоны, я бы с удовольствием их видел. Они должны иметь следующее:
- Проверено время - используется в реальных приложениях
- Необходимо указать исходный код. Должна быть как можно более прямой и педантичной.
- Конечно, правильно и работаем.
Причина, по которой я делаю это, заключается в том, что кажется, что наследование объектов в JavaScript было довольно сложно для многих из нас. моя глава по наследованию JavaScript в основном является учебным пособием: Crockford Good Parts и Zakas Professional JavaScript для веб-разработчиков.
Вот шаблоны, которые у меня есть до сих пор:
// Pseudoclassical Inheritance
function Animal(name) {
this.name = name;
this.arr = [1,2,3];
};
Animal.prototype = {
constructor: Animal,
whoAmI: function() { return "I am " + this.name + "!\n"; }
};
function Dog(name, breed) {
this.name = name;
this.breed = breed;
};
Dog.prototype = new Animal();
Dog.prototype.getBreed = function() {
return this.breed;
};
Dog.prototype.bark = function() {
return 'ruff ruff';
};
// Combination Inheritance
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
};
Parent.prototype = {
constructor: Parent,
toString: function() { return "My name is " + this.name; }
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
};
Child.prototype = new Parent();
Child.prototype.getAge = function() {
return this.age;
};
// Prototypal Inheritance
var helper = { // Thanks to Bob Vince for reminding me NOT to clobber Object!
inherit: function(p) {
NewObj = function(){};
NewObj.prototype = p;
return new NewObj();
},
inheritPrototype: function(subType, superType) {
var prototype = helper.inherit(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
};
function SubType(name, age) {
Parent.call(this, name);
this.age = age;
};
//Child.prototype = new Parent(); // Gets replaced by:
helper.inheritPrototype(SubType, Parent);
SubType.prototype.getAge = function() {
return this.age;
};
// Functional - Durable Pattern
function super_func(blueprint) {
var obj = {};
obj.getName = function() { return blueprint.name; };
obj.getAge = function() { return blueprint.age; };
obj.getFoo = function() { return blueprint.foo; };
obj.getBar = function() { return blueprint.bar; };
return obj;
};
function sub_func(blueprint) {
blueprint.name = blueprint.name || "Crockford Place";
supr = super_func(blueprint);
supr.coolAugment = function() { return "I give a fresh new perspective on things!" };
return supr;
};
И для тех, кого это интересует, вот тесты jspec (извините, но Markdown или все, что они используют, немного изменяет формат):
describe 'JavaScript Inheritance Tests'
before_each
animal = new Animal("Onyx")
dog = new Dog("Sebastian", "Lab")
person = { password : 'secret', toString : function(){ return '<Person>' } }
stub(person, 'toString').and_return('Original toString method!')
end
describe 'Pseudoclassical Inheritance Creation'
it 'should create parent and child object using pseudoclassical inheritance'
animal.constructor.should.eql Animal
// dog.constructor.should.eql Dog // Nope: expected Animal to eql Dog
dog.constructor.should.eql Animal
animal.should.be_a Animal
dog.should.be_a Animal
// dog.should.be_a Dog // Nope! We severed the original prototype pointer and now point to Animal!
dog.should.be_an_instance_of Animal
dog.should.be_an_instance_of Dog
(animal instanceof Dog).should.be_false
end
it 'should behave such that child inherits methods and instance variables defined in parent'
animal.whoAmI().should.match /I am Onyx.*/
dog.whoAmI().should.match /Sebastian.*/
animal.should.respond_to 'whoAmI'
dog.should.respond_to 'whoAmI'
dog.should.have_prop 'name'
end
it 'should behave such that methods and instance variables added to child are NOT available to parent'
dog.bark().should.match /Ruff Ruff/i
dog.should.have_property 'breed'
dog.should.respond_to 'bark'
// animal.should.have_prop 'breed' // Of course not!
// animal.should.respond_to 'bark' // Of course not!
end
it 'should behave such that reference variables on the parent are "staticy" to all child instances'
dog.arr.should.eql([1,2,3])
dog.arr.push(4)
dog.arr.should.eql([1,2,3,4])
spike = new Dog("Spike", "Pitbull")
spike.arr.should.eql([1,2,3,4])
spike.arr.push(5)
rover = new Dog("Rover", "German Sheppard")
spike.arr.should.eql([1,2,3,4,5])
rover.arr.should.eql([1,2,3,4,5])
dog.arr.should.eql([1,2,3,4,5])
end
end
describe 'Combination Inheritance Solves Static Prototype Properties Issue'
it 'should maintain separate state for each child object'
child_1 = new Child("David", 21)
child_2 = new Child("Peter", 32)
child_1.arr.push(999)
child_2.arr.push(333)
child_1.arr.should.eql([1,2,3,999])
child_2.arr.should.eql([1,2,3,333])
child_1.getAge().should.eql 21
child_1.should.be_a Parent
end
end
describe 'Prototypal Inheritance'
it 'should inherit properties from parent'
person.toString().should.match /Original toString.*/i
person.password.should.eql 'secret'
joe = helper.inherit(person)
joe.password.should.eql 'secret'
joe.password = 'letmein'
joe.password.should.eql 'letmein'
person.password.should.eql 'secret'
end
end
describe 'Parisitic Combination Inheritance'
it 'should use inheritPrototype (to call parent constructor once) and still work as expected'
sub = new SubType("Nicholas Zakas", 29)
sub.toString().should.match /.*Nicholas Zakas/
sub.getAge().should.eql 29
charlie = new SubType("Charlie Brown", 69)
charlie.arr.should.eql([1,2,3])
charlie.arr.push(999)
charlie.arr.should.eql([1,2,3,999])
sub.arr.should.eql([1,2,3])
sub.should.be_an_instance_of SubType
charlie.should.be_an_instance_of SubType
(sub instanceof SubType).should.eql true
(sub instanceof Parent).should.eql true
end
end
describe 'Functional Durable Inheritance'
it 'should hide private variables'
sup = new super_func( {name: "Superfly Douglas", age: 39, foo: "foo", bar: "bar"} )
sup.getName().should.eql 'Superfly Douglas'
sup.name.should.be_undefined
sup.getAge().should.eql 39
sup.age.should.be_undefined
sup.getFoo().should.eql 'foo'
sup.foo.should.be_undefined
end
it 'should create a descendent object that inherits properties while maintaining privacy'
sub = new sub_func( {name: "Submarine", age: 1, foo: "food", bar: "barfly"} )
sub.getName().should.eql 'Submarine'
sub.name.should.be_undefined
sub.getAge().should.eql 1
sub.age.should.be_undefined
sub.getFoo().should.eql 'food'
sub.foo.should.be_undefined
sub.getBar().should.eql 'barfly'
sub.bar.should.be_undefined
sub.coolAugment().should.match /.*fresh new perspective.*/
//sub.should.be_an_instance_of super_func NOPE!
//sub.should.be_an_instance_of sub_func NOPE!
sub.should.be_an_instance_of Object
end
end
end
Спасибо всем! О, и если вы хотите проверить мое эссе/книгу, я бы хотел получить обратную связь:
TDD JavaScript в репозитории GitHub
Ответы
Ответ 1
См. Как правильно и quot; создать пользовательский объект в JavaScript? для сводки. (Могу также связать это, так как я потратил столько времени, чтобы напечатать его!)
Dog.prototype = new Animal();
как правило, следует избегать. Вы видите это в примере/учебном коде, но это ужасный беспорядок, потому что он основывает класс на экземпляре и экземпляр, построенный с ошибкой: name
- undefined. Любой более сложный конструктор будет расстраиваться из-за такого рода вещей.
Object.prototype.inherit =
Лучше подходит для построения, но прототипирование чего-либо в Object
считается очень слабым вкусом. Он рискует испортить использование объектов в качестве тривиальных карт и нарушить другие коды. Вы можете поместить эту вспомогательную функцию в другое место, например. Function.prototype.subclass
.
prototype.constructor
Лично я бы избегал, потому что constructor
имеет особое значение в JavaScript (как реализовано в Firefox и некоторых других браузерах, а не в IE JScript), и это значение не то, что constructor
делает здесь, и то, что вы ожидать любого такого имущества; это запутывает и почти всегда лучше всего избегать. Поэтому, если включить ссылку на функцию конструктора в экземпляре в системе класса, я бы предпочел назвать ее чем-то другим.
Ответ 2
Сотрудник моей предыдущей компании разработал библиотеку, чтобы сделать java как наследование http://www.uselesspickles.com/class_library/. Я думаю, что он более сексуальный, чем предложения Rajendra, синтаксис выглядит чище.
Я написал статью, демонстрирующую различные способы ее приближения, но следя за тем, чтобы избежать известных вредоносных методов. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html, это если вы не хотите загружать библиотеку, а просто хотите скопировать какой-нибудь код, который вы можете улучшить, чтобы сделать то, что вам нужно.
Ответ 3
Здесь интересен интересный шаблон: конструктор JavaScript может возвращать любой объект (это не обязательно). Можно было бы создать функцию-конструктор, которая возвращает прокси-объект, который содержит прокси-методы для "реальных" методов "реального" экземпляра объекта. Это может показаться сложным, но это не так; Вот фрагмент кода:
var MyClass = function() {
var instanceObj = this;
var proxyObj = {
myPublicMethod: function() {
return instanceObj.myPublicMethod.apply(instanceObj, arguments);
}
}
return proxyObj;
};
MyClass.prototype = {
_myPrivateMethod: function() {
...
},
myPublicMethod: function() {
...
}
};
Самое приятное, что создание прокси-сервера может быть автоматизировано, если мы определим соглашение для именования защищенных методов. Я создал небольшую библиотеку, которая делает именно это: http://idya.github.com/oolib/
Ответ 4
У меня есть как минимум полдюжины реализаций различных шаблонов наследования, лежащих в моей папке dev/web/stuff
, но они в основном являются игрушками.
То, что я на самом деле иногда использую, - это следующая тонкая оболочка по сравнению с псевдо-классовым подходом JavaScript по умолчанию, чтобы упростить наследование:
Function.prototype.derive = (function() {
function Dummy() {}
return function() {
Dummy.prototype = this.prototype;
return new Dummy;
};
})();
Пример кода:
function Pet(owner, type, name) {
this.owner = owner;
this.type = type;
this.name = name;
}
Pet.prototype.toString = function() {
return this.owner + '\ ' + this.type + ' ' + this.name;
};
function Cat(owner, name) {
Pet.call(this, owner, 'cat', name);
}
Cat.prototype = Pet.derive();
var souris = new Cat('Christoph', 'Souris');
Другим интересным является следующее, которое автоматически добавляет методы factory к правильному прототипному подходу:
var Proto = new (function() {
function Dummy() {}
this.clone = function() {
Dummy.prototype = this;
return new Dummy;
};
this.init = function() {};
this.create = function() {
var obj = this.clone();
this.init.apply(obj, arguments);
return obj;
};
});
Пример кода:
var Pet = Proto.clone();
Pet.init = function(owner, type, name) {
this.owner = owner;
this.type = type;
this.name = name;
};
Pet.toString = function() {
return this.owner + '\ ' + this.type + ' ' + this.name;
};
Cat = Pet.clone();
Cat.init = function(owner, name) {
Pet.init.call(this, owner, 'cat', name);
};
// use factory method
var filou = Cat.create('Christoph', 'Filou');
// use cloning (the proper prototypal approach)
var red = filou.clone();
red.name = 'Red';
Вы уже видели реализация классов.
Ответ 5
Поздно к вечеринке здесь, но у меня есть 2 балла.
1) Пожалуйста, не сообщайте людям наследование путем создания объектов супертипа. Это считается плохой практикой по нескольким причинам. Во-первых, это принципиальная ошибка. Вы создаете объекты только для использования своих методов и ничего не делаете с экземпляром как таковым. Правильный способ сделать это - использовать метод Object.prototype.inherit. Кроме того, этот метод заставляет вас оставить аргумент функции конструктора супертипа пустым, что может привести к ошибке при строгих обстоятельствах.
2) Вы забыли упомянуть шаблон кражи конструктора.
function Supertype(name){
this.name=name;
this.sayName = function(){console.log(this.name);};
}
function Subtype(name){
//inherit by using (stealing) supertype constructor function
Supertype(name);
// child specific properties
//
}