¿Cómo "correctamente" crear un objeto personalizado en JavaScript?

471

Me pregunto cuál es la mejor manera de crear un objeto JavaScript que tenga propiedades y métodos.

He visto ejemplos en los que la persona usa var self = thisy luego usa self.en todas las funciones para asegurarse de que el alcance sea siempre correcto.

Luego he visto ejemplos de uso .prototypepara agregar propiedades, mientras que otros lo hacen en línea.

¿Alguien puede darme un ejemplo adecuado de un objeto JavaScript con algunas propiedades y métodos?

Michael Stum
fuente
13
No hay una "mejor" forma.
Tríptico
¿No es selfuna palabra reservada? Si no, debe ser; dado que selfes una variable predefinida que se refiere a la ventana actual. self === window
Shaz
2
@ Shaz: no es una palabra reservada más que otras propiedades de windowen el modelo de objetos del navegador como documento frames; ciertamente puede reutilizar el identificador como nombre de variable. Aunque, sí, estilísticamente prefiero var that= thisevitar cualquier posible confusión. Aunque en window.selfúltima instancia no tiene sentido, rara vez hay alguna razón para tocarlo.
bobince
77
Cuando se minimiza JS, la asignación thisa una variable local (por ejemplo self) reduce los tamaños de archivo.
Patrick Fisher
Nuevo enlace de Classjs
Nikola

Respuestas:

889

Hay dos modelos para implementar clases e instancias en JavaScript: la forma de creación de prototipos y la forma de cierre. Ambos tienen ventajas y desventajas, y hay muchas variaciones extendidas. Muchos programadores y bibliotecas tienen diferentes enfoques y funciones de utilidad de manejo de clases para empapelar algunas de las partes más feas del lenguaje.

El resultado es que en una empresa mixta tendrá una mezcla de metaclases, todas con un comportamiento ligeramente diferente. Lo que es peor, la mayoría del material de tutorial de JavaScript es terrible y ofrece algún tipo de compromiso intermedio para cubrir todas las bases, dejándolo muy confundido. (Probablemente, el autor también está confundido. El modelo de objetos de JavaScript es muy diferente a la mayoría de los lenguajes de programación y, en muchos lugares, está mal diseñado).

Comencemos con el prototipo . Esta es la versión más nativa de JavaScript que puede obtener: hay un mínimo de código indirecto y instancia de funcionará con instancias de este tipo de objeto.

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

Podemos agregar métodos a la instancia creada new Shapeescribiéndolos en la prototypebúsqueda de esta función constructora:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Ahora para subclasificarlo, en la medida en que puede llamar lo que JavaScript hace subclases. Hacemos eso reemplazando por completo esa extraña prototypepropiedad mágica :

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

antes de agregarle métodos:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Este ejemplo funcionará y verá un código similar en muchos tutoriales. Pero hombre, eso new Shape()es feo: estamos creando instancias de la clase base a pesar de que no se va a crear una Forma real. Le pasa a trabajar en este caso sencillo, porque JavaScript está tan descuidado: permite cero argumentos que se pasan en, en cuyo caso xy yse vuelven undefinedy se asignan al prototipo de this.xy this.y. Si la función constructora estuviera haciendo algo más complicado, se caería de bruces.

Entonces, lo que debemos hacer es encontrar una forma de crear un objeto prototipo que contenga los métodos y otros miembros que queremos a nivel de clase, sin llamar a la función constructora de la clase base. Para hacer esto, vamos a tener que comenzar a escribir código auxiliar. Este es el enfoque más simple que conozco:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Esto transfiere los miembros de la clase base en su prototipo a una nueva función de constructor que no hace nada, luego usa ese constructor. Ahora podemos escribir simplemente:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

en lugar de lo new Shape()incorrecto. Ahora tenemos un conjunto aceptable de primitivas para clases construidas.

Hay algunos refinamientos y extensiones que podemos considerar bajo este modelo. Por ejemplo, aquí hay una versión sintáctica de azúcar:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Cualquiera de las versiones tiene el inconveniente de que la función de constructor no se puede heredar, como sucede en muchos idiomas. Entonces, incluso si su subclase no agrega nada al proceso de construcción, debe recordar llamar al constructor base con cualquier argumento que la base desee. Esto se puede automatizar un poco apply, pero aún debe escribir:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Entonces, una extensión común es dividir el material de inicialización en su propia función en lugar del propio constructor. Esta función puede heredar de la base muy bien:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Ahora acabamos de obtener la misma plantilla de función de constructor para cada clase. Tal vez podamos moverlo a su propia función auxiliar para que no tengamos que seguir tipeándolo, por ejemplo, en lugar de Function.prototype.subclassgirarlo y dejar que la función de la clase base escupe subclases:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... que está empezando a parecerse un poco más a otros idiomas, aunque con una sintaxis un poco más torpe. Puede agregar algunas características adicionales si lo desea. Tal vez desee makeSubclasstomar y recordar un nombre de clase y proporcionar un valor predeterminado para toStringusarlo. Tal vez desee hacer que el constructor detecte cuándo se ha llamado accidentalmente sin el newoperador (que de lo contrario a menudo resultaría en una depuración muy molesta):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Tal vez desee pasar a todos los nuevos miembros y makeSubclassagregarlos al prototipo, para evitar tener que escribir Class.prototype...tanto. Muchos sistemas de clase hacen eso, por ejemplo:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Hay muchas características potenciales que podría considerar deseables en un sistema de objetos y nadie está realmente de acuerdo con una fórmula en particular.


La forma de cierre , entonces. Esto evita los problemas de la herencia basada en prototipos de JavaScript, al no utilizar la herencia en absoluto. En lugar:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Ahora cada instancia de Shapetendrá su propia copia del toStringmétodo (y cualquier otro método u otro miembro de clase que agreguemos).

Lo malo de que cada instancia tenga su propia copia de cada miembro de la clase es que es menos eficiente. Si se trata de un gran número de instancias subclasificadas, la herencia prototípica puede servirle mejor. Además, llamar a un método de la clase base es un poco molesto como puede ver: tenemos que recordar cuál era el método antes de que el constructor de la subclase lo sobrescribiera, o se pierde.

[También porque no hay herencia aquí, el instanceofoperador no funcionará; Tendría que proporcionar su propio mecanismo para detectar clases si lo necesita. Si bien podría manipular los objetos prototipo de una manera similar a la de la herencia prototipo, es un poco complicado y realmente no vale la pena simplemente comenzar a instanceoftrabajar.]

Lo bueno de que cada instancia tenga su propio método es que el método puede estar vinculado a la instancia específica que lo posee. Esto es útil debido a la extraña forma de vinculación de JavaScript thisen las llamadas a métodos, que tiene el resultado de que si desconecta un método de su propietario:

var ts= mycircle.toString;
alert(ts());

luego, thisdentro del método no estará la instancia de Circle como se esperaba (en realidad será el windowobjeto global , causando problemas de depuración generalizados). En realidad, esto suele ocurrir cuando se toma un método y se le asigna a setTimeout, onclicko EventListeneren general.

Con el prototipo, debe incluir un cierre para cada tarea:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

o, en el futuro (o ahora si piratea Function.prototype) también puede hacerlo con function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

si sus instancias se realizan de la manera de cierre, el enlace se realiza de forma gratuita mediante el cierre sobre la variable de instancia (generalmente se llama thato self, aunque personalmente recomendaría que esta última selfya tenga otro significado diferente en JavaScript). Sin 1, 1embargo, no obtienes los argumentos en el fragmento anterior de forma gratuita, por lo que aún necesitarías otro cierre o un a bind()si necesitas hacerlo.

También hay muchas variantes en el método de cierre. Es posible que prefiera omitir por thiscompleto, crear uno nuevo thaty devolverlo en lugar de usar el newoperador:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

¿Qué camino es "correcto"? Ambos. Cuál es el mejor"? Eso depende de tu situación. FWIW Tiendo a la creación de prototipos para la herencia real de JavaScript cuando estoy haciendo mucho OO y cierres para efectos de página desechables simples.

Pero ambas formas son bastante contra intuitivas para la mayoría de los programadores. Ambos tienen muchas posibles variaciones desordenadas. Conocerá ambos (así como muchos esquemas intermedios y generalmente rotos) si utiliza el código / bibliotecas de otras personas. No hay una respuesta generalmente aceptada. Bienvenido al maravilloso mundo de los objetos JavaScript.

[Esto ha sido parte 94 de Por qué JavaScript no es mi lenguaje de programación favorito.]

bobince
fuente
13
Muy buen paso gradual de la definición de "clase" a la creación de instancias de objetos. Y un buen toque para evitar new.
Crescent Fresh el
8
Parece que JavaScript no es su idioma favorito porque desea usarlo como si tuviera clases.
Jonathan Feinberg el
59
Por supuesto que sí, también lo hacen todos: el modelo de clase e instancia es el más natural para la mayoría de los problemas comunes que enfrentan los programadores en la actualidad. Estoy de acuerdo en que, sobre una base teórica, la herencia basada en prototipos puede ofrecer una forma de trabajo más flexible, pero JavaScript no cumple totalmente esa promesa. Su sistema de función de constructor torpe nos brinda lo peor de ambos mundos, lo que dificulta la herencia de clase sin proporcionar ninguno de los prototipos de flexibilidad o simplicidad que podría ofrecer. En resumen, es caca.
bobince
44
Bob, creo que esta es una respuesta increíble: he estado lidiando con estos dos patrones por un tiempo y creo que has codificado algo más conciso que un Resig y explicado con más perspicacia que un Crockford. No se me ocurren mayores elogios ...
James Westgate
44
Siempre me pareció que graficar paradigmas de herencia clásicos en lenguajes prototípicos como javascript es una clavija cuadrada y un agujero redondo. ¿Hay momentos en que esto es realmente necesario o es solo una forma de que las personas den a entender que el idioma es como quieren en lugar de simplemente usar el idioma para lo que es?
slf
90

Utilizo este patrón con bastante frecuencia: descubrí que me da una gran cantidad de flexibilidad cuando lo necesito. En uso es bastante similar a las clases de estilo Java.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Esto utiliza una función anónima que se llama a la creación, devolviendo una nueva función de constructor. Debido a que la función anónima se llama solo una vez, puede crear variables privadas estáticas (están dentro del cierre, visibles para los otros miembros de la clase). La función de constructor es básicamente un objeto Javascript estándar: usted define atributos privados dentro de él y los atributos públicos están asociados a la thisvariable.

Básicamente, este enfoque combina el enfoque crockfordiano con objetos Javascript estándar para crear una clase más poderosa.

Puede usarlo como lo haría con cualquier otro objeto Javascript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method
ShZ
fuente
44
Eso parece interesante, porque está bastante cerca de mi "hogar", que es C #. También creo que empiezo a entender por qué privateStaticVariable es realmente privado (como se define dentro del alcance de una función y se mantiene vivo siempre que haya referencias a ella)
Michael Stum
Dado que no está usando, this¿aún necesita ser instanciado new?
Jordan Parmer
En realidad, this no acostumbrarse en la constructorfunción, que se convierte Fooen el ejemplo.
ShZ
44
El problema aquí es que cada objeto obtiene su propia copia de todas las funciones públicas y privadas.
virtualnobi
2
@virtualnobi: Este patrón no le impide escribir métodos protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin
25

Douglas Crockford discute ese tema extensamente en The Good Parts . Recomienda evitar que el nuevo operador cree nuevos objetos. En su lugar, propone crear constructores personalizados. Por ejemplo:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

En Javascript, una función es un objeto y se puede usar para construir objetos junto con el nuevo operador. Por convención, las funciones destinadas a ser utilizadas como constructores comienzan con una letra mayúscula. A menudo ves cosas como:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

En caso de que olvide usar el nuevo operador mientras crea una instancia de un nuevo objeto, lo que obtiene es una llamada a una función ordinaria, y esto está vinculado al objeto global en lugar del nuevo objeto.

Diego Pino
fuente
55
¿Soy yo o creo que Crockford no tiene absolutamente ningún sentido con su ataque al nuevo operador?
meder omuraliev
3
@meder: No solo a ti. Al menos, creo que no hay nada malo con el nuevo operador. Y hay un implícito newde var that = {};todos modos.
Tim Down
17
Crockford es un viejo malhumorado y no estoy de acuerdo con él en muchas cosas, pero al menos está promoviendo una mirada crítica a JavaScript y vale la pena escuchar lo que tiene que decir.
bobince
2
@bobince: De acuerdo. Sus escritos sobre cierres me abrieron los ojos a muchas cosas hace unos 5 años, y alienta un enfoque reflexivo.
Tim Down
20
Estoy de acuerdo con Crockford. El problema con el nuevo operador es que JavaScript hará que el contexto de "esto" sea muy diferente que cuando se llama a una función. A pesar de la convención de caso adecuada, existen problemas que surgen en bases de código más grandes, ya que los desarrolladores se olvidan de usar nuevos, se olvidan de poner en mayúsculas, etc. Introducir más puntos de falla en el código? JS es un lenguaje prototípico, no basado en clases. Entonces, ¿por qué queremos que actúe como un lenguaje tipeado estáticamente? Ciertamente no.
Joshua Ramirez
13

Para continuar con la respuesta de bobince

En es6 ahora puedes crear un class

Entonces ahora puedes hacer:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Entonces, extiéndalo a un círculo (como en la otra respuesta) que puede hacer:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Termina un poco más limpio en es6 y un poco más fácil de leer.


Aquí hay un buen ejemplo de ello en acción:

Naftali aka Neal
fuente
6

También puede hacerlo de esta manera, utilizando estructuras:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Entonces :

var counter1 = createCounter();
counter1.increaseBy(4);
Eino Gourdin
fuente
66
No me gusta así porque el espacio en blanco es importante. El rizado después del retorno debe estar en la misma línea para la compatibilidad entre navegadores.
geowa4
5

Otra forma sería http://jsfiddle.net/nnUY4/ (no sé si este tipo de manejo de creación de objetos y funciones reveladoras siguen algún patrón específico)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"
Fluchtpunkt
fuente
4

Cuando se usa el truco de cerrar "esto" durante una invocación de constructor, es para escribir una función que pueda ser utilizada como devolución de llamada por algún otro objeto que no quiera invocar un método en un objeto. No está relacionado con "hacer que el alcance sea correcto".

Aquí hay un objeto JavaScript vainilla:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Puede obtener mucho de leer lo que Douglas Crockford tiene que decir sobre JavaScript. John Resig también es brillante. ¡Buena suerte!

Jonathan Feinberg
fuente
1
Uh, cerrar thistiene que ver con "hacer que el alcance sea correcto".
Roatin Marth
3
Jonathan tiene razón. El alcance de una función js es lo que sea que diseñe para ser. El self = este truco es una forma de vincularlo a una instancia particular para que no cambie cuando se llama en otro contexto. Pero a veces eso es lo que realmente quieres. Depende del contexto.
Marco
Creo que todos dicen lo mismo en realidad. self=thisaunque no obliga thisa persistir, permite fácilmente el alcance "correcto" mediante un cierre.
Crescent Fresh el
2
La razón por la que haces eso = esto es para dar acceso a funciones anidadas al alcance de esto tal como existe en la función constructora. Cuando las funciones anidadas están dentro de las funciones del constructor, su alcance "este" vuelve al alcance global.
Joshua Ramirez
4

ClosureEs versátil. bobince ha resumido bien los enfoques prototipo versus cierre al crear objetos. Sin embargo, puede imitar algunos aspectos del OOPuso del cierre de una manera funcional de programación. Recuerde que las funciones son objetos en JavaScript ; así que use la función como objeto de una manera diferente.

Aquí hay un ejemplo de cierre:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Hace un tiempo me encontré con el artículo de Mozilla sobre Cierre. Esto es lo que salta a mis ojos: "Un cierre le permite asociar algunos datos (el entorno) con una función que opera en esos datos. Esto tiene paralelos obvios con la programación orientada a objetos, donde los objetos nos permiten asociar algunos datos (las propiedades del objeto ) con uno o más métodos ". Fue la primera vez que leí un paralelismo entre el cierre y la OOP clásica sin referencia al prototipo.

¿Cómo?

Suponga que desea calcular el IVA de algunos artículos. Es probable que el IVA se mantenga estable durante la vida útil de una aplicación. Una forma de hacerlo en OOP (pseudocódigo):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Básicamente, pasa un valor de IVA a su constructor y su método de cálculo puede operar sobre él a través del cierre . Ahora, en lugar de usar una clase / constructor, pase su IVA como argumento a una función. Como lo único que le interesa es el cálculo en sí, devuelve una nueva función, que es el método de cálculo:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

En su proyecto, identifique los valores de nivel superior que sean buenos candidatos para el cálculo del IVA. Como regla general cada vez que pasa los mismos argumentos una y otra vez, hay una manera de mejorarlo usando el cierre. No es necesario crear objetos tradicionales.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

Roland
fuente
3

Creando un objeto

La forma más fácil de crear un objeto en JavaScript es usar la siguiente sintaxis:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Esto funciona muy bien para almacenar datos de forma estructurada.

Sin embargo, para casos de uso más complejos, a menudo es mejor crear instancias de funciones:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Esto le permite crear múltiples objetos que comparten el mismo "plano", similar a cómo usa las clases, por ejemplo. Java.

Sin embargo, esto todavía se puede hacer de manera más eficiente mediante el uso de un prototipo.

Siempre que diferentes instancias de una función compartan los mismos métodos o propiedades, puede moverlas al prototipo de ese objeto. De esa manera, cada instancia de una función tiene acceso a ese método o propiedad, pero no necesita duplicarse para cada instancia.

En nuestro caso, tiene sentido mover el método fal prototipo:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Herencia

Una manera simple pero efectiva de hacer herencia en JavaScript es utilizar el siguiente dos líneas:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Eso es similar a hacer esto:

B.prototype = new A();

La principal diferencia entre ambos es que el constructor de Ano se ejecuta cuando se usa Object.create, que es más intuitivo y más similar a la herencia basada en clases.

Siempre puede optar por ejecutar opcionalmente el constructor de Aal crear una nueva instancia de Bagregando agregándolo al constructor de B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Si desea pasar todos los argumentos de Ba A, también puede usar Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Si desea mezclar otro objeto en la cadena del constructor B, puede combinarlo Object.createcon Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Manifestación

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());


Nota

Object.createse puede usar de forma segura en todos los navegadores modernos, incluido IE9 +. Object.assignno funciona en ninguna versión de IE ni en algunos navegadores móviles. Se recomienda rellenar polivinílicamente Object.create y / o Object.assignsi desea utilizarlos y admitir navegadores que no los implementen.

Puede encontrar un polyfill por Object.create aquí y uno por Object.assign aquí .

John Slegers
fuente
0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);
adeola olanrewaju
fuente
44
¿Qué agrega eso a las numerosas respuestas extensas que ya se ofrecen?
blm
Me gusta esta respuesta, ya que es concisa y muestra las tres partes de la implementación: 1) Definir el objeto, 2) Instanciar una instancia del objeto, 3) Usar la instancia: lo muestra todo de un vistazo en lugar de analizarlo. a través de todas las respuestas detalladas anteriores (que, por supuesto, son todas respuestas extremadamente buenas con todos los detalles pertinentes que uno desearía) - una especie de resumen simple aquí
G-Man
0

Además de la respuesta aceptada de 2009. Si puede dirigirse a los navegadores modernos, puede utilizar Object.defineProperty .

El método Object.defineProperty () define una nueva propiedad directamente en un objeto, o modifica una propiedad existente en un objeto, y devuelve el objeto. Fuente: Mozilla.

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Para ver una lista de compatibilidad de escritorio y móvil, consulte la lista de compatibilidad del navegador de Mozilla . Sí, IE9 + lo admite, así como Safari mobile.

Alex Nolasco
fuente
0

también puedes probar esto

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();
G Naga Subrahmanyam
fuente
0
Un patrón que me sirve bien
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Primero, puede cambiar su preferencia de agregar métodos a la instancia en lugar del prototypeobjeto del constructor . Casi siempre declaro métodos dentro del constructor porque uso el secuestro de constructores muy a menudo para propósitos de herencia y decoradores.

Así es como decido dónde se escriben las declaraciones:

  • Nunca declare un método directamente en el objeto de contexto ( this)
  • Dejar que las vardeclaraciones tengan prioridad sobre las functiondeclaraciones
  • Deje que las primitivas tengan prioridad sobre los objetos ( {}y [])
  • Dejar que las publicdeclaraciones tengan prioridad sobre las privatedeclaraciones
  • Prefiero Function.prototype.bindmás thus, self, vm,etc
  • Evite declarar una Clase dentro de otra Clase, a menos que:
    • Debería ser obvio que los dos son inseparables
    • La clase interna implementa el patrón de comando
    • La clase interna implementa el patrón Singleton
    • La clase interna implementa el patrón de estado
    • La clase interna implementa otro patrón de diseño que garantiza esto
  • Siempre regrese thisdesde el alcance léxico del espacio de cierre.

He aquí por qué estos ayudan:

Secuestro de constructores
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Diseño del modelo
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Patrones de diseño
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Como puede ver, realmente no lo necesito thusya que prefiero Function.prototype.bind( .callo .apply) sobrethus . En nuestra Singletonclase, ni siquiera lo nombramos thusporque INSTANCEtransmite más información. Para Model, regresamos thispara poder invocar al Constructor usando .callpara devolver la instancia que le pasamos. Redundantemente, lo asignamos a la variable updated, aunque es útil en otros escenarios.

Además, prefiero construir literales de objeto usando la newpalabra clave sobre {corchetes}:

Privilegiado
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
No preferido
var klass = Super.apply({
    override: x
});

Como puede ver, este último no tiene la capacidad de anular la propiedad de "anulación" de su Superclase.

Si agrego métodos al prototypeobjeto de la Clase , prefiero un literal de objeto, con o sin usar la newpalabra clave:

Privilegiado
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
No preferido
Klass.prototype.method = function m() {...};
Cody
fuente
0

Me gustaría mencionar que podemos usar un Título o una Cadena para declarar un Objeto.
Hay diferentes formas de llamar a cada tipo de ellos. Vea abajo:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));

Chetabahana
fuente
-1

Básicamente, no hay un concepto de clase en JS, por lo que utilizamos la función como un constructor de clase que es relevante con los patrones de diseño existentes.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Hasta ahora, JS no tiene idea de que desea crear un objeto, así que aquí viene la nueva palabra clave.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: JS profesional para desarrolladores web - Nik Z

Airwind711
fuente
Se acepta el voto negativo: con un motivo válido habría sido más informativo y habría servido para mejorar.
Airwind711
No es el concepto de una classen JS, como usted mencionó en su encabezado utilizando la functionpalabra clave. Es no un patrón de diseño, pero una característica intencional de la lengua. No te rechacé por esto, pero parece que alguien más lo hizo por la brevedad y la casi irrelevancia de la pregunta. Espero que este comentario ayude.
Cody