¿Cómo extender la función con las clases de ES6?


105

ES6 permite ampliar objetos especiales. Entonces es posible heredar de la función. Dicho objeto se puede llamar como una función, pero ¿cómo puedo implementar la lógica para dicha llamada?

class Smth extends Function {
  constructor (x) {
    // What should be done here
    super();
  }
}

(new Smth(256))() // to get 256 at this call?

Cualquier método de clase obtiene referencia a la instancia de clase a través de this. Pero cuando se llama como función, se thisrefiere a window. ¿Cómo puedo obtener la referencia a la instancia de la clase cuando se la llama como función?

PD: Misma pregunta en ruso.


17
Ah, finalmente alguien hizo esta pregunta :-)
Bergi

1
¿Simplemente hacer super(x)(es decir, pasarlo a Function)? Sin Functionembargo, no estoy seguro de si realmente se puede extender.
Felix Kling

Tenga en cuenta que todavía existen problemas con la extensión de las clases integradas. La especificación sugiere que debería ser posible, pero me he encontrado con problemas de extensión Error, entre otros.
ssube

1
Tenga en cuenta que Functiones simplemente un constructor de funciones. La implementación de la función debe pasarse al constructor. Si no desea Smthaceptar una implementación, debe proporcionarla en el constructor, es decir super('function implementation here').
Felix Kling

1
@Qwertiy: Yo diría que esta es la excepción , no el caso general. Esto también es muy específico para las expresiones de función , pero está utilizando el Functionconstructor (tiempo de ejecución) que es muy diferente de una expresión de función (sintaxis).
Felix Kling

Respuestas:


49

La superllamada invocará al Functionconstructor, que espera una cadena de código. Si desea acceder a los datos de su instancia, simplemente puede codificarlos:

class Smth extends Function {
  constructor(x) {
    super("return "+JSON.stringify(x)+";");
  }
}

pero eso no es realmente satisfactorio. Queremos usar un cierre.

Hacer que la función devuelta sea un cierre que pueda acceder a las variables de su instancia es posible, pero no fácil. Lo bueno es que no tiene que llamar supersi no lo desea; aún puede obtener returnobjetos arbitrarios de los constructores de su clase ES6. En este caso, haríamos

class Smth extends Function {
  constructor(x) {
    // refer to `smth` instead of `this`
    function smth() { return x; };
    Object.setPrototypeOf(smth, Smth.prototype);
    return smth;
  }
}

Pero podemos hacerlo aún mejor y abstraer esto de Smth:

class ExtensibleFunction extends Function {
  constructor(f) {
    return Object.setPrototypeOf(f, new.target.prototype);
  }
}

class Smth extends ExtensibleFunction {
  constructor(x) {
    super(function() { return x; }); // closure
    // console.log(this); // function() { return x; }
    // console.log(this.prototype); // {constructor: …}
  }
}
class Anth extends ExtensibleFunction {
  constructor(x) {
    super(() => { return this.x; }); // arrow function, no prototype object created
    this.x = x;
  }
}
class Evth extends ExtensibleFunction {
  constructor(x) {
    super(function f() { return f.x; }); // named function
    this.x = x;
  }
}

Es cierto que esto crea un nivel adicional de indirección en la cadena de herencia, pero eso no es necesariamente algo malo (puede extenderlo en lugar del nativo Function). Si quieres evitarlo, usa

function ExtensibleFunction(f) {
  return Object.setPrototypeOf(f, new.target.prototype);
}
ExtensibleFunction.prototype = Function.prototype;

pero Smthtenga en cuenta que no heredará dinámicamente Functionpropiedades estáticas .


Quiero obtener acceso al estado de la clase desde la función.
Qwertiy

2
@Qwertiy: Entonces usa la segunda sugerencia de Bergi.
Felix Kling

@ AlexanderO'Mara: No puedes mutar el prototipo de la función si quieres que sean tus Smthinstancias instanceof Smth(como todos esperarían). Puede omitir la Object.setPrototypeOfllamada si no necesita este o ninguno de sus métodos prototipo declarado en su clase.
Bergi

@ AlexanderO'Mara: Tampoco Object.setPrototypeOfes un gran peligro de optimización siempre que se haga justo después de crear el objeto. Es solo que si mutas el [[prototipo]] de un objeto de un lado a otro durante su vida útil, será malo.
Bergi

1
@amn No, no lo hace, cuando no usa thisy returnun objeto.
Bergi

32

Este es un enfoque para crear objetos invocables que hagan referencia correctamente a sus miembros de objeto y mantengan la herencia correcta, sin interferir con los prototipos.

Simplemente:

class ExFunc extends Function {
  constructor() {
    super('...args', 'return this.__self__.__call__(...args)')
    var self = this.bind(this)
    this.__self__ = self
    return self
  }

  // Example `__call__` method.
  __call__(a, b, c) {
    return [a, b, c];
  }
}

Amplíe esta clase y agregue un __call__ método, más abajo ...

Una explicación en código y comentarios:

// This is an approach to creating callable objects
// that correctly reference their own object and object members,
// without messing with prototypes.

// A Class that extends Function so we can create
// objects that also behave like functions, i.e. callable objects.
class ExFunc extends Function {
  constructor() {
    super('...args', 'return this.__self__.__call__(...args)');
    // Here we create a function dynamically using `super`, which calls
    // the `Function` constructor which we are inheriting from. Our aim is to create
    // a `Function` object that, when called, will pass the call along to an internal
    // method `__call__`, to appear as though the object is callable. Our problem is
    // that the code inside our function can't find the `__call__` method, because it
    // has no reference to itself, the `this` object we just created.
    // The `this` reference inside a function is called its context. We need to give
    // our new `Function` object a `this` context of itself, so that it can access
    // the `__call__` method and any other properties/methods attached to it.
    // We can do this with `bind`:
    var self = this.bind(this);
    // We've wrapped our function object `this` in a bound function object, that
    // provides a fixed context to the function, in this case itself.
    this.__self__ = self;
    // Now we have a new wrinkle, our function has a context of our `this` object but
    // we are going to return the bound function from our constructor instead of the
    // original `this`, so that it is callable. But the bound function is a wrapper
    // around our original `this`, so anything we add to it won't be seen by the
    // code running inside our function. An easy fix is to add a reference to the
    // new `this` stored in `self` to the old `this` as `__self__`. Now our functions
    // context can find the bound version of itself by following `this.__self__`.
    self.person = 'Hank'
    return self;
  }
  
  // An example property to demonstrate member access.
  get venture() {
    return this.person;
  }
  
  // Override this method in subclasses of ExFunc to take whatever arguments
  // you want and perform whatever logic you like. It will be called whenever
  // you use the obj as a function.
  __call__(a, b, c) {
    return [this.venture, a, b, c];
  }
}

// A subclass of ExFunc with an overridden __call__ method.
class DaFunc extends ExFunc {
  constructor() {
    super()
    this.a = 'a1'
    this.b = 'b2'
    this.person = 'Dean'
  }

  ab() {
    return this.a + this.b
  }
  
  __call__(ans) {
    return [this.ab(), this.venture, ans];
  }
}

// Create objects from ExFunc and its subclass.
var callable1 = new ExFunc();
var callable2 = new DaFunc();

// Inheritance is correctly maintained.
console.log('\nInheritance maintained:');
console.log(callable2 instanceof Function);  // true
console.log(callable2 instanceof ExFunc);  // true
console.log(callable2 instanceof DaFunc);  // true

// Test ExFunc and its subclass objects by calling them like functions.
console.log('\nCallable objects:');
console.log( callable1(1, 2, 3) );  // [ 'Hank', 1, 2, 3 ]
console.log( callable2(42) );  // [ 'a1b2', Dean', 42 ]

// Test property and method access
console.log(callable2.a, callable2.b, callable2.ab())

Ver en repl.it

Más explicación de bind:

function.bind()funciona de manera muy similar function.call(), y comparten una firma de método similar:

fn.call(this, arg1, arg2, arg3, ...);más sobre mdn

fn.bind(this, arg1, arg2, arg3, ...);más sobre mdn

En ambos, el primer argumento redefine el thiscontexto dentro de la función. Los argumentos adicionales también se pueden vincular a un valor. Pero donde callinmediatamente llama a la función con los valores enlazados, binddevuelve un objeto de función "exótico" que envuelve de forma transparente el original, con thisy cualquier argumento preestablecido.

Entonces, cuando define una función, bindalgunos de sus argumentos:

var foo = function(a, b) {
  console.log(this);
  return a * b;
}

foo = foo.bind(['hello'], 2);

Llamas a la función vinculada solo con los argumentos restantes, su contexto está preestablecido, en este caso a ['hello'].

// We pass in arg `b` only because arg `a` is already set.
foo(2);  // returns 4, logs `['hello']`

¿Puede agregar una explicación de por qué bindfunciona (es decir, por qué devuelve una instancia de ExFunc)?
Bergi

@Bergi binddevuelve un objeto de función transparente que envuelve el objeto de función al que fue llamado, que es nuestro objeto invocable, solo con el thisrebote de contexto. Así que realmente devuelve una instancia de ExFunc. Publicación actualizada con más información sobre bind.
Adrien

1
@Bergi Todos los getters / setters y métodos son accesibles, las propiedades / atributos deben asignarse en el constructorafter bindin ExFunc. En las subclases de ExFunc, todos los miembros son accesibles. En cuanto a instanceof; en es6, las funciones vinculadas se denominan exóticas, por lo que su funcionamiento interno no es evidente, pero creo que pasa la llamada a su destino envuelto, a través de Symbol.hasInstance. Es muy parecido a un Proxy, pero es un método simple para lograr el efecto deseado. Su firma es similar, no la misma.
Adrien

1
@Adrien pero desde adentro __call__no puedo acceder this.ao this.ab(). por ejemplo, repl.it/repls/FelineFinishedDesktopenvironment
rob

1
@rob bien visto, hay un error de referencia, he actualizado la respuesta y el código con una solución y una nueva explicación.
Adrien

20

Puede envolver la instancia de Smth en un Proxy con una apply(y tal vez construct) trampa:

class Smth extends Function {
  constructor (x) {
    super();
    return new Proxy(this, {
      apply: function(target, thisArg, argumentsList) {
        return x;
      }
    });
  }
}
new Smth(256)(); // 256

Buena idea. Me gusta esto. ¿Debo implementar algo más de lógica en lugar de colocar dentro de aplicar?
Qwertiy

4
Un proxy incurriría en algunos gastos generales, ¿no? Además, thissigue siendo una función vacía (comprobar new Smth().toString()).
Bergi

2
@Bergi No tengo idea sobre el rendimiento. MDN tiene una gran advertencia roja en negrita setPrototypeOfacerca de los proxies y no dice nada sobre ellos. Pero supongo que los proxies pueden ser tan problemáticos como setPrototypeOf. Y aproximadamente toString, se puede sombrear con un método personalizado en Smth.prototype. El nativo depende de la implementación de todos modos.
Oriol

@Qwertiy Puede agregar una constructtrampa para especificar el comportamiento de new new Smth(256)(). Y agregue métodos personalizados que sigan a los nativos que acceden al código de una función, como toStringseñaló Bergi.
Oriol

¿Su applymétodo está implementado de la manera en que se supone que debe usarse, o es solo una demostración y necesito revisar más información Proxyy Reflectusarlo de manera adecuada?
Qwertiy

3

Seguí el consejo de la respuesta de Bergi y lo envolví en un módulo de NPM .

var CallableInstance = require('callable-instance');

class ExampleClass extends CallableInstance {
  constructor() {
    // CallableInstance accepts the name of the property to use as the callable
    // method.
    super('instanceMethod');
  }

  instanceMethod() {
    console.log("instanceMethod called!");
  }
}

var test = new ExampleClass();
// Invoke the method normally
test.instanceMethod();
// Call the instance itself, redirects to instanceMethod
test();
// The instance is actually a closure bound to itself and can be used like a
// normal function.
test.apply(null, [ 1, 2, 3 ]);

3

Actualizar:

Desafortunadamente, esto no funciona del todo porque ahora está devolviendo un objeto de función en lugar de una clase, por lo que parece que esto en realidad no se puede hacer sin modificar el prototipo. Cojo.


Básicamente, el problema es que no hay forma de establecer el thisvalor para el Functionconstructor. La única forma de hacer esto realmente sería usar el .bindmétodo después, sin embargo, esto no es muy amigable con la clase.

Podríamos hacer esto en una clase base auxiliar, sin thisembargo, no estará disponible hasta después de la superllamada inicial , por lo que es un poco complicado.

Ejemplo de trabajo:

'use strict';

class ClassFunction extends function() {
    const func = Function.apply(null, arguments);
    let bound;
    return function() {
        if (!bound) {
            bound = arguments[0];
            return;
        }
        return func.apply(bound, arguments);
    }
} {
    constructor(...args) {
        (super(...args))(this);
    }
}

class Smth extends ClassFunction {
    constructor(x) {
        super('return this.x');
        this.x = x;
    }
}

console.log((new Smth(90))());

(El ejemplo requiere un navegador moderno o node --harmony.)

Básicamente, la función base ClassFunctionamplía envolverá la Functionllamada al constructor con una función personalizada que es similar a .bind, pero permite el enlace más tarde, en la primera llamada. Luego, en el ClassFunctionpropio constructor, llama a la función devuelta de la superque ahora es la función vinculada, pasando thispara terminar de configurar la función de vinculación personalizada.

(super(...))(this);

Todo esto es un poco complicado, pero evita la mutación del prototipo, que se considera de mala forma por razones de optimización y puede generar advertencias en las consolas de los navegadores.


1
Estás complicando demasiado las cosas. boundse referirá a la función que usted returnde esa clase anónima. Simplemente nómbrelo y consúltelo directamente. También recomendaría evitar pasar cadenas de código, son un desastre para trabajar (en cada paso del proceso de desarrollo).
Bergi

Eso extendsrealmente no parece funcionar como se esperaba, ya que Function.isPrototypeOf(Smth)también new Smth instanceof Functiones falso.
Bergi

@Bergi ¿Qué motor JS estás usando? console.log((new Smth) instanceof Function);es truepara mí en Node v5.11.0 y el último Firefox.
Alexander O'Mara

Vaya, ejemplo equivocado. Es new Smth instanceof Smthque no funciona con tu solución. Además, no habrá métodos Smthdisponibles en sus instancias, ya que solo devuelve un estándar Function, no un Smth.
Bergi

1
@Bergi Maldita sea, parece que tienes razón. Sin embargo, extender cualquier tipo nativo parece tener el mismo problema. extend Functiontambién hace new Smth instanceof Smthfalso.
Alexander O'Mara

1

Primero llegué a la solución arguments.callee, pero fue horrible.
Esperaba que se rompiera en modo estricto global, pero parece que funciona incluso allí.

class Smth extends Function {
  constructor (x) {
    super('return arguments.callee.x');
    this.x = x;
  }
}

(new Smth(90))()

Fue una mala forma por usar arguments.callee, pasar el código como una cadena y forzar su ejecución en modo no estricto. Pero applyapareció la idea de anular .

var global = (1,eval)("this");

class Smth extends Function {
  constructor(x) {
    super('return arguments.callee.apply(this, arguments)');
    this.x = x;
  }
  apply(me, [y]) {
    me = me !== global && me || this;
    return me.x + y;
  }
}

Y la prueba, que muestra que puedo ejecutar esto como función de diferentes maneras:

var f = new Smth(100);

[
f instanceof Smth,
f(1),
f.call(f, 2),
f.apply(f, [3]),
f.call(null, 4),
f.apply(null, [5]),
Function.prototype.apply.call(f, f, [6]),
Function.prototype.apply.call(f, null, [7]),
f.bind(f)(8),
f.bind(null)(9),
(new Smth(200)).call(new Smth(300), 1),
(new Smth(200)).apply(new Smth(300), [2]),
isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),
] == "true,101,102,103,104,105,106,107,108,109,301,302,true,true"

Versión con

super('return arguments.callee.apply(arguments.callee, arguments)');

de hecho contiene bindfuncionalidad:

(new Smth(200)).call(new Smth(300), 1) === 201

Versión con

super('return arguments.callee.apply(this===(1,eval)("this") ? null : this, arguments)');
...
me = me || this;

hace cally applyen windowinconsistente:

isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),

por lo que el cheque debe trasladarse a apply:

super('return arguments.callee.apply(this, arguments)');
...
me = me !== global && me || this;

1
¿Qué estás intentando hacer realmente?
Gracias

2
Creo que las clases siempre están en modo estricto: stackoverflow.com/questions/29283935/…
Alexander O'Mara

@ AlexanderO'Mara, por cierto, thises ventana, no indefinida, por lo que la función creada no está en modo estricto (al menos en Chrome).
Qwertiy

Por favor, deje de escribir esta respuesta. Ya he escrito que es una mala forma. Pero realmente es una respuesta: funciona tanto en FF como en Chrome (no tiene Edge para verificar).
Qwertiy

Supongo que esto funciona porque Functionno está en modo estricto. Aunque horrible, es interesante +1. Sin embargo, probablemente no puedas caminar más en cadena.
Alexander O'Mara

1

Esta es la solución que he encontrado que sirve a todas mis necesidades de ampliación de funciones y me ha servido bastante bien. Los beneficios de esta técnica son:

  • Al extender ExtensibleFunction , el código es idiomático para extender cualquier clase ES6 (no, jugar con constructores o proxies fingidos).
  • La cadena de prototipos se conserva a través de todas las subclases y instanceof/ .constructordevuelve los valores esperados.
  • .bind() .apply()y .call()todos funcionan como se esperaba. Esto se hace anulando estos métodos para alterar el contexto de la función "interna" en oposición a la ExtensibleFunctioninstancia (o su subclase).
  • .bind()devuelve una nueva instancia del constructor de funciones (ya sea ExtensibleFunctionuna subclase). Se utiliza Object.assign()para garantizar que las propiedades almacenadas en la función vinculada sean coherentes con las de la función de origen.
  • Se respetan los cierres y las funciones de flecha continúan manteniendo el contexto adecuado.
  • La función "interna" se almacena a través de un Symbol, que puede ser ofuscado por módulos o un IIFE (o cualquier otra técnica común de privatización de referencias).

Y sin más preámbulos, el código:

// The Symbol that becomes the key to the "inner" function 
const EFN_KEY = Symbol('ExtensibleFunctionKey');

// Here it is, the `ExtensibleFunction`!!!
class ExtensibleFunction extends Function {
  // Just pass in your function. 
  constructor (fn) {
    // This essentially calls Function() making this function look like:
    // `function (EFN_KEY, ...args) { return this[EFN_KEY](...args); }`
    // `EFN_KEY` is passed in because this function will escape the closure
    super('EFN_KEY, ...args','return this[EFN_KEY](...args)');
    // Create a new function from `this` that binds to `this` as the context
    // and `EFN_KEY` as the first argument.
    let ret = Function.prototype.bind.apply(this, [this, EFN_KEY]);
    // For both the original and bound funcitons, we need to set the `[EFN_KEY]`
    // property to the "inner" function. This is done with a getter to avoid
    // potential overwrites/enumeration
    Object.defineProperty(this, EFN_KEY, {get: ()=>fn});
    Object.defineProperty(ret, EFN_KEY, {get: ()=>fn});
    // Return the bound function
    return ret;
  }

  // We'll make `bind()` work just like it does normally
  bind (...args) {
    // We don't want to bind `this` because `this` doesn't have the execution context
    // It's the "inner" function that has the execution context.
    let fn = this[EFN_KEY].bind(...args);
    // Now we want to return a new instance of `this.constructor` with the newly bound
    // "inner" function. We also use `Object.assign` so the instance properties of `this`
    // are copied to the bound function.
    return Object.assign(new this.constructor(fn), this);
  }

  // Pretty much the same as `bind()`
  apply (...args) {
    // Self explanatory
    return this[EFN_KEY].apply(...args);
  }

  // Definitely the same as `apply()`
  call (...args) {
    return this[EFN_KEY].call(...args);
  }
}

/**
 * Below is just a bunch of code that tests many scenarios.
 * If you run this snippet and check your console (provided all ES6 features
 * and console.table are available in your browser [Chrome, Firefox?, Edge?])
 * you should get a fancy printout of the test results.
 */

// Just a couple constants so I don't have to type my strings out twice (or thrice).
const CONSTRUCTED_PROPERTY_VALUE = `Hi, I'm a property set during construction`;
const ADDITIONAL_PROPERTY_VALUE = `Hi, I'm a property added after construction`;

// Lets extend our `ExtensibleFunction` into an `ExtendedFunction`
class ExtendedFunction extends ExtensibleFunction {
  constructor (fn, ...args) {
    // Just use `super()` like any other class
    // You don't need to pass ...args here, but if you used them
    // in the super class, you might want to.
    super(fn, ...args);
    // Just use `this` like any other class. No more messing with fake return values!
    let [constructedPropertyValue, ...rest] = args;
    this.constructedProperty = constructedPropertyValue;
  }
}

// An instance of the extended function that can test both context and arguments
// It would work with arrow functions as well, but that would make testing `this` impossible.
// We pass in CONSTRUCTED_PROPERTY_VALUE just to prove that arguments can be passed
// into the constructor and used as normal
let fn = new ExtendedFunction(function (x) {
  // Add `this.y` to `x`
  // If either value isn't a number, coax it to one, else it's `0`
  return (this.y>>0) + (x>>0)
}, CONSTRUCTED_PROPERTY_VALUE);

// Add an additional property outside of the constructor
// to see if it works as expected
fn.additionalProperty = ADDITIONAL_PROPERTY_VALUE;

// Queue up my tests in a handy array of functions
// All of these should return true if it works
let tests = [
  ()=> fn instanceof Function, // true
  ()=> fn instanceof ExtensibleFunction, // true
  ()=> fn instanceof ExtendedFunction, // true
  ()=> fn.bind() instanceof Function, // true
  ()=> fn.bind() instanceof ExtensibleFunction, // true
  ()=> fn.bind() instanceof ExtendedFunction, // true
  ()=> fn.constructedProperty == CONSTRUCTED_PROPERTY_VALUE, // true
  ()=> fn.additionalProperty == ADDITIONAL_PROPERTY_VALUE, // true
  ()=> fn.constructor == ExtendedFunction, // true
  ()=> fn.constructedProperty == fn.bind().constructedProperty, // true
  ()=> fn.additionalProperty == fn.bind().additionalProperty, // true
  ()=> fn() == 0, // true
  ()=> fn(10) == 10, // true
  ()=> fn.apply({y:10}, [10]) == 20, // true
  ()=> fn.call({y:10}, 20) == 30, // true
  ()=> fn.bind({y:30})(10) == 40, // true
];

// Turn the tests / results into a printable object
let table = tests.map((test)=>(
  {test: test+'', result: test()}
));

// Print the test and result in a fancy table in the console.
// F12 much?
console.table(table);

Editar

Como estaba de humor, pensé que publicaría un paquete para esto en npm.


1

Hay una solución simple que aprovecha las capacidades funcionales de JavaScript: pase la "lógica" como un argumento de función al constructor de su clase, asigne los métodos de esa clase a esa función, luego devuelva esa función del constructor como resultado :

class Funk
{
    constructor (f)
    { let proto       = Funk.prototype;
      let methodNames = Object.getOwnPropertyNames (proto);
      methodNames.map (k => f[k] = this[k]);
      return f;
    }

    methodX () {return 3}
}

let myFunk  = new Funk (x => x + 1);
let two     = myFunk(1);         // == 2
let three   = myFunk.methodX();  // == 3

Lo anterior se probó en Node.js 8.

Una deficiencia del ejemplo anterior es que no admite métodos heredados de la cadena de superclase. Para apoyar eso, simplemente reemplace "Object. GetOwnPropertyNames (...)" con algo que devuelva también los nombres de los métodos heredados. Creo que cómo hacer eso se explica en alguna otra pregunta-respuesta en Stack Overflow :-). Por cierto. Sería bueno si ES7 agregara un método para producir nombres de métodos heredados también ;-).

Si necesita admitir métodos heredados, una posibilidad es agregar un método estático a la clase anterior que devuelve todos los nombres de métodos heredados y locales. Luego llámalo desde el constructor. Si luego extiendes esa clase Funk, también obtienes ese método estático heredado.


Creo que este ejemplo da una respuesta simple a la pregunta original "... cómo puedo implementar la lógica para tal llamada". Simplemente páselo como un argumento con valor de función al constructor. En el código anterior, la clase Funk no extiende explícitamente Function, aunque podría, realmente no es necesario. Como puede ver, puede llamar a sus "instancias" como llama a cualquier función ordinaria.
Panu Logic
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.