2010-01-14 13 views
84

He estado programando con lenguajes OOP durante más de 10 años, pero ahora estoy aprendiendo JavaScript y es la primera vez que encuentro herencia basada en prototipos. Tiendo a aprender más rápido estudiando un buen código. ¿Qué es un ejemplo bien escrito de una aplicación de JavaScript (o biblioteca) que utiliza correctamente la herencia prototípica? ¿Y puede describir (brevemente) cómo/dónde se usa la herencia prototípica, entonces sé por dónde empezar a leer?Buen ejemplo de la herencia basada en prototipos de JavaScript

+1

¿Recibió la oportunidad de comprobar que la biblioteca Base? Realmente es lindo, y bastante pequeño. Si te gusta, considera marcar mi respuesta como la respuesta. TIA, Roland. –

+0

Supongo que estoy en el mismo barco que tú. También quiero aprender un poco sobre este lenguaje prototípico, no estar restringido solo a los marcos de oop o similares, incluso si son geniales y todo, tenemos que aprender, ¿no? No solo un marco de trabajo hace eso por mí, incluso si lo voy a usar. Pero aprende a crear cosas nuevas en nuevos idiomas con nuevas formas, piensa fuera de la caja. Me gusta tu estilo. Voy a intentar ayudarme y tal vez ayudarte. Tan pronto como encuentre algo, se lo haré saber. –

Respuesta

46

Douglas Crockford tiene una buena página en JavaScript Prototypal Inheritance:

hace

Cinco años escribí Classical Inheritance en JavaScript. Mostró que JavaScript es un lenguaje prototípico libre de clases, y que tiene suficiente poder expresivo para simular un sistema clásico. Mi estilo de programación ha evolucionado desde entonces, como cualquier buen programador debería. Aprendí a abrazar por completo el prototipalme y me liberé de los confines del modelo clásico. de

Dean Edward Base.js, Mootools's Class o John Resig's Simple Inheritance obras son maneras de hacer classical inheritance en JavaScript.

+0

¿Por qué no simplemente 'newObj = Object.create (oldObj);' si lo quiere sin clases? De lo contrario, reemplace con 'oldObj' con el objeto prototipo de la función de constructor ¿debería funcionar? – Cyker

+0

Se apreciaría un ejemplo de código. – JedatKinports

14

me gustaría echar un vistazo a YUI, y al Base biblioteca de Dean Edward: http://dean.edwards.name/weblog/2006/03/base/

Para YUI puede tomar un rápido vistazo a la lang module, esp. el método YAHOO.lang.extend. Y luego, puede explorar la fuente de algunos widgets o utilidades y ver cómo usan ese método.

+4

+1 para Dean's Base.js –

+0

YUI 2 ha quedado obsoleto a partir de 2011, por lo que el enlace a 'lang' está semi-roto. ¿Alguien se preocupa por arreglarlo para YUI 3? – ack

1

Los mejores ejemplos que he visto están en Douglas Crockford JavaScript: The Good Parts. Definitivamente vale la pena comprar para ayudarlo a obtener una vista equilibrada del idioma.

Douglas Crockford es responsable del formato JSON y trabaja en Yahoo como un gurú de JavaScript.

+7

responsable? eso suena casi como "culpable de" :) –

+0

@Roland Creo que JSON es un buen formato no detallado para almacenar datos. Definitivamente no lo inventó, el formato estaba ahí para la configuración de configuración en Steam en 2002 –

+0

Chris S, creo que también - Más y más a menudo desearía haber omitido todos los XML como formato de intercambio y seguir adelante el JSON de inmediato. –

74

Como se mencionó, las películas de Douglas Crockford dan una buena explicación sobre el por qué y cubre el cómo. Pero para ponerlo en un par de líneas de JavaScript:

// Declaring our Animal object 
var Animal = function() { 

    this.name = 'unknown'; 

    this.getName = function() { 
     return this.name; 
    } 

    return this; 
}; 

// Declaring our Dog object 
var Dog = function() { 

    // A private variable here   
    var private = 42; 

    // overriding the name 
    this.name = "Bello"; 

    // Implementing ".bark()" 
    this.bark = function() { 
     return 'MEOW'; 
    } 

    return this; 
}; 


// Dog extends animal 
Dog.prototype = new Animal(); 

// -- Done declaring -- 

// Creating an instance of Dog. 
var dog = new Dog(); 

// Proving our case 
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n", 
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n", 
    dog.bark() +"\n", // Should be: "MEOW" 
    dog.getName() +"\n", // Should be: "Bello" 
    dog.private +"\n" // Should be: 'undefined' 
); 

El problema con este enfoque, sin embargo, es que va a volver a crear el objeto cada vez que cree una. Otro enfoque es declarar sus objetos en la pila prototipo, así:

// Defining test one, prototypal 
var testOne = function() {}; 
testOne.prototype = (function() { 
    var me = {}, privateVariable = 42; 
    me.someMethod = function() { 
     return privateVariable; 
    }; 

    me.publicVariable = "foo bar"; 
    me.anotherMethod = function() { 
     return this.publicVariable; 
    }; 

    return me; 

}()); 


// Defining test two, function 
var testTwo = ​function() { 
    var me = {}, privateVariable = 42; 
    me.someMethod = function() { 
     return privateVariable; 
    }; 

    me.publicVariable = "foo bar"; 
    me.anotherMethod = function() { 
     return this.publicVariable; 
    }; 

    return me; 
}; 


// Proving that both techniques are functionally identical 
var resultTestOne = new testOne(), 
    resultTestTwo = new testTwo(); 

console.log(
    resultTestOne.someMethod(), // Should print 42 
    resultTestOne.publicVariable // Should print "foo bar" 
); 

console.log(
    resultTestTwo.someMethod(), // Should print 42 
    resultTestTwo.publicVariable // Should print "foo bar" 
); 



// Performance benchmark start 
var stop, start, loopCount = 1000000; 

// Running testOne 
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) { 
    new testOne(); 
} 
stop = (new Date()).getTime(); 

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds'); 



// Running testTwo 
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) { 
    new testTwo(); 
} 
stop = (new Date()).getTime(); 

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds'); 

Hay un pequeño inconveniente cuando se trata de la introspección. El dumping de TestOne dará como resultado información menos útil. También la propiedad privada "privateVariable" en "testOne" se comparte en todas las instancias, como se menciona amablemente en las respuestas de shesek.

+3

Tenga en cuenta que en testOne 'privateVariable' es simplemente una variable en el ámbito de [IIFE] (http://benalman.com/news/2010/11/immediately-invoked-function-expression/), y se comparte en todas las instancias, por lo que no debe almacenar datos específicos de la instancia en él. (en testTwo es específico de la instancia, ya que cada llamada a testTwo() crea un nuevo alcance, por instancia) – shesek

+0

He votado a favor porque mostraste el otro enfoque y por qué no usarlo porque hace copias – Murphy316

+0

El problema de volver a crear el objeto siempre se debe principalmente a los métodos que se están recreando para cada nuevo objeto. Sin embargo, podemos mitigar el problema definiendo el método en 'Dog.prototype'. Entonces, en lugar de usar 'this.bark = function() {...}', podemos hacer 'Dot.prototype.bark = function() {...}' fuera de la función 'Dog'. (Consulte más detalles en [esta respuesta] (http://stackoverflow.com/a/1598077/3522482)) –

25
function Shape(x, y) { 
    this.x = x; 
    this.y = y; 
} 

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver 
function Circle(x, y, r) { 
    Shape.call(this, x, y); 
    this.r = r; 
} 

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor 
Circle.prototype = Object.create(Shape.prototype); 
+3

Quizás agregar este enlace con su respuesta podría completar la imagen aún más: https://developer.mozilla.org/ en/docs/Web/JavaScript/Reference/Global_Objects/Object/create – Dynom

0

Hay un fragmento JavaScript Prototype-based Inheritance con implementaciones específicas de la versión ECMAScript.Elegirá automáticamente qué usar entre las implementaciones ES6, ES5 y ES3 de acuerdo con el tiempo de ejecución actual.

3

Este es el ejemplo más claro que he encontrado, del libro de Mixu Nodo (http://book.mixu.net/node/ch6.html):

estoy a favor de la composición sobre la herencia:

Composición - Funcionalidad de un objeto se compone de un agregado de diferentes clases al contener instancias de otros objetos. Herencia: la funcionalidad de un objeto se compone de su propia funcionalidad más la funcionalidad de sus clases principales. Si debe tener herencia, use el antiguo JS

Si debe implementar la herencia, al menos evite el uso de otra implementación no estándar/función mágica. Aquí es cómo puede implementar un facsímil razonable de la herencia en el más puro ES3 (siempre a medida que siga la regla de no definir las propiedades de los prototipos):

function Animal(name) { 
    this.name = name; 
}; 
Animal.prototype.move = function(meters) { 
    console.log(this.name+" moved "+meters+"m."); 
}; 

function Snake() { 
    Animal.apply(this, Array.prototype.slice.call(arguments)); 
}; 
Snake.prototype = new Animal(); 
Snake.prototype.move = function() { 
    console.log("Slithering..."); 
    Animal.prototype.move.call(this, 5); 
}; 

var sam = new Snake("Sammy the Python"); 
sam.move(); 

Esto no es lo mismo que la herencia clásica - pero es estándar, Javascript comprensible y tiene la funcionalidad que busca principalmente: constructores encadenables y la capacidad de llamar a los métodos de la superclase.

4

ES6 class y extends

ES6 class y extends son sólo azúcar sintaxis para antes posible manipulación cadena de prototipo, y así podría decirse que la configuración más canónica.

En primer lugar aprender más acerca de la cadena de prototipos y la búsqueda de . propiedad en: https://stackoverflow.com/a/23877420/895245

Ahora vamos a deconstruir lo que sucede:

class C { 
    constructor(i) { 
     this.i = i 
    } 
    inc() { 
     return this.i + 1 
    } 
} 

class D extends C { 
    constructor(i) { 
     super(i) 
    } 
    inc2() { 
     return this.i + 2 
    } 
} 
// Inheritance syntax works as expected. 
(new C(1)).inc() === 2 
(new D(1)).inc() === 2 
(new D(1)).inc2() === 3 
// "Classes" are just function objects. 
C.constructor === Function 
C.__proto__ === Function.prototype 
D.constructor === Function 
// D is a function "indirectly" through the chain. 
D.__proto__ === C 
D.__proto__.__proto__ === Function.prototype 
// "extends" sets up the prototype chain so that base class 
// lookups will work as expected 
var d = new D(1) 
d.__proto__ === D.prototype 
D.prototype.__proto__ === C.prototype 
// This is what `d.inc` actually does. 
d.__proto__.__proto__.inc === C.prototype.inc 
// Class variables 
// No ES6 syntax sugar apparently: 
// https://stackoverflow.com/questions/22528967/es6-class-variable-alternatives 
C.c = 1 
C.c === 1 
// Because `D.__proto__ === C`. 
D.c === 1 
// Nothing makes this work. 
d.c === undefined 

diagrama simplificado y sin todos los objetos predefinidos:

 __proto__ 
(C)<---------------(D)   (d) 
| |    |   | 
| |    |   | 
| |prototype  |prototype |__proto__ 
| |    |   | 
| |    |   | 
| |    | +---------+ 
| |    | | 
| |    | | 
| |    v v 
|__proto__  (D.prototype) 
| |    | 
| |    | 
| |    |__proto__ 
| |    | 
| |    | 
| | +--------------+ 
| | | 
| | | 
| v v 
| (C.prototype)--->(inc) 
| 
v 
Function.prototype 
+2

Tu avatar es un hombre tan impresionante –

+0

@CharlesChow gracias! –

Cuestiones relacionadas