Próbuję zaimplementować dziedziczenie w javascript. Wymyśliłem następujący minimalny kod do obsługi tego.
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
Eksperci, dajcie mi znać, czy to wystarczy lub jakaś inna ważna kwestia, którą mogłam przegapić. W oparciu o podobne problemy prosimy o zaproponowanie innych zmian.
Oto kompletny skrypt testowy:
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
this.superalert = function(){
alert('tst');
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
function Child(){
Base.extend(this, Base);
this.width = 20;
this.height = 15;
this.a = ['s',''];
this.alert = function(){
alert(this.a.length);
alert(this.height);
}
}
function Child1(){
Base.extend(this, Child);
this.depth = 'depth';
this.height = 'h';
this.alert = function(){
alert(this.height); // display current object height
alert(this.a.length); // display parents array length
this.call(this.base.alert);
// explicit call to parent alert with current objects value
this.call(this.base.superalert);
// explicit call to grandparent, parent does not have method
this.base.alert(); // call parent without overriding values
}
}
var v = new Child1();
v.alert();
alert(v.height);
alert(v.depth);
javascript
inheritance
hungryMind
źródło
źródło
Odpowiedzi:
Aby zaimplementować dziedziczenie javascript w ECMAScript 5 , możesz zdefiniować prototyp obiektu i użyć go
Object.create
do dziedziczenia. Możesz także dodawać / zastępować właściwości, ile chcesz.Przykład:
/** * Transform base class */ function Transform() { this.type = "2d"; } Transform.prototype.toString = function() { return "Transform"; } /** * Translation class. */ function Translation(x, y) { // Parent constructor Transform.call(this); // Public properties this.x = x; this.y = y; } // Inheritance Translation.prototype = Object.create(Transform.prototype); // Override Translation.prototype.toString = function() { return Transform.prototype.toString() + this.type + " Translation " + this.x + ":" + this.y; } /** * Rotation class. */ function Rotation(angle) { // Parent constructor Transform.call(this); // Public properties this.angle = angle; } // Inheritance Rotation.prototype = Object.create(Transform.prototype); // Override Rotation.prototype.toString = function() { return Transform.prototype.toString() + this.type + " Rotation " + this.angle; } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
źródło
Translation.prototype = new Transform()
? A skoro odpowiedź obecnie nie działa, czy zmodyfikowałbyś ją?Translation.prototype.constructor = Translation
. Przydatne do klonowania obiektu (w większości technik).Myślę, że rozwiązanie Crockforda jest zbyt skomplikowane, podobnie jak rozwiązanie Johna. O wiele prostsze jest uzyskanie dziedziczenia javascript, niż oba wydają się opisywać. Rozważać:
//Classes function A() { B.call(this); } function B() { C.call(this); this.bbb = function() { console.log("i was inherited from b!"); } } function C() { D.call(this); } function D() { E.call(this); } function E() { //instance property this.id = Math.random() } //set up the inheritance chain (order matters) D.prototype = new E(); C.prototype = new D(); B.prototype = new C(); A.prototype = new B(); //Add custom functions to each A.prototype.foo = function() { console.log("a"); }; B.prototype.bar = function() { console.log("b"); }; C.prototype.baz = function() { console.log("c"); }; D.prototype.wee = function() { console.log("d"); }; E.prototype.woo = function() { console.log("e"); }; //Some tests a = new A(); a.foo(); a.bar(); a.baz(); a.wee(); a.woo(); console.log(a.id); a.bbb(); console.log(a instanceof A); console.log(a instanceof B); console.log(a instanceof C); console.log(a instanceof D); console.log(a instanceof E); var b = new B(); console.log(b.id)
Pełny opis powyższego rozwiązania napisałem na swoim blogu .
źródło
this._myProtectedVariable = 5;
tworzenie chronionych członków.Kiedy bawiłem się obiektami JS, znalazłem bardziej minimalistyczne rozwiązanie :-) Ciesz się!
function extend(b,a,t,p) { b.prototype = a; a.apply(t,p); }
Przykład
function A() { this.info1 = function() { alert("A"); } } function B(p1,p2) { extend(B,A,this); this.info2 = function() { alert("B"+p1+p2); } } function C(p) { extend(C,B,this,["1","2"]); this.info3 = function() { alert("C"+p); } } var c = new C("c"); c.info1(); // A c.info2(); // B12 c.info3(); // Cc
źródło
Oto najprostszy i mam nadzieję, że najłatwiejszy sposób zrozumienia dziedziczenia w JS. Najbardziej pomocny przykład będzie dla programistów PHP.
function Mother(){ this.canSwim = function(){ console.log('yes'); } } function Son(){}; Son.prototype = new Mother; Son.prototype.canRun = function(){ console.log('yes'); }
Teraz syn ma jedną zastąpioną metodę i jedną nową
function Grandson(){} Grandson.prototype = new Son; Grandson.prototype.canPlayPiano = function(){ console.log('yes'); }; Grandson.prototype.canSwim = function(){ console.log('no'); }
Teraz wnuk ma dwie nadpisane metody i jedną nową
var g = new Grandson; g.canRun(); // => yes g.canPlayPiano(); // => yes g.canSwim(); // => no
źródło
new
do dziedziczeniaDlaczego nie używać obiektów zamiast funkcji:
var Base = { superalert : function() { alert('tst'); } }; var Child = Object.create(Base); Child.width = 20; Child.height = 15; Child.a = ['s','']; Child.childAlert = function () { alert(this.a.length); alert(this.height); } var Child1 = Object.create(Child); Child1.depth = 'depth'; Child1.height = 'h'; Child1.alert = function () { alert(this.height); alert(this.a.length); this.childAlert(); this.superalert(); };
I nazwij to tak:
var child1 = Object.create(Child1); child1.alert();
To podejście jest znacznie bardziej przejrzyste niż w przypadku funkcji. Znalazłem ten blog wyjaśniający, dlaczego dziedziczenie z funkcjami nie jest właściwym sposobem robienia tego w JS: http://davidwalsh.name/javascript-objects-deconstruction
EDYTOWAĆ
var Dziecko można również zapisać jako:
var Child = Object.create(Base, { width : {value : 20}, height : {value : 15, writable: true}, a : {value : ['s', ''], writable: true}, childAlert : {value : function () { alert(this.a.length); alert(this.height); }} });
źródło
Oto moje rozwiązanie, które opiera się na standardowym prototypowego dziedziczenia metody opisanej w Lorenzo Polidori za odpowiedź .
Najpierw zacznę od zdefiniowania tych pomocniczych metod, które później ułatwiają zrozumienie i czytelność:
Function.prototype.setSuperclass = function(target) { // Set a custom field for keeping track of the object's 'superclass'. this._superclass = target; // Set the internal [[Prototype]] of instances of this object to a new object // which inherits from the superclass's prototype. this.prototype = Object.create(this._superclass.prototype); // Correct the constructor attribute of this class's prototype this.prototype.constructor = this; }; Function.prototype.getSuperclass = function(target) { // Easy way of finding out what a class inherits from return this._superclass; }; Function.prototype.callSuper = function(target, methodName, args) { // If methodName is ommitted, call the constructor. if (arguments.length < 3) { return this.callSuperConstructor(arguments[0], arguments[1]); } // `args` is an empty array by default. if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); var method = superclass.prototype[methodName]; if (typeof method != "function") throw new TypeError("TypeError: Object " + superclass.prototype + " has no method '" + methodName + "'"); return method.apply(target, args); }; Function.prototype.callSuperConstructor = function(target, args) { if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); return superclass.apply(target, args); };
Teraz nie tylko możesz ustawić nadklasę klasy za pomocą
SubClass.setSuperclass(ParentClass)
, ale możesz także wywołać metody nadpisane za pomocąSubClass.callSuper(this, 'functionName', [argument1, argument2...])
:/** * Transform base class */ function Transform() { this.type = "2d"; } Transform.prototype.toString = function() { return "Transform"; } /** * Translation class. */ function Translation(x, y) { // Parent constructor Translation.callSuper(this, arguments); // Public properties this.x = x; this.y = y; } // Inheritance Translation.setSuperclass(Transform); // Override Translation.prototype.toString = function() { return Translation.callSuper(this, 'toString', arguments) + this.type + " Translation " + this.x + ":" + this.y; } /** * Rotation class. */ function Rotation(angle) { // Parent constructor Rotation.callSuper(this, arguments); // Public properties this.angle = angle; } // Inheritance Rotation.setSuperclass(Transform); // Override Rotation.prototype.toString = function() { return Rotation.callSuper(this, 'toString', arguments) + this.type + " Rotation " + this.angle; } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
Trzeba przyznać, że nawet w przypadku funkcji pomocniczych składnia jest tutaj dość niezręczna. Na szczęście w ECMAScript 6 dodano trochę cukru syntaktycznego ( maksymalnie minimalne klasy ), aby uczynić rzeczy znacznie ładniejszymi. Na przykład:
/** * Transform base class */ class Transform { constructor() { this.type = "2d"; } toString() { return "Transform"; } } /** * Translation class. */ class Translation extends Transform { constructor(x, y) { super(); // Parent constructor // Public properties this.x = x; this.y = y; } toString() { return super(...arguments) + this.type + " Translation " + this.x + ":" + this.y; } } /** * Rotation class. */ class Rotation extends Transform { constructor(angle) { // Parent constructor super(...arguments); // Public properties this.angle = angle; } toString() { return super(...arguments) + this.type + " Rotation " + this.angle; } } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
Zauważ, że ECMAScript 6 jest nadal w fazie roboczej i o ile wiem, nie jest zaimplementowany w żadnej większej przeglądarce internetowej. Jeśli jednak chcesz, możesz użyć czegoś takiego jak kompilator Traceur, aby skompilować
ECMAScript 6
się do zwykłego, staregoECMAScript 5
JavaScript. Możesz zobaczyć powyższy przykład skompilowany przy użyciu Traceur tutaj .źródło
Chociaż zgadzam się ze wszystkimi powyższymi odpowiedziami, uważam, że JavaScript nie musi być zorientowany obiektowo (unikaj dziedziczenia), zamiast tego w większości przypadków wystarczające powinno być podejście oparte na obiektach .
Podoba mi się sposób, w jaki Eloquent JavaScript rozpoczyna rozdział 8 poświęcony programowaniu obiektowemu, mówiąc o obiektach obiektowych. Zamiast rozszyfrować najlepszy sposób implementacji dziedziczenia, należy poświęcić więcej energii na naukę funkcjonalnych aspektów JavaScript, stąd bardziej interesujący wydał mi się rozdział 6 dotyczący programowania funkcjonalnego.
źródło
//This is an example of how to override a method, while preserving access to the original. //The pattern used is actually quite simple using JavaScripts ability to define closures: this.somefunction = this.someFunction.override(function(args){ var result = this.inherited(args); result += this.doSomethingElse(); return result; }); //It is accomplished through this piece of code (courtesy of Poul Krogh): /*************************************************************** function.override overrides a defined method with a new one, while preserving the old method. The old method is only accessible from the new one. Use this.inherited() to access the old method. ***************************************************************/ Function.prototype.override = function(func) { var remember = this; var f = function() { var save = this.inherited; this.inherited = remember; var result = func.apply(this, Array.prototype.slice.call(arguments)); this.inherited = save; return result; }; return f; }
źródło
Co powiesz na to proste podejście
function Body(){ this.Eyes = 2; this.Arms = 2; this.Legs = 2; this.Heart = 1; this.Walk = function(){alert(this.FirstName + ' Is Walking')}; } function BasePerson() { var BaseBody = new Body(this); BaseBody.FirstName = ''; BaseBody.LastName = ''; BaseBody.Email = ''; BaseBody.IntroduceSelf = function () { alert('Hello my name is ' + this.FirstName + ' ' + this.LastName); }; return BaseBody; } function Person(FirstName,LastName) { var PersonBuild = new BasePerson(); PersonBuild.FirstName = FirstName; PersonBuild.LastName = LastName; return PersonBuild; } var Person1 = new Person('Code', 'Master'); Person1.IntroduceSelf(); Person1.Walk();
źródło
Podstawowe dziedziczenie prototypowe
Prostym, ale skutecznym sposobem dziedziczenia w JavaScript jest użycie następującego dwuwierszowego:
B.prototype = Object.create(A.prototype); B.prototype.constructor = B;
To jest podobne do zrobienia tego:
B.prototype = new A();
Główna różnica między nimi polega na tym, że konstruktor programu
A
nie jest uruchamiany podczas używaniaObject.create
, co jest bardziej intuicyjne i bardziej podobne do dziedziczenia opartego na klasach.Zawsze możesz opcjonalnie uruchomić konstruktora programu
A
podczas tworzenia nowej instancji programuB
, dodając go do konstruktoraB
:function B(arg1, arg2) { A(arg1, arg2); // This is optional }
Jeśli chcesz przekazać wszystkie argumenty funkcji
B
doA
, możesz również użyćFunction.prototype.apply()
:function B() { A.apply(this, arguments); // This is optional }
Jeśli chcesz dodać inny obiekt do łańcucha konstruktorów
B
, możesz połączyćObject.create
zObject.assign
:B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype); B.prototype.constructor = B;
Próbny
function A(name) { this.name = name; } A.prototype = Object.create(Object.prototype); A.prototype.constructor = A; function B() { A.apply(this, arguments); this.street = "Downing Street 10"; } B.prototype = Object.create(A.prototype); B.prototype.constructor = B; function mixin() { } mixin.prototype = Object.create(Object.prototype); mixin.prototype.constructor = mixin; mixin.prototype.getProperties = function() { return { name: this.name, address: this.street, year: this.year }; }; function C() { B.apply(this, arguments); this.year = "2018" } C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype); C.prototype.constructor = C; var instance = new C("Frank"); console.log(instance); console.log(instance.getProperties());
Tworzenie własnego opakowania
Jeśli nie lubisz pisać z grubsza tego samego dwuwierszowego kodu w całym kodzie, możesz napisać podstawową funkcję opakowującą, taką jak ta:
function inheritance() { var args = Array.prototype.slice.call(arguments); var firstArg = args.shift(); switch (args.length) { case 0: firstArg.prototype = Object.create(Object.prototype); firstArg.prototype.constructor = firstArg; break; case 1: firstArg.prototype = Object.create(args[0].prototype); firstArg.prototype.constructor = firstArg; break; default: for(var i = 0; i < args.length; i++) { args[i] = args[i].prototype; } args[0] = Object.create(args[0]); var secondArg = args.shift(); firstArg.prototype = Object.assign.apply(Object, args); firstArg.prototype.constructor = firstArg; } }
Jak działa to opakowanie:
Object
.Próbny
function inheritance() { var args = Array.prototype.slice.call(arguments); var firstArg = args.shift(); switch (args.length) { case 0: firstArg.prototype = Object.create(Object.prototype); firstArg.prototype.constructor = firstArg; break; case 1: firstArg.prototype = Object.create(args[0].prototype); firstArg.prototype.constructor = firstArg; break; default: for(var i = 0; i < args.length; i++) { args[i] = args[i].prototype; } args[0] = Object.create(args[0]); var secondArg = args.shift(); firstArg.prototype = Object.assign.apply(Object, args); firstArg.prototype.constructor = firstArg; } } function A(name) { this.name = name; } inheritance(A); function B() { A.apply(this, arguments); this.street = "Downing Street 10"; } inheritance(B, A); function mixin() { } inheritance(mixin); mixin.prototype.getProperties = function() { return { name: this.name, address: this.street, year: this.year }; }; function C() { B.apply(this, arguments); this.year = "2018" } inheritance(C, B, mixin); var instance = new C("Frank"); console.log(instance); console.log(instance.getProperties());
Uwaga
Object.create
może być bezpiecznie używany w każdej nowoczesnej przeglądarce, w tym IE9 +.Object.assign
nie działa w żadnej wersji IE ani w niektórych przeglądarkach mobilnych. Zaleca się wypełnianie w formie polyfillObject.create
i / lubObject.assign
jeśli chcesz ich używać i obsługiwać przeglądarki, które ich nie implementują.Możesz znaleźć polyfill dla
Object.create
tutaj i jeden dlaObject.assign
tutaj .źródło
// // try this one: // // function ParentConstructor() {} // function ChildConstructor() {} // // var // SubClass = ChildConstructor.xtendz( ParentConstructor ); // Function.prototype.xtendz = function ( SuperCtorFn ) { return ( function( Super, _slice ) { // 'freeze' host fn var baseFn = this, SubClassCtorFn; // define child ctor SubClassCtorFn = function ( /* child_ctor_parameters..., parent_ctor_parameters[] */ ) { // execute parent ctor fn on host object // pass it last ( array ) argument as parameters Super.apply( this, _slice.call( arguments, -1 )[0] ); // execute child ctor fn on host object // pass remaining arguments as parameters baseFn.apply( this, _slice.call( arguments, 0, -1 ) ); }; // establish proper prototype inheritance // 'inherit' methods SubClassCtorFn.prototype = new Super; // (re)establish child ctor ( instead of Super ctor ) SubClassCtorFn.prototype.constructor = SubClassCtorFn; // return built ctor return SubClassCtorFn; } ).call( this, SuperCtorFn, Array.prototype.slice ); }; // declare parent ctor function Sup( x1, x2 ) { this.parent_property_1 = x1; this.parent_property_2 = x2; } // define some methods on parent Sup.prototype.hello = function(){ alert(' ~ h e l l o t h e r e ~ '); }; // declare child ctor function Sub( x1, x2 ) { this.child_property_1 = x1; this.child_property_2 = x2; } var SubClass = Sub.xtendz(Sup), // get 'child class' ctor obj; // reserve last array argument for parent ctor obj = new SubClass( 97, 98, [99, 100] ); obj.hello(); console.log( obj ); console.log('obj instanceof SubClass -> ', obj instanceof SubClass ); console.log('obj.constructor === SubClass -> ', obj.constructor === SubClass ); console.log('obj instanceof Sup -> ', obj instanceof Sup ); console.log('obj instanceof Object -> ', obj instanceof Object ); // // Object {parent_property_1: 99, parent_property_2: 100, child_property_1: 97, child_property_2: 98} // obj instanceof SubClass -> true // obj.constructor === SubClass -> true // obj instanceof Sup -> true // obj instanceof Object -> true //
źródło
Najłatwiejszy sposób korzystania z biblioteki AWeb . Oficjalna próbka:
/** * A-class */ var ClassA = AWeb.class({ public : { /** * A-class constructor */ constructor : function() { /* Private variable */ this.variable1 = "A"; this.calls = 0; }, /** * Function returns information about the object */ getInfo : function() { this.incCalls(); return "name=" + this.variable1 + ", calls=" + this.calls; } }, private : { /** * Private function */ incCalls : function() { this.calls++; } } }); /** * B-class */ var ClassB = AWeb.class({ extends : ClassA, public : { /** * B-class constructor */ constructor : function() { this.super(); /* Private variable */ this.variable1 = "B"; }, /** * Function returns extended information about the object */ getLongInfo : function() { return this.incCalls !== undefined ? "incCalls exists" : "incCalls undefined"; } } }); /** * Main project function */ function main() { var a = new ClassA(), b = new ClassB(); alert( "a.getInfo " + (a.getInfo ? "exists" : "undefined") + "\n" + "a.getLongInfo " + (a.getLongInfo ? "exists" : "undefined") + "\n" + "b.getInfo " + (b.getInfo ? "exists" : "undefined") + "\n" + "b.getLongInfo " + (b.getLongInfo ? "exists" : "undefined") + "\n" + "b.getInfo()=" + b.getInfo() + "\n" + "b.getLongInfo()=" + b.getLongInfo() ); }
źródło
Znalazłem rozwiązanie znacznie łatwiejsze niż rozbudowa i prototypowanie rzeczy. Właściwie nie wiem, na ile to jest wydajne, chociaż wygląda czysto i funkcjonalnie.
var A = function (p) { if (p == null) p = this; p.a1 = 0; this.a2 = 0; var a3 = 0; }; var B = function (p) { if (p == null) p = this; p.b1 = new A(this); this.b2 = new A(this); var b3 = new A(this); this b4 = new A(); }; var a = new A (); var b = new B ();
wynik:
a a1 0 a2 0 b a1 0 b1 a2 0 b2 a2 0 b4 a1 0 a2 0
praktyczny przykład:
var Point = function (p) { if (p == null) p = this; var x = 0; var y = 0; p.getPoint = function () { return [x,y]; }; p.setPoint = function (_x,_y) { x = _x; y = _y; }; }; var Dimension = function (p) { if (p == null) p = this; var w = 0; var h = 0; p.getDimension = function() { return [w,h] }; p.setDimension = function(_w,_h) { w = _w; h = _h }; }; var Rect = function (p) { if (p == null) p = this; var dimension = new Dimension(this); var location = new Point(this); }; var rect = new Rect (); rect.setDimension({w:30,h:40}); rect.setPoint({x:50,y:50});
źródło