Ustaw domyślną wartość atrybutów obiektu javascript

83

Czy istnieje sposób na ustawienie domyślnego atrybutu obiektu javascript, takiego jak:

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

IE można zignorować, Chrome Frame uwolnił mnie od tego bólu głowy.

sandstrom
źródło
KAŻDY nieistniejący atrybut lub po prostu ZNANA nazwa atrybutu?
js1568,

Odpowiedzi:

120

Odkąd zadałem to pytanie kilka lat temu, sprawy potoczyły się ładnie.

Serwery proxy są częścią ES6. Poniższy przykład działa w Chrome, Firefox, Safari i Edge :

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

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

p.answerToTheUltimateQuestionOfLife; //=> 42

Przeczytaj więcej w dokumentacji Mozilli na temat serwerów proxy .

sandstrom
źródło
Jeśli planujesz obsługę Internet Explorera (przed Edge), nie masz szczęścia: caniuse.com/#search=proxy Ponadto polyfill github.com/tvcutsem/harmony-reflect nie obsługuje IE
Tiagojdferreira
21
Gratulacje, zdobyłeś Feniksa (złota odznaka: odpowiedziałeś na jedno z twoich pytań co najmniej rok później, z odpowiedzią, która co najmniej podwoiła liczbę głosów na najpopularniejszą odpowiedź)
Ziggy
Object.withDefault = (defaultValue, o = {}) => nowy Proxy (o, {get: (o, k) => (k in o)? O [k]: defaultValue}); o = Object.withDefault (42); ox // => 42 ox = 10 ox // => 10 o.xx // => 42
Vlad V
Tak więc używanie proxy oznacza również, że Object.getEntriesnie można wywołać serwera proxy :(
Meredith
1
Jeśli masz na myśli Object.entries, możesz zmodyfikować procedurę obsługi, aby ustawić właściwości, gdy są one dostępne. Zmień return target.hasOwnProperty(name) ? target[name] : 42;na if (!target.hasOwnProperty(name)) target[name] = 42; return target[name];.
dosentmatter
31

Użyj destrukturyzacji (nowość w ES6)

Jest świetna dokumentacja Mozili, a także fantastyczny wpis na blogu, który wyjaśnia składnię lepiej niż ja.

Odpowiedzieć na Twoje pytanie

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

Idąc dalej

Czy mogę zmienić nazwę tej zmiennej? Pewnie!

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

A co z danymi zagnieżdżonymi? Dawaj!

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
źródło
28

Nie ma sposobu, aby to ustawić w JavaScript - zwracanie undefinedw przypadku nieistniejących właściwości jest częścią podstawowej specyfikacji Javascript. Zobacz dyskusję dotyczącą tego podobnego pytania . Jak tam zasugerowałem, jednym podejściem (chociaż nie mogę tego naprawdę polecić) byłoby zdefiniowanie getPropertyfunkcji globalnej :

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"

Doprowadziłoby to jednak do powstania wielu niestandardowych kodów, które byłyby trudne do odczytania dla innych i mogłoby mieć niezamierzone konsekwencje w obszarach, w których można oczekiwać lub chcieć niezdefiniowanej wartości. Lepiej po prostu sprawdzić na bieżąco:

var someVar = o.someVar || "my default";
nrabinowitz
źródło
10
ostrzeżenie: var someVar = o.someVar || "my default";będzie miał potencjalnie nieoczekiwane wyniki, gdy o.someVar zostanie wypełniony, ale zostanie oszacowany na false (np. null, 0, ""). someVar = o.someVar === undefined ? o.someVar : "my default";byłoby lepiej. Zwykle używam ||samego, gdy wartość domyślna również ocenia się na false. (e.g. o.someVar ||
0`
5
To dobra uwaga - to nie zadziała nigdzie, gdzie wartość false-y jest prawidłowym wejściem i musisz to wziąć pod uwagę, używając tego wzorca. Jednak częściej niż nie, sensowne jest traktowanie właściwości jawnie ustawionej nulllub falsew ten sam sposób jak nieruchomość nieustawioną, w którym to przypadku ma to podwójne zadanie. Jednak ostrzeżenie jest uczciwe.
nrabinowitz
5
@Shanimal Twój przykład jest zły, powinien być someVar = o.someVar === undefined ? "my default" : o.someVar;, tylko drobny problem, ale trochę mnie rzucił, kiedy pierwszy raz wypróbowałem twój kod ;-)
Metalskin,
tak @Metalskin nie obchodziłaby nas nieokreślona wartość, prawda? lol. przepraszam, mam nadzieję, że pomyłka nie kosztowała cię zbyt wiele czasu :)
Shanimal,
11

To z pewnością brzmi jak typowe użycie obiektów opartych na protojach:

// 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
źródło
jeśli wywołasz console.log (emptyObj), zwróci {}. not {attribute1: 'defaultValue1'}
throrin19
2
Tak, ponieważ attribute1: defaultValue1znajduje się w prototypie, a console.log wylicza tylko elementy ustawione w obiekcie najwyższego poziomu, a nie w prototypie. Ale wartość jest taka, jak moje console.log(emptyObj.attribute1)pokazy.
jfriend00
to prawda, ale to ten sam problem JSON.stringify(emptyobj). Byłem zmuszony stworzyć metodę, która zwraca wszystkie atrybuty w odpowiedzi na ten problem
throrin19
11

mój kod to:

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
źródło
1
Najbardziej podoba mi się to rozwiązanie, ponieważ jest podobne do tego, co robię w PHP. function foo( array $kwargs = array() ) { // Fill in defaults for optional keyworded arguments. $kwargs += array( 'show_user' => true, 'show_links' => false, ); ...
Mike Finch
10

Sposób, w jaki to osiągam, to object.assignfunkcja

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
źródło
5
Robiąc to, nadpisujesz również wartości overwritePropertiesw prawidłowy sposób:const newObj = Object.assign({}, defaultProperties, overwriteProperties)
Francisco Puga
6

Myślę, że najprostszym podejściem jest użycie Object.assign.

Jeśli masz tę klasę:

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

Możesz go używać w ten sposób:

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

Dokumentacja (z wypełnieniem polyfill): https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign

gianlucaparadise
źródło
6

Najprostsze ze wszystkich rozwiązań:

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

Teraz,

dict['last'] || 'Excluded'

zwróci wartość „Wykluczone”, która jest wartością domyślną.

Gil Baggio
źródło
3
to się nie powiedzie, jeśli masz trochę inny dyktando:dict = {'first': 0, 'second': 1, 'third': 2}
Vlad V
2
To także świetny sposób na łańcuch wartości, które mogą nie istnieć. Na przykład a.b.c.d. Jeśli a, d lub c są nieokreślone, napotkasz błąd, ale możesz to zrobić(((a || {}).b || {}).c || {}).d) || "default"
hobberwickey
1
Zdecydowanie najlepsze rozwiązanie!
Arthur Harduim,
5

Albo możesz spróbować

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

val = dict['anotherkey'] || 'anotherval';
user1386350
źródło
15
Zły pomysł, jeśli dict ['anotherkey'] to 0.
Ray Toal
Może powinieneś rozważyć to: codereadability.com/…
ebragaparah
@RayToal Więc nie chcesz String()tego naprawić? Jak w:val = dict[String('anotherkey')] || 'anotherval';
Godstime Osarobo
Nie, przepraszam, nie byłoby. Taki sam problem. Spróbuj: najpierw zrób d = {x: 1, y: 0}to, d['y'] || 100a nie błędnie daje 100. Następnie wypróbuj swój pomysł, który jest d[String('y')] || 100- nadal niewłaściwie daje 100, kiedy powinien dać 0.
Ray Toal
4

Wydaje mi się, że jest to najprostszy i najbardziej czytelny sposób:

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

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

Odwołanie do obiektu Object. assign ()

joaquin keller
źródło
Miałem zamiar odpowiedzieć, że biblioteka podkreślenia ma do tego metodę _.defaults (obiekt, * domyślne), ale twoja odpowiedź jest taka sama bez żadnej biblioteki!
DaniCE,
2

Widziałem wczoraj artykuł, w którym wspomniano o Object.__noSuchMethod__właściwości: Porady Javascript Nie miałem okazji się nią bawić, więc nie wiem o obsłudze przeglądarek, ale może mógłbyś to w jakiś sposób wykorzystać?

James Long
źródło
Widziałem tę stronę, musieliśmy przeczytać ten sam artykuł w HN. Ale tylko metody. Można to zrobić za pomocą defineGetter, ale niestety tego nie ma w ECMA5. Nie mieli innego podejścia getter / setter, które moim zdaniem jest gorsze (wymaga wcześniejszego zdefiniowania właściwości).
sandstrom,
To może być odpowiedź w przyszłości :) Miejmy nadzieję, że przeglądarki wkrótce tam dotrą :) ♬
jave.web
1
Wierzę, że przyszłą odpowiedzią będzie użycie Proxy
James Long
@JamesLong tak, masz rację! okazuje się, że przyszłość nadeszła :) 3 lata po tym, jak zadałem to pytanie, teraz działa w FF (i innych przeglądarkach wkrótce). Poniżej dodałem odpowiedź.
sandstrom
2

Dziwię się, że nikt jeszcze nie wspomniał o operatorze trójskładnikowym.

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

W ten sposób, nawet jeśli wartość „c” jest równa 0, nadal otrzyma poprawną wartość.

Alan Dong
źródło
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
źródło
Więc ... używanie proxy oznacza również, że tracisz teraz wszystkie fajne metody Object :(
Meredith,
0

W rzeczywistości jest to możliwe Object.create. Nie będzie działać dla „niezdefiniowanych” właściwości. Ale dla tych, którym nadano wartość domyślną.

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

Następnie, kiedy tworzysz obiekt właściwości, robisz to z Object.create

properties = Object.create(defaults);

Teraz będziesz mieć dwa obiekty, w których pierwszy obiekt jest pusty, ale prototyp wskazuje na ten defaultsobiekt. Testować:

console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Nils
źródło
0

Jednym podejściem byłoby pobranie obiektu domyślnego i scalenie go z obiektem docelowym. Obiekt docelowy przesłania wartości w obiekcie domyślnym.

jQuery ma rozszerzenie .extend() metodę, która to robi. jQuery nie jest jednak potrzebne, ponieważ istnieją proste implementacje JS, takie jak można znaleźć tutaj:

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

Doug Amos
źródło
-1

Przyszedłem tutaj, szukając rozwiązania, ponieważ nagłówek pasował do opisu mojego problemu, ale to nie jest to, czego szukałem, ale znalazłem rozwiązanie mojego problemu (chciałem mieć domyślną wartość dla atrybutu, który byłby dynamiczny, jak data ).

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

Powyższy kod był rozwiązaniem, na które ostatecznie się zdecydowałem.

Jibin Philipose
źródło