How to use the property of object inside the object, like this:
var obj = {
a: 1
b: this.obj + this.obj.a
}
If you want to have computed property with your object, think about it similar to a getter in object-oriented class.
You can do something like:
var obj = {
a: 1,
b: 2,
c: () => this.a + this.b
}
Later, you can access obj.c() to get the desired value.
Your question got me thinking about "Why not creating an object out of values and getters" - it is probably an overkill for your requirements, but i had to think this out :)
/*
** This function creates an object by rewriting the values of the first
** object as "value properties" (meaning the values stay as they are but
** can be accessed and modified like properties), while the second object
** expects functions that act as getters, which can access the values
** defined in the first object as well the getters from itself.
*/
const LetsBeLazy = function(values, lazy) {
for(let key in values) {
Object.defineProperty(this, key, {
value: values[key],
configurable: true,
writable: true
});
}
for(key in lazy) {
Object.defineProperty(this, key, {
get: lazy[key]
});
}
return this;
}
// Pointless example to get the point through :)
var obj = LetsBeLazy({
// These values stay as they are and can be
// accessed and changed from outside with the property notation
firstName: 'John',
lastName: 'Doe',
salutation: 'Mr.',
buildFullName: (random) => `${salutation} ${firstName} ${lastName} (${random})`
}, {
// These functions are lazily evaluated and can access other getters
// as well the values from the previous object - notice that since
// they are properties, they can't be called like functions.
sayHello: () => `Hello ${buildFullName(Math.ceil(Math.random() * 10))}`,
sayHelloWithABang: () => `${sayHello}!`
});
document.write(obj.sayHello + "<br>");
obj.firstName = 'Jane';
obj.salutation = 'Mrs.';
document.write(obj.sayHelloWithABang + "<br>");
document.write(obj.buildFullName('X') + "<br>");
You can't reference an object that hasn't been created yet.
Would something like this help?
var obj = {
a: 1
}
obj.b = obj + obj.a
This will give the same result as you seem to expect from the code above.
Related
TL;DR
How do I make {...myCls} return {...myCls.instanceVar}?
Actual Question
I'm trying to implement a custom version of *[Symbol.iterator]() { yield this.internalObj; } such that object-spreads of my class perform an object-spread operation to myClass.instanceVar.
Specifically, I want to make {...(new MyClass('a', 'b'}))} return {...(new MyClass('a', 'b'})).innerVal}. However, it seems we cannot override object-spread logic, we can only override array-spread logic.
For example, this is a simple class to create an Array wrapper
class MyArray {
innerArray = [];
getItem(index) {
return (index < this.innerArray.length) ? this.innerArray[index] : null;
}
setItem(index, val) {
const innerArrayLength = this.innerArray.length;
return (index < innerArrayLength)
?
this.innerArray[index] = val
:
this.innerArray.push(val)
;
}
removeItem(index) {
return this.innerArray.splice(index, 1);
}
clear() {
this.innerArray = [];
}
get length() {
return this.innerArray.length;
}
*[Symbol.iterator]() {
return yield* this.innerArray;
}
}
// Usage:
let myArr = new MyArray() // undefined
myArr.setItem(0, 'a') // 1
myArr.setItem(10, 'b') // 2
console.log([...myArr]) // (2) [ 0 => "a", 1 => "b" ]
However, what I want is a way to do that with object class instance variables instead of array class instance variables.
For example, this is what happens when I try to implement a StorageMock class
class StorageMock {
storage = {};
setItem(key, val) {
this.storage[key] = val;
}
getItem(key) {
return (key in this.storage) ? this.storage[key] : null;
}
removeItem(key) {
delete this.storage[key];
}
clear() {
this.storage = {};
}
get length() {
return Object.keys(this.storage).length;
}
key(index) {
return Object.keys(this.storage)[index] || null;
}
*[Symbol.iterator]() {
return yield* Object.entries(this.storage).map(([ k, v ]) => ({ [k]: v }));
}
}
let myStore = new StorageMock() // undefined
myStore.setItem('a', 'hello'); // undefined
myStore.setItem('b', 'world'); // undefined
console.log({...myStore}); // { storage: { a: "hello", b: "world" } } <== PROBLEM
// Doing the same with localStorage prints out:
// { a: "hello", b: "world" }
// instead of
// { storage: { a: "hello", b: "world" } }
In this case, the Storage API works to spread storage entries when spreading (local|session)Storage, but creating a special StorageMock class does not.
Point being that I can't make {...storageMockInstance} === {...(storageMockInstance.storage)}. So how does one override the object-spreading syntax of an ES class?
References/attempts
I've tried various combinations of Object.create(), Object.definePropert(y|ies)(), variants of the in operator (all of which have relevant access-ability defined here), all depending on the for...in syntax defininition from the generic-spreading-syntax proposal. But all I've found is that only "standard" destructuring can be used according to references 1, 2, and 3.
But there has to be a way to do this via ESNext classes. None of my attempts to accomplish the ability to use actual native class features instead of those available through AMD module syntax. It doesn't seem reasonable that I couldn't override these fields in a way similar to how other languages do so. i.e. If I could only override how the JS for..in loop works in the same way that Python allows overriding it, then I could spread the inner variable through a forIn() method just like toString() or toJSON().
Note
Please do not respond with #babel/polyfill, core-js, or babel-jest for this question. It's not only meant for (local|session)Storage, but also just a question on a high-level problem.
Short answer
You cannot.
Unless you cheat. But might not be worth it.
Actual answer
The term "array destructuring" might be a slightly misleading. It actually always starts by getting the iterator of the object and draws values from there until all bindings are satisfied. In fact, it is not only supposed to be used on arrays.
const obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
yield 4;
}
};
//1. take iterator
//2. take first three values
const [a, b, c] = obj;
//1. take iterator (do not continue the old one)
//2. take the first value
const [x] = obj;
console.log(a, b, c, x); // 1, 2, 3, 1
Object destructuring, however, does not have a similar mechanism. When using {...x} the abstract operation CopyDataProperties is performed. As the name suggests, it will copy properties, rather than invoke some mechanism to get the data to copy.
The properties that will be copied would be
Own - not coming from the prototype.
A data properties as opposed to an accessor properties (a property defined by a getter and/or setter).
Enumerable.
Not part of the excludedNames parameter to the abstract operation. Note: this is only relevant when using spread with a rest target, like const {foo, bar, ...rest} = obj;
What could be done is to lie to the runtime about each of these things. This can be done using a Proxy and you need to change the following things:
ownKeys trap to switch what keys would be used for the destructuring.
getOwnPropertyDescriptor trap to make sure the properties are enumerable.
get trap to give the value for the property.
This can be done as a function like this, for example and will make an object behave as if you are using one of its property values:
const obj = {
a: 1,
b: 2,
c: 3,
myProp: {
d: 4,
e: 5,
f: 6
}
};
const x = { ...lieAboutSpread("myProp", obj) };
console.log(x);
function lieAboutSpread(prop, obj) {
//this will be the false target for spreading
const newTarget = obj[prop];
const handler = {
// return the false target's keys
ownKeys() {
return Reflect.ownKeys(newTarget);
},
// return the false target's property descriptors
getOwnPropertyDescriptor(target, property) {
return Reflect.getOwnPropertyDescriptor(newTarget, property);
},
// return the false target's values
get(target, property, receiver) {
return Reflect.get(newTarget, property, receiver);
}
}
return new Proxy(obj, handler);
}
So, this is possible. I am however, not sure it is of that much benefit to simply doing { ...obj.myProp }. Moreover, the above function could be re-written in a way that does not "lie" at all. But it becomes extremely boring:
const obj = {
a: 1,
b: 2,
c: 3,
myProp: {
d: 4,
e: 5,
f: 6
}
};
const x = { ...lieAboutSpread("myProp", obj) };
console.log(x);
function lieAboutSpread(prop, obj) {
//this will be the target for spreading
return obj[prop];
}
In my opinion, this highlights why the artificial way of masking the object is an overkill.
I want to create a object with many properties, but, when I just console.log my object or insert it into a evaluation, it does have a default value to evaluate or log, a primitive value, like "test", for example.
I tried to use getters and setters but had no sucess.
const obj = { a: 'test1', b: 'test2' } // this is my object
console.log(obj.a); // this should return 'test1'
console.log(obj); // this should return a value of my choice, like 'testobj' or a number
'testobj' === obj; // should be true, since I want my obj to have a default value of 'testobj' in evaluations
// just like a primitive type, like strings or numbers. They have many functions and a default value
When an object is treated like a string, the JavaScript runtime will look to see if it has a toString() method and will return whatever that method indicates. If there isn't a toString(), then usually you'll see [object Object]. Also, to produce the underlying primitive value, give the object a valueOf value.
Additionally, when making objects that will be used in this way, use constructor functions, rather than object literals.
const objA = function(){ this.a= 'test1'; this.b= 'test2' }
let instA = new objA();
console.log(instA.toString());
const objB = function() {
this.a= 5;
this.b= 'test2';
this.toString= function(){ return this.a; };
this.valueOf = function() { return this.toString(); };
}
let instB = new objB();
console.log(instB.toString());
console.log(instB + 2); // Get the primitive and work with that
One year later I ran into this question again and found a solution. My objective was to create a string/number with a few methods that would use itself(this) to generate the output values and could be used in your code as a normal number/string without casting or anything like that, like a normal JS number/string.
Something like a number that has its own value but you can access the number.negative property and you will have the negative equivalent to your number. You can do it creating a class that extends the primitive type that you want to use, like, for a Number, I can use:
class Num extends Number {
negative = this * (-1);
// I can even add a few methods that receive props to calculate new output values
exponential(exp) {
let result = 1;
while(exp-- > 0) {
result *= this;
}
return result;
}
}
const myNum = new Num(3);
console.log('Simple sum: ', 1 + myNum); // outputs 4
console.log('Negative: ', myNum.negative); // outputs -3
console.log('Exponential: ', myNum.exponential(2)); // outputs 9
The code above works for strings as well. Hope I can help someone with this.
What I'm trying to do is to pass an object to a function, where certain default values may be set, and the whole object shall have a name to pass it to another function.
The following code, without naming the parameter, works just fine.
function test({
item1,
item2 = 0
}) {
console.log(item1 + " " + item2);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj.item1 + " " + obj.item2);
}
But if I now start setting obj = {...} to pass to print() I get a syntax error:
function test(obj = {
item1,
item2 = 0
}) {
print(obj);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj.item1 + " " + obj.item2);
}
If I write item2: 0, there will be no error, but then in print item2 is undefinded.
From the answers below, this seems to be the way that works best for me so far:
function test(obj) {
obj = Object.assign({
item1: undefined,
item2: 0
}, obj);
print(obj);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj.item1 + " " + obj.item2);
}
Destructuring extracts properties from an object passed to the function and puts those properties into standalone variables - that's all. What you're trying to do is mutate one of the parameters, not extract properties from the parameter into standalone variables.
You can't mutate parameters inside a parameter list - for the sort of logic you're looking for, you'll have to do it inside the function body of test:
function test(obj) {
if (!obj.hasOwnProperty('item2')) {
obj.item2 = 0;
}
print(obj);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj.item1 + " " + obj.item2);
}
If you have lots of properties you want to assign default values to, you can use Object.assign:
function test(obj) {
const filledObj = Object.assign({
item2: 0,
item3: 'item3'
}, obj);
print(filledObj);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj);
}
If you only want an object with certain properties to pass to print, then extract those properties in the parameter list like you're doing originally, then pass a reconstructed object of only those properties to print:
function test({
item1,
item2 = 0
}) {
const obj = { item1, item2 };
print(obj);
}
test({
item1: "foo"
});
function print(obj) {
console.log(obj.item1 + " " + obj.item2);
}
= is invalid for assigning values in javascript, you are looking for {key: value}.
Change the = to : to fix the error:
// You want to destructure you object, drop the `obj =`
function test({item1,item2 = 0}) {
// If you were to modify this data,
// use `Object.assign({}, {item1,item2})` to prevent mutating your data
print({item1,item2});
}
// save some space by destructuring the items you want
function print({item1, item2}) {
console.log(`${item1} ${item2}`);
}
// Initial expected result
test({item1: "foo"});
Object Destructuring vs Default Values
I am assuming that you expect the value of item2 to equal 0 correct? It does not equal 0 because you are passing in a new object that overrides the object in your function parameters.
Just like how if you were to set:
function(a = 1){}
and pass in a value, a will no longer equal 1 because it has been replaced with a new value.
The reason you get your expected behavior in your first code snippet (without having obj = {...}) is because you are destructuring an object. This is not an assignment, rather, you are extracting pieces that you want from the object.
When you have a function with the arguments like:
function({arg1, arg2}){}
JavaScript will pull out these keys from an object you pass in.
Assigning default values
If, on the other hand, if you want to pass in an object without destructuring you may do it like so:
function(obj = {a: 'default'}){}
But, if you pass in an object in the function directly above, the default value for the object and all of its keys (a or any others) will be replaced with whatever object you pass in. Here is a link on how default parameters work in javascript.
I highly recommend you take a gander at destructuring, it is incredibly useful when dealing with objects or arrays in javascript.
Hope this helps,
This question already has an answer here:
Where can I get info on the object parameter syntax for JavaScript functions?
(1 answer)
Closed 4 years ago.
I recently came across this nifty piece of JavaScript
and I am struggling a bit to understand how it works, and specifically this part :
Array.from(e.attributes, ({name, value}) => [name, value])
Here, we deal with a NamedNodeMap which is a collection of Attr objects, and an Attr does have properties named name and value among many others.
So, we have an anonymous function that takes an object and returns an array. So far, so good.
What I don't quite get is the way the argument of the function is defined as the litteral object {name, value}.
I was able to isolate the behavior :
> o={ a: 1, b: 2, ldfk: 'mùl' }
> let objToArr = function({a,b}){ return [a,b] }
> objToArr(o)
[ 1, 2 ]
>
> o = {'dfklj':3.14, 'c':'z', 'a':1, 'foo':'bar', 'b':2, 'z':26 }
{ dfklj: 3.14, c: 'z', a: 1, foo: 'bar', b: 2, z: 26 }
> objToArr(o)
[ 1, 2 ]
>
but I still don't understand why it works. Could someone please explain or refer me to the appropriate documentation ?
What you are looking for is a destructuring assignment, where an object is assigned to an object literal with only the keys, you need to take.
var object = { name_: 'foo', value: 42 },
{ name_, value } = object; // name is a property of window
console.log(name_);
console.log(value);
What I don't quite get is the way the argument of the function is defined as the litteral object {name, value}.
This is called destructuring assignment JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Your not exactly defining an object literal as an argument, but rather you are destructuring the object. You can think of deconstruction as another way of accessing the properties of an object.
For example, if you have an object a:
const a = {
name: 'foo',
value: 'bar'
}
You can get its properties in a number of different ways:
Through bracket notation:
const name = a["name"];
const value = a["value"];
Via dot notation:
const name = a.name;
const value = a.value;
Or via destructuring assignment:
const {name, value} = a; // name & value are variables which you can use
const a = {
name: "foo",
value: "bar"
}
var name = a["name"];
var value = a["value"];
console.log(name, value);
var name = a.name;
var value = a.value;
console.log(name, value);
var {name, value} = a;
console.log(name, value);
Thus, when you use {name, value} in your function arguments, you are effectively telling javascript to extract the name and value properties from the object passed in as the argument.
I was reading somewhere that when we pass an object into a function "...JavaScript always uses the Object by reference when it passes as argument..." What I think this implies is (correct me if I'm wrong) is that if the function was to modify the object in some way, it would change the original defined object. I tried illustrating this with some code and it does do what I think it does but when I try the example in the blog post with a Number obj, it doesn't change the original value in that object. Please see my jsbin: https://jsbin.com/wociro/edit?js,console,output
console.clear();
/**myobject Object**/
function myobject() {
this.value = 5;
}
var o = new myobject();
console.log("Original value of o: " + o.value); // o.value = 5
function objectchanger(fnc) {
fnc.value = 6;
}
objectchanger(o);
console.log("New value of o: " + o.value); // o.value is now equal to 6
/*Number Object*/
var num2 = new Number(2);
console.log("Original value of num2: " + num2);
function numberChanger(fnc) {
return fnc + 1;
}
console.log("num2 after running numberChanger: " + numberChanger(num2));
console.log("New value of num2: " + num2); //looks the same
Am I missing something?
Number objects are still objects. So their value is a reference, and if a function alters a property of an object passed as an argument, that object will be affected outside the function.
function changer(obj) {
obj.foo = 'bar';
}
var num = new Number(123);
console.log(num.foo); // undefined
changer(num);
console.log(num.foo); // 'bar'
However, the value wrapped inside the number object is not stored as a property. It's stored as a [[NumberData]] internal slot. ECMAScript provides no way to alter that slot, so you can't change the number.
Your attempt of fnc+1 unwraps the number object to get its [[NumberData]], and adds 1 to that. But the result is just discarded, it's not stored back in the [[NumberData]] slot of fnc.
If you want to be able to achieve something analogous to changing the [[NumberData]], you can
function MyNumber(num) {
this.__number__ = +num;
}
MyNumber.prototype = Object.create(Number.prototype);
Object.getOwnPropertyNames(Number.prototype).forEach(function(prop) {
var desc = Object.getOwnPropertyDescriptor(Number.prototype, prop);
if(desc && desc.value && typeof desc.value == 'function') {
var native = desc.value;
desc.value = function() {
return native.apply(this.__number__, arguments);
};
Object.defineProperty(MyNumber.prototype, prop, desc);
}
});
var num = new MyNumber(123);
console.log(+num, num+'', num.toFixed(2)); // 123, "123", "123.00"
num.__number__ = 456;
console.log(+num, num+'', num.toFixed(2)); // 456, "456", "456.00"
I actually had a lot issues when I started getting into the object side of JavaScript myself. Best way I can explain is by these examples.
Objects link.
var obj = {a: 5};
var b = obj.a;
b = 2;
// obj.a: 2
// b: 2
This will link to the object value I believe. So if you change b it will also change obj.a.
HTML DOM object link with odd behavior
var x = document.getElementById("some_div_id");
x.innerHTML = "example"; // this works
var x = document.getElementById("some_div_id").innerHTML;
x = "example"; // this doesn't, it thinks that it's document.getElementById("some_div_id");
Took me time to figure what was wrong when I first did the second DOM method.
Variables are not linked but copied.
var a = 5;
var b = a;
b = 2;
// a: 5
// b: 2
As you can see, this doesn't link the value but creates a new one based from it.
Deep copying from objects trick.
function deepCopy(objValue) {
return JSON.parse(JSON.stringify(objValue));
}
var obj = {a: 5};
var b = deepCopy(obj.a);
b = 2;
// obj.a: 5
// b: 2
This was a trick given to me some time back when I had issues wanting a object value being stored in a variable and edited but without it being linked to the object value. After a while I found I never needed it after improving my coding skills.
Also last note. I read somewhere in clean JavaScript coding that you shouldn't need to use the new object method unless it's a Date() object or or simulated class, or you may run into typeof and value check issues with ===.
Can't be certain if this is error free but hope this helps explains better.
In Javascript, objects refer to an array, indicated by [] or an object {}. You can verify the type of the variable by using typeof. These are passed by reference.
typeof [2, 5, 3] //object
typeof { a: 10} // object
If you pass the object literal to a function and modify the value of the property 'a', it would result in the value being modified.