public int Suma(params int[] values)
{
int suma = 0;
for (int i = 0; i < values.Length; i++)
suma += values[i];
return suma;
}
Bastante fácil, ¿verdad?Happy coding!
Diario de un desarrollador al que también le gusta correr en sus ratos libres...
public int Suma(params int[] values)
{
int suma = 0;
for (int i = 0; i < values.Length; i++)
suma += values[i];
return suma;
}
Bastante fácil, ¿verdad?<!-- include spec files here... --> <script type="text/javascript" src="spec/prototypesSpec.js"></script> <!-- include source files here... --> <script type="text/javascript" src="../prototypes.js"></script>
describe("Object.prototype.equals", function() {
it("El objeto a comparar es null", function() {
var u1 = {prop1: "1", prop2: 2};
var u2 = null;
expect(u1.equals(u2)).toBe(false);
});
it("El objeto a comparar es undefined", function() {
var u1 = {prop1: "1", prop2: 2};
var u2 = undefined;
expect(u1.equals(u2)).toBe(false);
});
});
Como vemos el código es bastante fácil de entender. El método describe se usar para definir un grupo de test (u otro grupo) y le pasamos una función donde se definen los test. Para definir un test usamos el método it, al que le pasamos una descripción del test y un función donde se ejecuta el test gracias a expect donde indicamos lo que queremos probar y el valor que debe tener. Si todo ha ido bien al recargar la página SpecRunner.html debemos obtener algo como estodescribe("Object.prototype.equals", function() {
it("El objeto a comparar es null", function() {
var u1 = {prop1: "1", prop2: 2};
var u2 = null;
expect(u1.equals(u2)).toBe(false);
});
it("El objeto a comparar es undefined", function() {
var u1 = {prop1: "1", prop2: 2};
var u2 = undefined;
expect(u1.equals(u2)).toBe(false);
});
it("Distinto numero de propiedades", function() {
var u1= {prop1 : "value1", prop2: 2};
var u2= {prop1 : "value1", prop2: 2, prop3: 3};
expect(u1.equals(u2)).toBe(false);
});
it("Mismas propiedades pero con distintos valores", function() {
var u1= {prop1 : "value1", prop2: 2};
var u2= {prop1 : "value1", prop2: 3};
expect(u1.equals(u2)).toBe(false);
});
it("Mismo numero de propiedades pero con nombres distintos", function() {
var u1= {prop1 : "value1", prop2: 2};
var u2= {prop1 : "value1", prop3: 3};
expect(u1.equals(u2)).toBe(false);
});
it("Dos objetos simples iguales", function() {
var u1 = {prop1: "1", prop2: 2};
var u2 = {prop1: "1", prop2: 2};
expect(u1.equals(u2)).toBe(true);
});
it("Dos objetos compuestos distintos", function() {
var u1 = {prop1: "1", prop2: 2, prop3: { prop4: 4}};
var u2 = {prop1: "1", prop2: 2, prop3: { prop5: 5}};
expect(u1.equals(u2)).toBe(false);
});
it("Dos objetos compuestos iguales", function() {
var u1 = {prop1: "1", prop2: 2, prop3: { prop4: 4}};
var u2 = {prop1: "1", prop2: 2, prop3: { prop4: 4}};
expect(u1.equals(u2)).toBe(true);
});
});
Obteniendo la siguiente salidavar u1 = {prop1 : 1, prop2: "value2"};
var u2 = {prop1 : 1, prop2: "value2"};
console.log(u1 == u2);
console.log(u1 === u2);
Simplemente estamos declarando dos objetos y los estamos comparando. Como vemos ambos objetos tienes las mismas propiedades (prop1 y prop2) y los mismos valores en dichas propiedades, prop1 vale 1 y prop2 vale "value2". Las siguientes lineas muestran por la consola el resultado de comparar ambos objetos. Supongo que la respuesta lógica sería pensar que algunas de las dos comparaciones debería devolver true. ¿Verdad?, pues no, ambas comparaciones devuelve false.
function Foo(p1,p2) {
this.prop1 = p1;
this.prop2 = p2;
}
var u1 = new Foo(1, "value2");
var u2 = new Foo(1, "value2");
console.log(u1 == u2); // false
console.log(u1 === u2); // false
Vemos que en ese caso el comportamiento similar así que podemos descartar que este comportamiento sea debido a como hemos declarado nuestros objetos.function Foo(p1,p2) {
this.prop1 = p1;
this.prop2 = p2;
this.bar = function() {
// do operation
}
}
var u1 = new Foo(1, "value2");
var u2 = new Foo(1, "value2");
console.log(u1 == u2); // false
console.log(u1 === u2); // false
Con eso claro lo único que tenemos que hacer es recorrer las propiedades de los objetos implicados e ir comparando valores. Recorrer las propiedades de un objeto es bastante fácil pero puede darnos algún problema si no tenemos algunas cosas en cuenta. En concreto el principal problema es que dependiendo del contexto donde recorramos las propiedades del objeto podemos obtener las propiedades heredadas de dicho objeto, por lo que para evitarlo debemos usar el método hasOwnProperty, que nos asegura que esa propiedad es del objeto en cuestión. En nuestro ejemplofunction Foo(p1,p2) {
this.prop1 = p1;
this.prop2 = p2;
this.func1 = function() {
// do operation
}
}
var u1 = new Foo(1, "value2");
for (var p in u1)
{
if (u1.hasOwnProperty(p))
console.log(p);
}
Obtendríamos esta salidaprop1 prop2 barCon esto claro ya podemos intentar hacer nuestro comparador de objetos. La primera versión que se nos viene a la mente es hacer algo así
function equals(p1, p2)
{
// Comparamos los objetos
}
Esta alternativa no es mala, pero pienso que es más elegante extender el tipo object y añadir el método equals, para poder hacer algo así
p1.equals(p2);Esto es bastante fácil en javascript y el esqueleto sería el siguiente
Object.prototype.equals = function(x)
{
// Comparamos los objetos
}
Al principio del método podemos hacer algunas comparaciones básicas como si x no está definido o si los objetos a comparar tienen el mismo número de propiedades. Esta útlima comparación se hace a través del método keys que desgraciadamente no está disponible en todos los navegadores (se puede consultar su disponibilidad aquí). Con esto, nuestro método ya va tomando cuerpo y sería de la siguiente formaObject.prototype.equals = function(x)
{
if (x === null || x === undefined)
return false;
if (Object.keys(this).length != Object.keys(x).length)
return false;
// Comparamos los objetos
return true;
}
Con esto ya lo único que nos queda es recorrer las propiedades del objeto de origen e ir buscando sus homólogas para luego comparar su valor. En estas comparaciones veremos de que tipo es la propiedad, para ver si comparamos directamente sus valores, volvernos a llamar al método equals en caso de ser un objeto o devolvemos false en caso que sea una función. Con esto nuestro método equals quedaría de la siguiente maneraObject.prototype.equals = function(x)
{
if (x === null || x === undefined)
return false;
if (Object.keys(this).length != Object.keys(x).length) return false;
for (var p in this)
{
// Evitamos navegar por las propiedades "heredadas"
if (this.hasOwnProperty(p)) {
// No es una propiedad de x
if (!x.hasOwnProperty(p)) return false;
switch(typeof(this[p])) {
case 'function':
// No admitimos objetos con funciones
return false;
case 'object':
// Comparamos los objetos
if (!this[p].equals(x[p]))
return false;
break;
default:
// Las propiedades tienes valores distintos
if (this[p] !== x[p])
return false;
break;
}
}
}
return true;
}
Para probar este código de manera rudimentaria he hecho este código y lo he lanzado en jsFiddle y los resultado obtenidos son los esperados
var u1= {prop1 : "value1", prop2: "value2"};
var u2= {prop1 : "value1", prop2: "value2"};
console.log('u1 equals u2: ' + u1.equals(u2));
var v1= {prop1 : "value1", prop2: "value2"};
var v2= {prop1 : "value1", prop2: "value2", prop3: "value3"};
console.log('v1 equals v2: ' + v1.equals(v2));
var w1= {prop1 : "value1", prop2: "value2"};
var w2= {prop1 : "value1", prop2: "value3"};
console.log('w1 equals w2: ' + w1.equals(w2));
var x1= {prop1 : "value1", prop2: "value2"};
var x2= {prop1 : "value1", prop3: "value2"};
console.log('x1 equals x2: ' + x1.equals(x2));
var y1= {prop1 : "value1", prop2: { prop3: "value3"}};
var y2= {prop1 : "value1", prop2: { prop4: "value4"}};
console.log('y1 equals y2: ' + y1.equals(y2));
var z1= {prop1 : "value1", prop2: { prop3: "value3"}};
var z2= {prop1 : "value1", prop2: { prop3: "value3"}};
console.log('z1 equals z2: ' + z1.equals(z2));
Puedes probar este código desde aquíif (a == b) {
// a y b son iguales
}
El problema está en que javascript las cosas no son como nosotros estamos acostumbrados que sean y este comparador nos pueden dar algún que otro quebradero de cabeza y más teniendo en cuenta que también tenemos un triple comparador, si como lo oyes, tenemos un triple comparador que se escribe ===.1 == 1 // true como era de esperar 1 == "1" // true, se convierte la cadena de texto a numero null == undefined // true 1 == true // true "" == false // true '\n' == false // true, el '\n' se interpreta como la cadena vacía "" == 0 // true '\n' == 0 // trueComo vemos en todas las comparaciones (salvo la primera) esperaríamos que javascript nos devolviera false, sin embargo nos devuelve true. Para salvar esto deberíamos usar el triple comparador.
1 === 1 // sigue siendo true 1 === "1" // false null === undefined // false 1 === true // false "" === false // false '\n' === false // false "" === 0 // false '\n' === 0 // falseDe igual forma que tenemos == y ===, también tenemos != y !== cuyo comportamiento es análogo.
NaN == NaN // false, NaN es Not a Number y no se puede comparar NaN === NaN // false 1.0 == 1 // true 1.0 === 1 // true, para javascript ambos son de tipo numericLa coerción de tipos puede ser algo realmente frustrante y que nos puede volver locos un par horas, pero que nos puede venir bastante bien en algunas situaciones por lo que lo mejor es estar seguro de lo que estamos haciendo en cada momento.
<p>Nombre: <input type="text" data-bind="value: firstname" /></p> <p>Apellidos: <input type="text" data-bind="value: lastname" /></p> <p>Nombre completo: <label data-bind="text: name"></p>Y este modelo
function ViewModel(model) {
var self = this;
self.firstname = ko.observable('');
self.lastname = ko.observable('');
self.name = ko.computed(function() {
if (self.firstname() == '' || self.lastname() == '') return '';
return self.lastname() + ', ' + self.firstname();
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
Como vemos establecemos dos observables sobre firstname y lastname. También definimos observable computed que actualiza la propiedad name. Esta función se lanzará cada vez que la propiedades lastname y firstname cambien. Lo realmente maravilloso es que la función detecta a que propiedades debe detectar los cambios sin que nosotros debamos hacer nada más que usarlas en el cuerpo de la función.<p>Nombre: <input type="text" data-bind="value: firstname" /></p> <p>Apellidos: <input type="text" data-bind="value: lastname" /></p> <p>Nombre completo: <input type="text" data-bind="value: name"></p>y el modelo de la siguiente manera
function ViewModel(model) {
var self = this;
self.firstname = ko.observable('');
self.lastname = ko.observable('');
self.name = ko.computed({
read: function() {
if (self.firstname() == '' || self.lastname() == '') return '';
return self.lastname() + ', ' + self.firstname();
},
write: function(value) {
var index = value.lastIndexOf(", ");
if (index > 0)
{
self.firstname(value.substring(index + 2));
self.lastname(value.substring(0, index));
}
}
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
Tal y como hicimos antes, si actualizamos el nombre y los apellidos automáticamente se actualizará el campo del nombre del completo. La salvedad en este caso, es que si actualizamos el nombre completo nosotros con el formato apellidos, nombre, se actualizarán los campos nombre y apellidos.<div>
<input type="text" data-bind="value: message, event: { keyup: onkeyup }" />
<label data-bind="text: status" ></label>
</div>
y el siguiente modelofunction ViewModel(model) {
var self = this;
self.message = ko.observable('');
self.status = ko.observable('Has escrito 0 caracteres...');
self.onkeyup = function(data, event) {
self.status('Has escrito ' + event.target.value.length + ' caracteres...');
return true;
}
}
ko.applyBindings(new ViewModel());
En este ejemplo estamos enganchándonos al evento keyup de un textbox y actualizamos un label con la información del número de caracteres que hemos introducido en el textbox.