The new object rest/spread syntax has some surprisingly nice applications, like omitting a field from an object.
Is there a (proposed) way to also assign to several properties of an object, the values from variables with the same names? In other words, a shorter way to say:
o.foo = foo;
o.bar = bar;
o.baz = baz;
Note: Without losing the existing properties of o, only adding to them.
Use Object.assign:
const o = { initial: 'initial' };
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
Object.assign(o, { foo, bar, baz });
console.log(o);
Note that both shorthand property names and Object.assign were introduced in ES6 - it's not something that requires an extremely up-to-date browser/environment.
Something similar that reassigns the reference to the object would be to initialize another object by spreading o and list foo, bar, baz:
let o = { initial: 'initial' };
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
o = { ...o, foo, bar, baz };
console.log(o);
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
const o = {foo, bar, baz};
console.log(o);
Related
This is apparently a valid destructuring assignment despite qux depending on bar:
const { foo, bar, qux = () => bar } = myObject;
How does this work since the documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) does not cover how dependent assignments like the example above works.
qux: () => 'qux' means to declare const qux, whose value will be extracted as the property qux from myObject.
However, if myObject has no such property, the const is declared as if you'd just written const qux = () => bar. Therefore, the default value of qux is an arrow function.
Note that for the default to be used, the qux property must be absent or set to undefined in myObject. The default will not be used if qux in myObject is null or any other value.
Also note that this will work:
const { foo, qux = () => bar, bar, x=qux() } = {};
But this will throw ReferenceError: Cannot access 'bar' before initialization:
const { foo, qux = () => bar, x=qux(), bar } = {};
This is because when you do qux = () => bar, it's not attempting to access an undeclared variable yet. However, invoking qux() does attempt to access the bar variable, so the order is important.
I ran into some code that looks like the following:
const {
foo = []
} = this.options
Assuming in this case that this.options is an JavaScript Object, how does this work? Does all of this.options get assigned to foo and if this.options is undefined, does foo just get initialized to an empty array? I found this code confusing because this.options is not an Array but is instead an Object of key/val pairs.
Sometimes it helps to just try things out. What you'd observe is that a default value is assigned to foo in case it is missing within the to be assigned object
function one() {
const options = {};
const {
foo = []
} = options;
console.log(foo);
}
function two() {
const options = {foo: 'bar'};
const {
foo = []
} = options;
console.log(foo);
}
function three() {
const options = {};
const {
foo = 'foo',
bar = 'bar',
baz = 'baz'
} = options;
console.log(foo, bar, baz);
}
one();
two();
three();
From MDN (emphesis mine ) :
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Not all of this.options get assigned to foo, it's unpacking foo from the Object :
const options = {
foo: ['foo', 'bar'],
bar: 'hello world'
}
const {
foo = []
} = options;
console.log(foo);
And foo = [] is there to be a fallback to have an empty array if this.options does not have a property foo :
const options = {
bar: 'hello world'
}
const {
foo = []
} = options;
console.log(foo);
If this.options is ` undefined, you'll get errors,
options is not defined
const {
foo = []
} = options;
console.log(foo);
Or:
Cannot destructure property foo of 'undefined' or 'null'.
const options = undefined;
const {
foo = []
} = options;
console.log(foo);
If you run it through babel you'll see that if this.options.foo exists then it will be bound to the name 'foo' in that scope and if it doesn't then foo is set to an empty array.
Here is an in-depths article for you to better understand ES6 Destructuring https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/
Destructuring assignment allows you to assign the properties of an
array or object to variables using syntax that looks similar to array
or object literals. This syntax can be extremely terse, while still
exhibiting more clarity than the traditional property access.
For your sample script the assignment are looking for an object with property "foo" from right side. If it can not find it, it will assign foo with default value as empty array.
If the left side is null or undefined, the operator will throw an error "Uncaught TypeError: Cannot destructure propertyfooof 'undefined' or 'null'."
// A regular JS object.
this.options = {
'a': 'a',
'foo': 'bar',
'z': 'z'
}
console.log('this.options.foo =', this.options.foo);
// this.options.foo === 'bar'
// Get 'foo' out of this.options. Becomes undefined if 'foo' doesn't exist in this.options.
const { foo } = this.options;
console.log('foo =', foo);
// foo === this.options.foo === 'bar';
const { nope } = this.options;
console.log('nope =', nope);
// typeof nope === 'undefined'
// Get 'zzz' if it exists, otherwise fall back to 1234.
const { zzz = 1234 } = this.options;
console.log('zzz =', zzz);
// zzz === 1234;
// zzz was set by its default fallback value.
Is it possible to somehow compose a string dynamically? I've read a bit about pass-by-value and pass-by-reference, and thus I'm creating all the strings as objects.
Example:
var foo = {str: 'foo'};
var bar = {str: foo.str + 'bar'};
var baz = {str: bar.str + 'baz'};
foo.str = 'fuu';
console.log(baz.str); //expected 'fuubarbaz', got 'foobarbaz
Thanks in advance!
Nah, when you define things statically like that, they're going to use the variable when it was called. You could do something like this with getters though:
let foo = {str: 'foo'};
let bar = {get str() { return foo.str + 'bar'; }};
let baz = {get str() { return bar.str + 'baz'; }};
foo.str = 'fuu';
console.log(baz.str); // properly outputs `fuubarbaz`
The reason why this works is the magic of getters; instead of defining the property statically, you're defining a function that gets called when trying to access the property. This way it can "react" to any downstream changes, because it's always dynamically generated.
It doesn't work like this, the concatenation foo.str + was executed only once, the plus sign is not a function that is called multiple times.
One way to do what you want is create an object with 3 strings and a method!:
const obj = {
a: 'foo',
b: 'bar',
c: 'baz',
show: function() {
return this.a + this.b + this.c;
}
};
console.log(obj.show());
obj.a = 'fuu';
console.log(obj.show());
Based on puddi's answer I came up with this:
console.clear()
var foo = {
// _str is the storage of str
_str: 'foo',
// getter of str, always called when accessing str in a read context
get str() {return this._str},
// setter of str, always called when accessing str in a write context
set str(str) {this._str = str}
};
// read context, so get str() of foo is called
console.log(foo.str) // "foo"
var bar = {
// define getter function of bar, calls getter function of foo
get str() {return foo.str + 'bar'}
};
// read context, so get str() of bar is called
console.log(bar.str) // "foobar"
var baz = {
// define getter function of baz, calls getter function of baz
get str() {return bar.str + 'baz'}
};
// read context, so get str() of baz is called
console.log(baz.str) // "foobarbaz"
// write context, so set str(str) of foo is called. foo._str is now 'fuu', was 'foo'
foo.str = 'fuu';
// read context, getter of baz is called which calls getter of bar which calls getter of foo which returns _str which has the value of 'fuu'
console.log(baz.str); // "fuubarbaz"
Alternatively you can user Object.defineProperty:
console.clear();
var foo = Object.defineProperty({}, 'str', {
enumerable: true,
get: () => this._property_str,
set: (str) => this._property_str = str
});
var bar = Object.defineProperty({}, 'str', {
enumerable: true,
get: () => foo.str + 'bar',
});
var baz = Object.defineProperty({}, 'str', {
enumerable: true,
get: () => bar.str + 'baz',
});
foo.str = 'foo'
console.log(foo.str) // "foo"
console.log(bar.str) // "foobar"
console.log(baz.str) // "foobarbaz"
foo.str = 'fuu';
console.log(baz.str); // "fuubarbaz"
I know that an object literal can have its keys listed by using Object.keys() method. But what about an object that was created through a constructor function?
function Foo () {}
Foo.prototype.bar = 'bar';
Foo.prototype.baz = 'baz';
var foo = new Foo();
console.log(Object.keys(foo).join(', ')); // returns ''
console.log(Object.getOwnPropertyNames(foo).join(', ')); // returns ''
Object.keys will only get own enumerable properties, and getOwnPropertyNames will only get own properties (even if not enumerable). Neither of them will give you the names of properties inherited from the prototype (or its prototype, or its, ...).
If you only care about enumerable properties, see trincot's answer.
If you want all of them,¹ even if they're not enumerable, you have to loop through the prototype chain:
function getAllPropertyNames(obj) {
var result = [];
while (obj && obj !== Object.prototype) {
result.push.apply(result, Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
}
return result;
}
function Foo () {}
Foo.prototype.bar = 'bar';
Foo.prototype.baz = 'baz';
var foo = new Foo();
console.log(getAllPropertyNames(foo));
In that example, I stopped when we reached Object.prototype, but of course you could keep going until you hit null instead:
function getAllPropertyNames(obj) {
var result = [];
while (obj) {
result.push.apply(result, Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
}
return result;
}
function Foo () {}
Foo.prototype.bar = 'bar';
Foo.prototype.baz = 'baz';
var foo = new Foo();
console.log(getAllPropertyNames(foo));
¹ "If you want all of them..." Note that in the above, we haven't tried to get properties that are named by Symbols instead of strings. If we did, we'd use getOwnPropertySymbols as well as getOwnPropertyNames.
You can use a for loop:
function Foo () {}
Foo.prototype.bar = 'bar';
Foo.prototype.baz = 'baz';
var foo = new Foo();
for (var key in foo)
console.log(key, foo[key]);
Note that this has limitations. Some properties can be made non-enumerable, and will then not be included. This is for instance the case for the length property of arrays:
var a = [1, 2];
for (var key in a)
console.log(key, a[key]);
Can someone explain why if I add a property to foo, somehow bar also inherits that property when I do this:
var foo = bar = {};
foo.hi = 'hi';
console.log(bar); //Object {hi: "hi"}
How does this work? I'm setting properties on foo, not bar. I realized I passed the object to bar and then bar to foo, but I must be missing something here.
Integer assignment works differently and more sense (to me anyhow):
var foo = bar = 5;
foo = 4;
console.log(bar); // 5
Objects are passed by reference in JavaScript. Strings and number literals are not. In your code foo === bar, is the same object. You can simply declare the variables separately:
// Two different object instances
var foo = {};
var baz = {};
by doing foo = bar = {};, foo and bar are pointers to same object. so when you do:
foo.hi = 'hi';
It sets bar.hi also, because foo and bar point to the same object. To make it different, you should do:
var foo = {}, bar = {};
foo.hi = 'hi';