Getters \ setters para tontos

133

He estado tratando de entender los getters y setters y no se está hundiendo. He leído JavaScript Getters and Setters y Defining Getters and Setters y simplemente no lo .

¿Alguien puede decir claramente:

  1. Qué deben hacer un captador y un colocador, y
  2. ¿Da algunos ejemplos MUY simples?
Alexander Abakumov
fuente
12
En lo personal, no sé cómo se puede obtener una explicación más clara que el de Juan ...
Jason Bunting
En el mundo OO, getter y setter ayuda escribir una clase que esté debidamente encapsulado
overexchange
Básicamente lo veo así: estás definiendo sobrecargas para obtener y configurar la propiedad, y estas sobrecargas son funciones; pero no tienes que llamarlos. De esta manera se puede reemplazar a = setValue(5);con a = 5;y setValue()por ello se pondría bajo el capó para hacer lo que quiera.
Andrew

Respuestas:

102

Además de la respuesta de @ millimoose , los setters también se pueden usar para actualizar otros valores.

function Name(first, last) {
    this.first = first;
    this.last = last;
}

Name.prototype = {
    get fullName() {
        return this.first + " " + this.last;
    },

    set fullName(name) {
        var names = name.split(" ");
        this.first = names[0];
        this.last = names[1];
    }
};

Ahora, se puede establecer fullName, y first, y lastse actualizará y viceversa.

n = new Name('Claude', 'Monet')
n.first # "Claude"
n.last # "Monet"
n.fullName # "Claude Monet"
n.fullName = "Gustav Klimt"
n.first # "Gustav"
n.last # "Klimt"
Matthew Crumley
fuente
2
@Akash: No, aunque Internet Explorer 9 admite la Object.definePropertyfunción más nueva que puede definir captadores y definidores.
Matthew Crumley
9
¿No es realmente doloroso que MS no sea compatible con JS correctamente y no hagan que su Silverlight funcione en todas partes, por lo que tengo que programar todo dos veces, una para SL y otra para el resto del mundo :)
Akash Kava
2
@ Martin: Puede hacerlos privados utilizando la misma técnica que en la respuesta de John . Si desea usar getters / setters reales, debería usar this.__defineGetter__la Object.definePropertyfunción más nueva .
Matthew Crumley el
1
Solo un problema con el enfoque enumerado anteriormente, si desea agregar captadores y establecedores para la clase ya existente, anulará los prototipos y los métodos originales no serán accesibles.
xchg.ca
1
¿No se sobrescribe este enfoque Name.prototype.constructor? Parece una mala alternativa a la respuesta de millimoose .
r0estir0bbe
62

Getters y Setters en JavaScript

Visión general

Los captadores y establecedores en JavaScript se utilizan para definir propiedades computadas o accesores . Una propiedad calculada es aquella que usa una función para obtener o establecer un valor de objeto. La teoría básica es hacer algo como esto:

var user = { /* ... object with getters and setters ... */ };
user.phone = '+1 (123) 456-7890'; // updates a database
console.log( user.areaCode ); // displays '123'
console.log( user.area ); // displays 'Anytown, USA'

Esto es útil para hacer cosas automáticamente detrás de escena cuando se accede a una propiedad, como mantener los números dentro del rango, formatear cadenas, desencadenar eventos de cambio de valor, actualizar datos relacionales, proporcionar acceso a propiedades privadas y más.

Los siguientes ejemplos muestran la sintaxis básica, aunque simplemente obtienen y establecen el valor del objeto interno sin hacer nada especial. En casos reales, modificaría el valor de entrada y / o salida para satisfacer sus necesidades, como se indicó anteriormente.

obtener / establecer palabras clave

ECMAScript 5 admite gety setpalabras clave para definir propiedades calculadas. Funcionan con todos los navegadores modernos, excepto IE 8 y versiones inferiores.

var foo = {
    bar : 123,
    get bar(){ return bar; },
    set bar( value ){ this.bar = value; }
};
foo.bar = 456;
var gaz = foo.bar;

Getters y Setters personalizados

gety setno son palabras reservadas, por lo que se pueden sobrecargar para crear sus propias funciones de propiedad computarizadas de navegador cruzado. Esto funcionará en cualquier navegador.

var foo = {
    _bar : 123,
    get : function( name ){ return this[ '_' + name ]; },
    set : function( name, value ){ this[ '_' + name ] = value; }
};
foo.set( 'bar', 456 );
var gaz = foo.get( 'bar' );

O para un enfoque más compacto, se puede usar una sola función.

var foo = {
    _bar : 123,
    value : function( name /*, value */ ){
        if( arguments.length < 2 ){ return this[ '_' + name ]; }
        this[ '_' + name ] = value;
    }
};
foo.value( 'bar', 456 );
var gaz = foo.value( 'bar' );

Evite hacer algo como esto, lo que puede conducir a la acumulación de código.

var foo = {
    _a : 123, _b : 456, _c : 789,
    getA : function(){ return this._a; },
    getB : ..., getC : ..., setA : ..., setB : ..., setC : ...
};

Para los ejemplos anteriores, los nombres de las propiedades internas se resumen con un guión bajo para disuadir a los usuarios de simplemente hacer foo.barvs. foo.get( 'bar' )y obtener un valor "sin cocinar". Puede usar código condicional para hacer cosas diferentes según el nombre de la propiedad a la que se accede (a través del nameparámetro).

Object.defineProperty ()

Usar Object.defineProperty()es otra forma de agregar captadores y establecedores, y se puede usar en objetos después de que se hayan definido. También se puede usar para establecer comportamientos configurables y enumerables. Esta sintaxis también funciona con IE 8, pero desafortunadamente solo en objetos DOM.

var foo = { _bar : 123 };
Object.defineProperty( foo, 'bar', {
    get : function(){ return this._bar; },
    set : function( value ){ this._bar = value; }
} );
foo.bar = 456;
var gaz = foo.bar;

__defineGetter __ ()

Finalmente, __defineGetter__()es otra opción. Está en desuso, pero todavía se usa ampliamente en la web y, por lo tanto, es poco probable que desaparezca pronto. Funciona en todos los navegadores, excepto IE 10 y versiones inferiores. Aunque las otras opciones también funcionan bien en no IE, entonces esta no es tan útil.

var foo = { _bar : 123; }
foo.__defineGetter__( 'bar', function(){ return this._bar; } );
foo.__defineSetter__( 'bar', function( value ){ this._bar = value; } );

También vale la pena señalar que en los últimos ejemplos, los nombres internos deben ser diferentes a los nombres de acceso para evitar la recurrencia (es decir, foo.barllamar foo.get(bar)llamar foo.barllamar llamar foo.get(bar)...).

Ver también

MDN get , set , Object.defineProperty () , __defineGetter __ () , __defineSetter __ ()
MSDN IE8 Getter Support

Beejor
fuente
1
En el enfoque más compacto , this[ '_' + name ] = value;podría haber this[ '_' + name ] = arguments[1];y no habría necesidad de especificar valuearg.
Redhart
1
El ejemplo: var foo = { bar : 123, get bar(){ return bar; }, set bar( value ){ this.bar = value; } }; foo.bar = 456; genera una excepción: RangeError no capturado: el tamaño máximo de la pila de llamadas excedido en Object.set bar [como bar] (<anónimo>: 4: 32) en Object.set bar [como barra] (<anónimo>: 4: 32 ) en Object.set bar [como barra] (<anónimo>: 4: 32) en Object.set barra [como barra] (<anónimo>: 4: 32) en Object.set barra [como barra] (<anónimo> : 4: 32) en Object.set bar [como bar] (<anónimo>: 4: 32)
nevf
1
El nombre set / get debe ser diferente al nombre de la propiedad. Entonces, en lugar de bar: 123y this.bar = valueetc., cámbielos a, _bar por ejemplo. Ver: hongkiat.com/blog/getters-setters-javascript
nevf
@nevf ¡Gracias por la corrección! Sí, normalmente con propiedades calculadas, la interna "real" se denomina como _fooo mFoo. Si es lo mismo que getter / setter, causará un bucle infinito debido a la recursión y luego un Stack Overflow ™ ;-) porque cuando dices a = b, llama a a.get (b) que a su vez llama a = b , que llama a.get (b), ...
Beejor
58

Los usaría, por ejemplo, para implementar propiedades calculadas.

Por ejemplo:

function Circle(radius) {
    this.radius = radius;
}

Object.defineProperty(Circle.prototype, 'circumference', {
    get: function() { return 2*Math.PI*this.radius; }
});

Object.defineProperty(Circle.prototype, 'area', {
    get: function() { return Math.PI*this.radius*this.radius; }
});

c = new Circle(10);
console.log(c.area); // Should output 314.159
console.log(c.circumference); // Should output 62.832

(CodePen)

milimoose
fuente
Ok, creo que estoy empezando a entenderlo. Estoy tratando de asignar un captador a la propiedad de longitud de un objeto de matriz pero obtengo un error: "Redeclaración de longitud var" Y el código se ve así: obj = []; obj .__ defineGetter __ ('length', function () {return this.length;});
1
Esto se debe a que los objetos de matriz ya tienen una propiedad de longitud incorporada. Si se permitiera la redeclaración, llamar a la nueva duración se repetiría infinitamente. Intente llamar a la propiedad "my_length" o algo así.
millimoose
Para definir ambos captadores en una declaración, use Object.defineProperties.
r0estir0bbe
¿No puedes simplemente hacer {"area": ​​function () {return ...}}? simplemente asignarlo como una propiedad de objeto
RegarBoy
@developer Eso no es un getter de Javascript como lo define el lenguaje, es solo una función. Debe llamarlo para obtener el valor, no sobrecarga el acceso a la propiedad. También hay un círculo especial del infierno reservado para las personas que inventan sus propios sistemas de objetos rotos en JS en lugar de construir sobre el que ya tiene.
millimoose
16

Lamento resucitar una vieja pregunta, pero pensé que podría aportar un par de ejemplos muy básicos y explicaciones para tontos. Ninguna de las otras respuestas publicadas hasta ahora ilustra una sintaxis como el primer ejemplo de la guía MDN , que es lo más básico posible.

Adquiridor:

var settings = {
    firstname: 'John',
    lastname: 'Smith',
    get fullname() { return this.firstname + ' ' + this.lastname; }
};

console.log(settings.fullname);

... iniciará sesión John Smith, por supuesto. Un captador se comporta como una propiedad de objeto variable, pero ofrece la flexibilidad de una función para calcular su valor devuelto sobre la marcha. Básicamente es una forma elegante de crear una función que no requiere () al llamar.

Setter:

var address = {
    set raw(what) {
        var loc = what.split(/\s*;\s*/),
        area = loc[1].split(/,?\s+(\w{2})\s+(?=\d{5})/);

        this.street = loc[0];
        this.city = area[0];
        this.state = area[1];
        this.zip = area[2];
    }
};

address.raw = '123 Lexington Ave; New York NY  10001';
console.log(address.city);

... iniciará sesión New Yorken la consola. Al igual que los getters, los setters se llaman con la misma sintaxis que establecer el valor de una propiedad de objeto, pero son otra forma elegante de llamar a una función sin ().

Vea este jsfiddle para un ejemplo más completo, quizás más práctico. Pasar valores al setter del objeto desencadena la creación o población de otros elementos del objeto. Específicamente, en el ejemplo de jsfiddle, al pasar una matriz de números se le pide al colocador que calcule la media, la mediana, la moda y el rango; luego establece las propiedades del objeto para cada resultado.

rojo
fuente
Todavía no entiendo el beneficio de usar get and set vs crear getMethod o setMethod. ¿Es el único beneficio que puede llamar sin ()? Debe haber otra razón por la que se agrega a JavaScript.
Andreas
@Andreas Getters y setters se comportan como propiedades cuando se les llama, lo que puede ayudar a articular su propósito. No desbloquean habilidades que de otra manera faltarían, pero su uso puede ayudarte a organizar tus pensamientos. Ese es el beneficio real. Como ejemplo práctico, solía usar un captador para extender un objeto de Google Maps. Necesitaba calcular el ángulo de giro de la cámara para poder rotar los mosaicos del mapa hasta el horizonte. Google hace esto automáticamente en el back-end ahora; pero en ese momento fue útil para mí recuperarlo maps.rollcomo una propiedad en lugar del valor maps.roll()de retorno. Es solo una preferencia.
rojo
así que es solo azúcar sintáctico para que el código se vea más limpio sin el () No puedo ver por qué no se podía su ejemplo, conmaps.roll()
Andreas
@Andreas ¿Quién dice que no pude? Como digo, es solo una forma de ayudarme a mantener mis pensamientos organizados. La codificación es un arte. No le preguntas a Bob Ross por qué tuvo que usar ocre quemado cuando podría haber usado naranja. Es posible que no vea una necesidad ahora, pero un día cuando decida que su pintura necesita un poco de ocre quemado, estará en su paleta.
rojo
:) Una cosa que veo que hace y establece la sintaxis es que se ejecute automáticamente si se usa como una propiedad de una propiedad.
Andreas
11

Getters y setters realmente solo tienen sentido cuando tienes propiedades privadas de clases. Dado que Javascript realmente no tiene propiedades de clase privada como normalmente pensaría en lenguajes orientados a objetos, puede ser difícil de entender. Aquí hay un ejemplo de un objeto contador privado. Lo bueno de este objeto es que no se puede acceder a la variable interna "cuenta" desde fuera del objeto.

var counter = function() {
    var count = 0;

    this.inc = function() {
        count++;
    };

    this.getCount = function() {
        return count;
    };
};

var i = new Counter();
i.inc();
i.inc();
// writes "2" to the document
document.write( i.getCount());

Si todavía está confundido, eche un vistazo al artículo de Crockford sobre Miembros privados en Javascript .

Juan
fuente
39
Estoy en desacuerdo. Los captadores y establecedores también son muy útiles para encapsular información cuya definición puede no ser solo una variable simple. Puede ser útil si necesita cambiar el comportamiento de un sistema que anteriormente utilizaba propiedades simples y de las cuales pueden depender otras cosas. Además, su ejemplo solo muestra "pseudo captadores" que son solo funciones. Los captadores reales de JavaScript aparecen como valores simples (y se accede a ellos sin notación de función), de ahí su verdadero poder.
devios1
No estoy seguro si llamaría a eso poderoso. Algo que aparece como X pero es realmente Y no es necesariamente claro. Absolutamente NO esperaría var baz = foo.bartener un conjunto completo de comportamiento oculto detrás de él. Me gustaría esperar que de foo.getBar(), sin embargo.
AgmLauncher
8

Creo que el primer artículo al que enlaza lo dice con bastante claridad:

La ventaja obvia de escribir JavaScript de esta manera es que puede usar valores oscuros a los que no desea que el usuario acceda directamente.

El objetivo aquí es para encapsular y abstraer los campos permitiendo sólo el acceso a ellos a través de una get()o set()método. De esta manera, puede almacenar el campo / datos internamente de la forma que desee, pero los componentes externos solo están lejos de su interfaz publicada. Esto le permite realizar cambios internos sin cambiar las interfaces externas, hacer alguna validación o verificación de errores dentro del set()método, etc.

mate b
fuente
6

Aunque a menudo estamos acostumbrados a ver objetos con propiedades públicas sin ningún control de acceso, JavaScript nos permite describir propiedades con precisión. De hecho, podemos usar descriptores para controlar cómo se puede acceder a una propiedad y qué lógica podemos aplicarle. Considere el siguiente ejemplo:

var employee = {
    first: "Boris",
    last: "Sergeev",
    get fullName() {
        return this.first + " " + this.last;
    },
    set fullName(value) {
        var parts = value.toString().split(" ");
        this.first = parts[0] || "";
        this.last = parts[1] || "";
    },
    email: "[email protected]"
};

El resultado final:

console.log(employee.fullName); //Boris Sergeev
employee.fullName = "Alex Makarenko";

console.log(employee.first);//Alex
console.log(employee.last);//Makarenko
console.log(employee.fullName);//Alex Makarenko
Michael Horojanski
fuente
2

Lo que es tan confuso al respecto ... los getters son funciones que se llaman cuando obtienes una propiedad, setters, cuando la configuras. ejemplo, si lo haces

obj.prop = "abc";

Estás configurando la propiedad prop, si estás usando getters / setters, entonces se llamará a la función setter, con "abc" como argumento. La definición de la función setter dentro del objeto idealmente se vería así:

set prop(var) {
   // do stuff with var...
}

No estoy seguro de qué tan bien se implementa en todos los navegadores. Parece que Firefox también tiene una sintaxis alternativa, con métodos especiales ("mágicos") con doble subrayado. Como de costumbre, Internet Explorer no admite nada de esto.

Rolf
fuente
2

Puede definir el método de instancia para la clase js, a través del prototipo del constructor.

El siguiente es el código de muestra:

// BaseClass

var BaseClass = function(name) {
    // instance property
    this.name = name;
};

// instance method
BaseClass.prototype.getName = function() {
    return this.name;
};
BaseClass.prototype.setName = function(name) {
    return this.name = name;
};


// test - start
function test() {
    var b1 = new BaseClass("b1");
    var b2 = new BaseClass("b2");
    console.log(b1.getName());
    console.log(b2.getName());

    b1.setName("b1_new");
    console.log(b1.getName());
    console.log(b2.getName());
}

test();
// test - end

Y, esto debería funcionar para cualquier navegador, también puede simplemente usar nodejs para ejecutar este código.

Eric Wang
fuente
1
Esto solo está creando nuevos métodos getName y setName. ¡Estos no están relacionados con la creación de propiedades!
uzay95
2

También estaba algo confundido por la explicación que leí , porque estaba tratando de agregar una propiedad a un prototipo existente que no escribí, por lo que reemplazar el prototipo parecía un enfoque incorrecto. Entonces, para la posteridad, así es como agregué una lastpropiedad a Array:

Object.defineProperty(Array.prototype, "last", {
    get: function() { return this[this.length - 1] }
});

Muy ligeramente mejor que agregar una función en mi humilde opinión.

shawkinaw
fuente
1

Si se refiere al concepto de accesores, entonces el objetivo simple es ocultar el almacenamiento subyacente de la manipulación arbitraria. El mecanismo más extremo para esto es

function Foo(someValue) {
    this.getValue = function() { return someValue; }
    return this;
}

var myFoo = new Foo(5);
/* We can read someValue through getValue(), but there is no mechanism
 * to modify it -- hurrah, we have achieved encapsulation!
 */
myFoo.getValue();

Si te estás refiriendo a la característica real de get / setter de JS, por ejemplo. defineGetter/ defineSetter, o { get Foo() { /* code */ } }bien, vale la pena señalar que en la mayoría de los motores modernos el uso posterior de esas propiedades será mucho más lento de lo que sería de otra manera. p.ej. comparar el rendimiento de

var a = { getValue: function(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.getValue();

vs.

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;
olliej
fuente
-1

Tengo uno para ustedes, que puede ser un poco feo, pero se puede hacer en todas las plataformas.

function myFunc () {

var _myAttribute = "default";

this.myAttribute = function() {
    if (arguments.length > 0) _myAttribute = arguments[0];
    return _myAttribute;
}
}

de esta manera, cuando llamas

var test = new myFunc();
test.myAttribute(); //-> "default"
test.myAttribute("ok"); //-> "ok"
test.myAttribute(); //-> "ok"

Si realmente quieres condimentar las cosas ... puedes insertar un tipo de verificación:

if (arguments.length > 0 && typeof arguments[0] == "boolean") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "number") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "string") _myAttribute = arguments[0];

o vuélvase aún más loco con el código avanzado typeof check: type.of () en codingforums.com

artsy.ca
fuente
el punto era poder cambiar un atributo a algo más elegante sin necesidad de cambiar la interfaz pública. Agregar una etiqueta de llamada () lo cambia.
Michael Scott Cuthbert