Javascript: ¿Necesito poner this.var para cada variable en un objeto?

Resuelto Chaosed0 asked hace 11 años • 6 respuestas

En C++, el lenguaje con el que me siento más cómodo, normalmente se declara un objeto como este:

class foo
{
public:
    int bar;
    int getBar() { return bar; }
}

Las llamadas getBar()funcionan bien (ignorando el hecho de que barpodrían no estar inicializadas). La variable barinterna getBar()está en el alcance de la clase foo, por lo que no necesito decirla this->bara menos que realmente necesite dejar en claro que me refiero a la clase baren lugar de, digamos, un parámetro.

Ahora, estoy intentando comenzar con la programación orientada a objetos en Javascript. Entonces, busco cómo definir clases e intento el mismo tipo de cosas:

function foo()
{
     this.bar = 0;
     this.getBar = function() { return bar; }
}

Y me da bar is undefined. Cambiar barto this.barsoluciona el problema, pero hacerlo para cada variable desordena bastante mi código. ¿Es esto necesario para cada variable? Como no encuentro ninguna pregunta relacionada con esto, me hace sentir como si estuviera haciendo algo fundamentalmente incorrecto.


EDITAR: Bien, entonces, de los comentarios lo que obtengo es que this.bar, una propiedad de un objeto, hace referencia a algo diferente a baruna variable local. ¿Alguien puede decir exactamente por qué es así, en términos de alcance y objetos, y si hay otra forma de definir un objeto donde esto no sea necesario?

Chaosed0 avatar Nov 16 '12 21:11 Chaosed0
Aceptado

JavaScript no tiene clases modelo de objetos basado en clases. Utiliza la herencia prototípica más poderosa, que puede imitar clases, pero no es adecuada para ello. Todo es un objeto y los objetos [pueden] heredar de otros objetos.

Un constructor es simplemente una función que asigna propiedades a objetos recién creados. Se puede hacer referencia al objeto (creado mediante una llamada con la newpalabra clave ) a través de la thispalabra clave (que es local de la función).

Un método también es simplemente una función que se llama en un objeto, nuevamente thisapuntando al objeto. Al menos cuando esa función se invoca como propiedad del objeto, utilizando un operador miembro (punto, corchetes). Esto causa mucha confusión a los novatos, porque si pasa esa función (por ejemplo, a un detector de eventos) se "separa" del objeto al que se accedió.

¿Dónde está ahora la herencia? Las instancias de una "clase" heredan del mismo objeto prototipo. Los métodos se definen como propiedades de función en ese objeto (en lugar de una función para cada instancia), la instancia en la que los llama simplemente hereda esa propiedad.

Ejemplo:

function Foo() {
    this.bar = "foo"; // creating a property on the instance
}
Foo.prototype.foo = 0; // of course you also can define other values to inherit
Foo.prototype.getBar = function() {
    // quite useless
    return this.bar;
}

var foo = new Foo; // creates an object which inherits from Foo.prototype,
                   // applies the Foo constructor on it and assigns it to the var
foo.getBar(); // "foo" - the inherited function is applied on the object and
              // returns its "bar" property
foo.bar; // "foo" - we could have done this easier.
foo[foo.bar]; // 0 - access the "foo" property, which is inherited
foo.foo = 1;  // and now overwrite it by creating an own property of foo
foo[foo.getBar()]; // 1 - gets the overwritten property value. Notice that
(new Foo).foo;     // is still 0

Entonces, solo usamos propiedades de ese objeto y estamos contentos con él. ¡Pero todos ellos son "públicos" y pueden sobrescribirse/cambiarse/eliminarse! Si eso no te importa, estás de suerte. Puede indicar la "privacidad" de las propiedades anteponiendo sus nombres con guiones bajos, pero eso es sólo una pista para otros desarrolladores y es posible que no se obedezca (especialmente en caso de error).

Entonces, mentes inteligentes han encontrado una solución que utiliza la función constructora como cierre, permitiendo la creación de "atributos" privados. Cada ejecución de una función de JavaScript crea un nuevo entorno de variables para las variables locales, que pueden recolectar basura una vez finalizada la ejecución. Cada función que se declara dentro de ese alcance también tiene acceso a estas variables, y mientras esas funciones puedan ser llamadas (por ejemplo, por un detector de eventos), el entorno debe persistir. Entonces, al exportar funciones definidas localmente desde su constructor, conserva ese entorno variable con variables locales a las que solo pueden acceder estas funciones.

Veámoslo en acción:

function Foo() {
    var bar = "foo"; // a local variable
    this.getBar = function getter() {
        return bar; // accesses the local variable
    }; // the assignment to a property makes it available to outside
}

var foo = new Foo; // an object with one method, inheriting from a [currently] empty prototype
foo.getBar(); // "foo" - receives us the value of the "bar" variable in the constructor

Esta función getter, que se define dentro del constructor, ahora se denomina " método privilegiado ", ya que tiene acceso a los "atributos" (variables) "privados" (locales). El valor de barnunca cambiará. También podrías declarar una función de establecimiento para ello, por supuesto, y con eso podrías agregar alguna validación, etc.

Observe que los métodos del objeto prototipo no tienen acceso a las variables locales del constructor, pero pueden usar los métodos privilegiados. Agreguemos uno:

Foo.prototype.getFooBar = function() {
    return this.getBar() + "bar"; // access the "getBar" function on "this" instance
}
// the inheritance is dynamic, so we can use it on our existing foo object
foo.getFooBar(); // "foobar" - concatenated the "bar" value with a custom suffix

Entonces, puedes combinar ambos enfoques. Tenga en cuenta que los métodos privilegiados necesitan más memoria, ya que crea objetos de función distintos con diferentes cadenas de alcance (pero el mismo código). Si va a crear cantidades increíblemente grandes de instancias, debe definir métodos solo en el prototipo.

Se vuelve incluso un poco más complicado cuando configuras la herencia de una "clase" a otra: básicamente tienes que hacer que el objeto prototipo hijo herede del padre y aplicar el constructor padre en las instancias hijas para crear los "atributos privados". ". Eche un vistazo a Corregir herencia de JavaScript , Variables privadas en prototipos heredados , Definir miembros de campos privados y herencia en el patrón del módulo JAVASCRIPT y ¿Cómo implementar la herencia en el patrón de prototipo JS Revealing?

Bergi avatar Nov 16 '2012 14:11 Bergi

Decir explícitamente this.foosignifica (como ha entendido bien) que está interesado en la propiedad foodel objeto actual al que hace referencia this. Entonces, si usa: this.foo = 'bar';establecerá la propiedad foodel objeto actual al que hace referencia es thisigual a bar.

La thispalabra clave en JavaScript no siempre significa lo mismo que en C++. Aquí te puedo dar un ejemplo:

function Person(name) {
   this.name = name;
   console.log(this); //Developer {language: "js", name: "foo"} if called by Developer
}

function Developer(name, language) {
   this.language = language;
   Person.call(this, name);
}

var dev = new Developer('foo', 'js');

En el ejemplo anterior, llamamos a la función Personcon el contexto de la función Developer, por lo thisque hacemos referencia al objeto que será creado por Developer. Como puede ver en el console.logresultado, thisproviene de Developer. Con el primer argumento del método callespecificamos el contexto con el que será llamada la función.

Si no usa thissimplemente la propiedad que ha creado será una variable local. Como sabrás, JavaScript tiene alcance funcional, por eso la variable será local, visible solo para la función donde está declarada (y, por supuesto, todas sus funciones secundarias que se declaran dentro de la función principal). Aquí hay un ejemplo:

function foo() {
    var bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(f.getBar());  //'foobar'

Esto es cierto cuando utiliza la varpalabra clave. Esto significa que la estás definiendo barcomo variable local, si la olvidas, varlamentablemente barse volverá global.

function foo() {
    bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(window.bar);  //'foobar'

Exactamente el alcance local puede ayudarle a lograr privacidad y encapsulación, que son uno de los mayores beneficios de la programación orientada a objetos.

Ejemplo del mundo real:

function ShoppingCart() {
    var items = [];

    this.getPrice = function () {
       var total = 0;
       for (var i = 0; i < items.length; i += 1) {
          total += items[i].price;
       }
       return total;
    }

    this.addItem = function (item) {
        items.push(item);
    }

    this.checkOut = function () {
        var serializedItems = JSON.strigify(items);
        //send request to the server...
    }
}

var cart = new ShoppingCart();
cart.addItem({ price: 10, type: 'T-shirt' });
cart.addItem({ price: 20, type: 'Pants' });
console.log(cart.getPrice()); //30

Un ejemplo más de los beneficios del alcance de JavaScript es el patrón de módulo . En Module Pattern puede simular la privacidad utilizando el alcance funcional local de JavaScript. Con este enfoque puede tener propiedades y métodos privados. Aquí hay un ejemplo:

var module = (function {

    var privateProperty = 42;

    function privateMethod() {
        console.log('I\'m private');
    }
    return {

       publicMethod: function () {
           console.log('I\'m public!');
           console.log('I\'ll call a private method!');
           privateMethod();
       },

       publicProperty: 1.68,

       getPrivateProperty: function () {
           return privateProperty;
       },

       usePublicProperty: function () {
           console.log('I\'ll get a public property...' + this.publicProperty);
       }

    }
}());

module.privateMethod(); //TypeError
module.publicProperty(); //1.68
module.usePublicProperty(); //I'll get a public property...1.68
module.getPrivateProperty(); //42
module.publicMethod(); 
/*
 * I'm public!
 * I'll call a private method!
 * I'm private
 */

Hay una sintaxis un poco extraña con el parentless que envuelve las funciones anónimas, pero olvídalo por el momento (solo está ejecutando la función después de que se inicializa). La funcionalidad se puede ver en el ejemplo de uso, pero los beneficios están relacionados principalmente con proporcionar una interfaz pública simple que no lo involucra con todos los detalles de implementación. Para una explicación más detallada del patrón puedes ver el enlace que he puesto arriba.


Espero que con this:-) información te haya ayudado a comprender algunos temas básicos de JavaScript.

Minko Gechev avatar Nov 16 '2012 15:11 Minko Gechev
function Foo() {
  this.bar = 0;
  this.getBar = function () { return this.bar };
}

Cuando llamas a la función anterior con la newpalabra clave, así...

var foo = new Foo();

... - suceden algunas cosas:

1) se crea un objeto
2) la función se ejecuta con la thispalabra clave que hace referencia a ese objeto.
3) ese objeto se devuelve.

foo, entonces, se convierte en este objeto:

{
    bar: 0,
    getBar: function () { return this.bar; }
};

Entonces, ¿por qué no hacer esto?

var foo = {
    bar: 0,
    getBar: function () { return this.bar; }
};

Lo harías, si fuera sólo ese objeto simple.

Pero crear un objeto con un constructor (así se llama) nos da una gran ventaja al crear múltiples objetos "iguales".

Mira, en javascript, todas las funciones se crean con una propiedad prototipo [un objeto], y todos los objetos creados con esa función (llamándola con la nueva palabra clave) están vinculados a ese objeto prototipo. Por eso es tan genial: puedes almacenar todos los métodos comunes (y propiedades, si así lo deseas) en el objeto prototipo y ahorrar mucha memoria. Así es como funciona:

function Foo( bar, bob ) {
   this.bar = bar;
   this.bob = bob;
}

Foo.prototype.calculate = function () {
  // 'this' points not to the 'prototype' object 
  // as you could've expect, but to the objects
  // created by calling Foo with the new keyword.
  // This is what makes it work.
  return this.bar - this.bob;  
};

var foo1 = new Foo(9, 5);
var foo2 = new Foo(13, 3);
var result1 = foo1.calculate();
var result2 = foo2.calculate();

console.log(result1); //logs 4
console.log(result2); //logs 10

¡Eso es todo!

Paulo R. avatar Nov 16 '2012 15:11 Paulo R.

Para acercarse a la programación orientada a objetos en JavaScript, es posible que desee echar un vistazo a un patrón de diseño de módulo (por ejemplo, descrito aquí ).

Basado en el efecto de cierre, este patrón permite emular propiedades privadas en sus objetos.

Con las propiedades 'privadas' puede hacer referencia a ellas directamente mediante su identificador (es decir, sin thispalabra clave como en los constructores).

Pero de todos modos, los cierres y patrones de diseño en JS son un tema avanzado. Entonces, familiarízate con los conceptos básicos (también explicados en el libro mencionado anteriormente).

John Doe avatar Nov 16 '2012 15:11 John Doe