Establecer el valor predeterminado de los atributos del objeto javascript

82

¿Hay alguna manera de establecer el atributo predeterminado de un objeto javascript de manera que:

var emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue

IE puede ignorarse, Chrome Frame me ha aliviado de ese dolor de cabeza.

Sandstrom
fuente
¿CADA atributo no existente o simplemente un nombre de atributo CONOCIDO?
js1568

Respuestas:

115

Desde que hice la pregunta hace varios años, las cosas han progresado muy bien.

Los proxies son parte de ES6. El siguiente ejemplo funciona en Chrome, Firefox, Safari y Edge :

var handler = {
  get: function(target, name) {
    return target.hasOwnProperty(name) ? target[name] : 42;
  }
};

var p = new Proxy({}, handler);

p.answerToTheUltimateQuestionOfLife; //=> 42

Lea más en la documentación de Mozilla sobre Proxies .

Sandstrom
fuente
Si planea admitir Internet Explorer (antes de Edge), no tiene suerte: caniuse.com/#search=proxy Además, polyfill github.com/tvcutsem/harmony-reflect no es compatible con IE
Tiagojdferreira
21
Felicidades, se ha ganado Phoenix (Insignia de oro: respondió una de sus propias preguntas al menos un año después, con una respuesta que al menos duplicó el número de votos de la respuesta más popular)
Ziggy
Object.withDefault = (defaultValue, o = {}) => new Proxy (o, {get: (o, k) => (k en o)? O [k]: defaultValue}); o = Object.withDefault (42); buey // => 42 buey = 10 buey // => 10 o.xx // => 42
Vlad V
Entonces, usar Proxy también significa Object.getEntriesque no se puede llamar a un Proxy :(
Meredith
1
Si quiere decir Object.entries, puede modificar el controlador para establecer propiedades cuando se accede a ellas. Cambiar return target.hasOwnProperty(name) ? target[name] : 42;a if (!target.hasOwnProperty(name)) target[name] = 42; return target[name];.
dosentmatter
28

No hay una manera de configurar esto en Javascript: regresar undefinedpara propiedades inexistentes es parte de la especificación principal de Javascript. Vea la discusión para esta pregunta similar . Como sugerí allí, un enfoque (aunque realmente no puedo recomendarlo) sería definir una getPropertyfunción global :

function getProperty(o, prop) {
    if (o[prop] !== undefined) return o[prop];
    else return "my default";
}

var o = {
    foo: 1
};

getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // "my default"

Pero esto conduciría a un montón de código no estándar que sería difícil de leer para otros y podría tener consecuencias no deseadas en áreas donde esperaría o desearía un valor indefinido. Es mejor comprobarlo sobre la marcha:

var someVar = o.someVar || "my default";
nrabinowitz
fuente
10
advertencia: var someVar = o.someVar || "my default";tendrá resultados potencialmente inesperados cuando o.someVar se complete pero se evalúe como falso (por ejemplo, nulo, 0, ""). someVar = o.someVar === undefined ? o.someVar : "my default";seria mejor. Normalmente uso ||solo cuando el valor predeterminado también se evalúa como false. (e.g. o.someVar || 0`)
Shanimal
5
Ese es un buen punto: esto no funcionará en ningún lugar donde un valor falso-y sea una entrada válida, y debe tenerlo en cuenta al usar este patrón. Pero la mayoría de las veces, tiene sentido tratar una propiedad establecida explícitamente nullo falsede la misma manera que una propiedad no establecida, en cuyo caso esto cumple una doble función. Sin embargo, la advertencia es justa.
nrabinowitz
5
@Shanimal Tu ejemplo está al revés, debería serlo someVar = o.someVar === undefined ? "my default" : o.someVar;, solo un problema menor, pero me
confundió
sí @Metalskin, no nos importaría el valor indefinido, ¿verdad? lol. lo siento, espero que el error no le haya costado demasiado tiempo :)
Shanimal
28

Usar desestructuración (nuevo en ES6)

Hay una gran documentación de Mozila , así como una publicación de blog fantástica que explica la sintaxis mejor que yo.

Para responder tu pregunta

var emptyObj = {};
const { nonExistingAttribute = defaultValue } = emptyObj;
console.log(nonExistingAttribute); // defaultValue

Ir más lejos

¿Puedo cambiar el nombre de esta variable? ¡Por supuesto!

const { nonExistingAttribute: coolerName = 15} = emptyObj;
console.log(coolerName); // 15

¿Qué pasa con los datos anidados? ¡Dale!

var nestedData = {
    name: 'Awesome Programmer',
    languages: [
        {
            name: 'javascript',
            proficiency: 4,
        }
    ],
    country: 'Canada',
};

var {name: realName, languages: [{name: languageName}]} = nestedData ;

console.log(realName); // Awesome Programmer
console.log(languageName); // javascript
alexdriedger
fuente
11

Seguro que esto suena como el uso típico de objetos basados ​​en prototipos:

// define a new type of object
var foo = function() {};  

// define a default attribute and value that all objects of this type will have
foo.prototype.attribute1 = "defaultValue1";  

// create a new object of my type
var emptyObj = new foo();
console.log(emptyObj.attribute1);       // outputs defaultValue1
jfriend00
fuente
si llama a console.log (emptyObj), devuelve {}. no {atributo1: 'defaultValue1'}
throrin19
2
Sí, porque attribute1: defaultValue1está en el prototipo y console.log solo enumera los elementos establecidos en el objeto de nivel superior, no en el prototipo. Pero, el valor está ahí como mis console.log(emptyObj.attribute1)programas.
jfriend00
está bien, pero tiene el mismo problema JSON.stringify(emptyobj). Me vi obligado a crear un método que devuelva todos los atributos en respuesta a este problema
throrin19
11

mi codigo es:

function(s){
    s = {
        top: s.top || 100,    // default value or s.top
        left: s.left || 300,  // default value or s.left
    }
    alert(s.top)
}
Kiyan
fuente
1
Me gusta más esta solución, porque es similar a lo que hago en PHP. function foo( array $kwargs = array() ) { // Fill in defaults for optional keyworded arguments. $kwargs += array( 'show_user' => true, 'show_links' => false, ); ...
Mike Finch
10

La forma en que lo logro es con la object.assignfunción

const defaultProperties = { 'foo': 'bar', 'bar': 'foo' };
const overwriteProperties = { 'foo': 'foo' };
const newObj = Object.assign({}, defaultProperties, overwriteProperties);
console.log(defaultProperties);  // {"foo": "bar", "bar": "foo"}
console.log(overwriteProperties);  // { "foo": "foo" };
console.log(newObj);  // { "foo": "foo", "bar": "foo" }
Colin
fuente
5
Haciendo esto también estás sobrescribiendo los valores de overwritePropertiesla forma correcta es:const newObj = Object.assign({}, defaultProperties, overwriteProperties)
Francisco Puga
6

Creo que el enfoque más simple es utilizar Object.assign.

Si tiene esta clase:

class MyHelper {
    constructor(options) {
        this.options = Object.assign({
            name: "John",
            surname: "Doe",
            birthDate: "1980-08-08"
        }, options);
    }
}

Puedes usarlo así:

let helper = new MyHelper({ name: "Mark" });
console.log(helper.options.surname); // this will output "Doe"

Documentación (con polyfill): https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

gianlucaparadise
fuente
5

O puedes probar esto

dict = {
 'somekey': 'somevalue'
};

val = dict['anotherkey'] || 'anotherval';
usuario1386350
fuente
15
Mala idea si dict ['anotherkey'] es 0.
Ray Toal
Tal vez debería considerar esto: codereadability.com/...
ebragaparah
@RayToal ¿Entonces no String()arreglarías eso? Como en:val = dict[String('anotherkey')] || 'anotherval';
Godstime Osarobo
No, lo siento, no lo haría. El mismo problema. Pruébelo: primero haga d = {x: 1, y: 0}entonces d['y'] || 100y no, erróneamente da 100. Luego pruebe su idea, que es d[String('y')] || 100: todavía da incorrectamente 100 cuando debería dar 0.
Ray Toal
4

La más simple de todas las soluciones:

dict = {'first': 1,
        'second': 2,
        'third': 3}

Ahora,

dict['last'] || 'Excluded'

devolverá 'Excluido', que es el valor predeterminado.

Gil Baggio
fuente
3
esto falla si tiene un dictado ligeramente diferente:dict = {'first': 0, 'second': 1, 'third': 2}
Vlad V
1
Esta también es una excelente manera de encadenar valores que podrían no existir. Por ejemplo a.b.c.d. Si no están definidos A, D, C o se golpea un error, pero sólo puede hacerlo(((a || {}).b || {}).c || {}).d) || "default"
hobberwickey
3

Esta me parece la forma más sencilla y legible de hacerlo:

let options = {name:"James"}
const default_options = {name:"John", surname:"Doe"}

options = Object.assign({}, default_options, options)

Referencia de Object.assign ()

joaquin keller
fuente
Iba a responder que la biblioteca de subrayado tiene el método _.defaults (object, * defaults) para eso, ¡pero su respuesta es la misma sin ninguna biblioteca!
DaniCE
2

Ayer vi un artículo que menciona una Object.__noSuchMethod__propiedad: JavascriptTips No he tenido la oportunidad de jugar con él, así que no sé sobre la compatibilidad con el navegador, pero ¿tal vez podrías usar eso de alguna manera?

James Long
fuente
Vi esa página también, debemos haber leído el mismo artículo de la HN. Aunque solo métodos. Se podría hacer con defineGetter, pero lamentablemente no está en ECMA5. No lo hicieron con otro enfoque getter / setter que es peor en mi opinión (requiere una definición de propiedades de antemano).
sandstrom
Esta podría ser una respuesta en el futuro :) Esperemos que los navegadores lleguen pronto :) ♬
jave.web
1
Creo que la respuesta futura será usar un proxy
James Long
@JamesLong sí, ¡tienes razón! resulta que el futuro ha llegado :) 3 años después de que hice la pregunta, esto ahora funciona en FF (y pronto en otros navegadores). Agregué una respuesta a continuación.
sandstrom
2

Me sorprende que nadie haya mencionado al operador ternario todavía.

var emptyObj = {a:'123', b:'234', c:0};
var defaultValue = 'defaultValue';
var attr = 'someNonExistAttribute';
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue;//=> 'defaultValue'


attr = 'c'; // => 'c'
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue; // => 0

De esta manera, incluso si el valor de 'c' es 0, seguirá obteniendo el valor correcto.

Alan Dong
fuente
1
Object.withDefault = (defaultValue,o={}) => {
  return new Proxy(o, {
    get: (o, k) => (k in o) ? o[k] : defaultValue 
  });
}

o = Object.withDefault(42);
o.x  //=> 42

o.x = 10
o.x  //=> 10
o.xx //=> 42
Vlad V
fuente
Entonces ... usar Proxies también significa que perderá todos los buenos métodos Object ahora :(
Meredith
0

De hecho, es posible hacerlo Object.create. No funcionará para propiedades "no definidas". Pero para los que se les ha dado un valor predeterminado.

var defaults = {
    a: 'test1',
    b: 'test2'
};

Luego, cuando crea su objeto de propiedades, lo hace con Object.create

properties = Object.create(defaults);

Ahora tendrá dos objetos donde el primer objeto está vacío, pero el prototipo apunta al defaultsobjeto. Probar:

console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Nils
fuente
0

Un enfoque sería tomar un objeto predeterminado y fusionarlo con el objeto de destino. El objeto de destino anularía los valores del objeto predeterminado.

jQuery tiene el .extend()método que hace esto. Sin embargo, jQuery no es necesario, ya que hay implementaciones de JS vanilla como las que se pueden encontrar aquí:

http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/

Doug Amos
fuente
-1

Vine aquí buscando una solución porque el encabezado coincidía con la descripción de mi problema, pero no es lo que estaba buscando, pero obtuve una solución a mi problema (quería tener un valor predeterminado para un atributo que sería dinámico, algo como fecha ).

let Blog = {
title  : String,
image  : String,
body   : String,
created: {type: Date, default: Date.now}
}

El código anterior fue la solución por la que finalmente me conformé.

Jibin Philipose
fuente