When composing objects with functions, what's the proper way to pass parameters within the composition object?
For example:
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greetWorld = {
'id': 2,
'speak': greetPerson('World') // how do I go about passing the function a parameter here?
};
const greetMars = {
'id': 3,
'speak': greetPerson('Mars')
};
// register and run each
const salutations = [hi, greetWorld, greetMars];
for (let salutation of salutations) {
salutation.speak();
}
The output when I run this is:
$ node sayHi.js
Hello there, World!
Hello there, Mars!
Hi!
/Users/rotarydial/sayHi.js:21
salutation.speak();
TypeError: salutation.speak is not a function
How do I handle this properly so I can pass greetPerson() a parameter in the greet object?
In the code, greetPerson('World'), greetPerson('Mars') were getting executed at the time of object creation only.
const greetWorld = {
'id': 2,
'speak': greetPerson('World')
};
was getting evaluate to below after execution
const greetWorld = {
'id': 2,
'speak': undefined // as it was getting execute immediately, and function by default returns undefined if nothing explicitly returned from it.
};
Fix -
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greetWorld = {
'id': 2,
'speak': () => greetPerson('World') // how do I go about passing the function a parameter here?
};
const greetMars = {
'id': 3,
'speak': () => greetPerson('Mars')
};
// register and run each
const salutations = [hi, greetWorld, greetMars];
for (let salutation of salutations) {
salutation.speak();
}
I was able to get this working by declaring the function with parameter as an anonymous function, like this:
'speak': () => greetPerson('World')
I think this may be what you're looking for:
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greet = {
'id': 2,
'speak': greetPerson // pass in a reference to the function for later use.
};
// register and run each
const salutations = [hi, greet];
for (let salutation of salutations) {
salutation.speak("World!");
}
I suggest you read the MDN Working with Objects page, particularly the section Defining Methods
Here's an example from that page:
objectName.methodname = functionName;
var myObj = {
myMethod: function(params) {
// ...do something
}
// OR THIS WORKS TOO
myOtherMethod(params) {
// ...do something else
}
};
Related
here is by JS code:
var a = { depth: object_with_many_attributes.depth };
notice how depth appear twice, any standard way to rewrite by using depth just once?
You can use Object Destructuring, but in that case, you need to define additional variables, like in an example
const object_with_many_attributes = { depth: 'xxx', 'somethingOther': 'qwerty'};
const { depth, somethingOther } = object_with_many_attributes;
const a = { depth, somethingOther };
If depth must be used only once, you can destructure it with key:
// assignment
var object_with_many_attributes = { depth: 'foo', bar: 123 };
// destructure it:
var { 'depth' : a } = object_with_many_attributes;
console.log('object a:', { a }); // use as object later
I would like to write a proxy object to automatically print errors when calling some property in original object which is not found.
const proxyObjectFn = () => {
const _obj = Object.assign({}, originalObject);
const get = (key) => {
const value = _obj[key];
if (value === undefined) {
console.error(`${key} not found`);
}
return value;
};
return {
get,
};
};
const proxyObject = proxyObjectFn();
export default proxyObject;
// caller
proxyObject.get('someProperty')
This works, but is there any elegant way so that I can call through proxyObject.someProperty instead of proxyObject.get('someProperty')?
Update
Let me make it more specific. Actually I am writing a translation object.
Original object may be from json, like { "HELLO_KEY": "Hello World" }. I am to call like { label: _t.SOME_I18N_KEY } in UI display code, assuming _t is the proxy object above. I can print the warning to tell me there is missing translation.
You can use the Proxy object:
const handler = {
get: (obj, prop) => {
if(!obj.hasOwnProperty(prop)) console.error(`${prop} not found`);
return obj[prop];
}
};
const _t = new Proxy({ "HELLO_KEY": "Hello World" }, handler);
console.log(_t.HELLO_KEY);
console.log(_t.SOME_NONEXISTENT_KEY);
I have a function which has a default configuration stored in an object. The function takes optional argument "options", if there is any configuration in the options argument, it should overwrite the default config.
This is my implementation:
const myFunction = (props) => {
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
};
if (props.options) {
for (const property in options) {
config[property] = options[property];
}
}
};
export default myFunction;
in other file I would do something like this:
import myFunction
const options = {minDistance: 50, slope: 3};
myFunction({arg1, arg2, options});
Is this solution a good practice? Is there a standard way how to store default values and overwrite them with optional argument?
Also, I am getting an eslint(guard-for-in) warning, could my code cause any bugs?
Is there a standard way how to store default values and overwrite them with optional argument?
Yes, there's a standard way to do this, via Object.assign
const myFunction = (props) => {
const config = Object.assign({
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
}, props.options);
// ...
};
export default myFunction;
There's also a newer Rest/Spread syntax available, which Alberto's answer shows.
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
... props.options
};
Is this solution a good practice?
Overriding default config this way is fine. My concern would be with putting other arguments on your options object. If your other arguments are required, you might want to make them official parameters in their own right, and have the options be a last (optional) parameter.
const myFunction = (arg1, arg2, props) => ...;
const options = {minDistance: 50, slope: 3};
myFunction(arg1, arg2, options);
Also, I am getting an eslint(guard-for-in) warning, could my code cause any bugs?
It could. As the docs point out, it's possible that someone could add properties to the prototype for whatever object type you pass in as your options, and you'd be picking up those prototype properties in addition to whatever properties were added directly to the object. This would be bad practice, but it's not unheard of:
// someone else's code, maybe in a third-party library that thought it was being clever.
Object.prototype.foo = "bar";
// Your code
for(const prop in {"1": 2}) { console.log(prop); }
Output:
1
foo
You could use the spread operator to override properties if the props.options exist in the function call, like this:
const myFunction = (props) => {
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
...props.options
};
};
export default myFunction;
You are violating the Single Responsibility Principle, and are better off creating a class to set, update, and return your config code:
class configClass{
config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null
}
getConfig(){
return this.config
}
updateConfig(options){
for(const property in options) {
this.config[property] = options[property]
}
}
}
//export default configClass
//import configClass as myClass
const myClass = new configClass()
const options = {minDistance: 50, slope: 3}
const obj = {arg1:"foo", arg2:"bar", options: options}
myClass.updateConfig(obj.options)
console.log(myClass.getConfig())
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
You can call this function without any parameters and the default values will be applied:
const myFunction = ({
slopeModifier = 2,
minDistance = 30,
lockScrollIfHorizontal = true,
callback = null
} = {}) => {
...
};
export default myFunction;
On your other file, you can call the function like this:
import myFunction
myFunction({minDistance: 10}); // or myFunction();
Example
const myFunction = ({
slopeModifier = 2,
minDistance = 30,
lockScrollIfHorizontal = true,
callback = null
} = {}) => {
console.log(slopeModifier, minDistance);
};
myFunction();
myFunction({minDistance: 10});
Came across this piece of code recently:
const fetchMock = async (name, type="success") => {
const response = await fetch(someUrl);
let mock = await response.json();
mock = mock[type];
mock.json = () => mock.data;
return mock;
}
I have trouble understanding why, on line 5, we use a function to assign the mock.data to mock.json. Can't we simply write mock.json = mock.data? How is it different?
P. S. I don't have any idea about the kind of data it is receiving.
Your question has nothing to do with the async JSON fetching stuff. In the end, mock is just an object which has a data property. It also needs a json property that yields the data property.
So a reduced code sample would look like this:
const mock = {
data: {
"someKey": "someValue"
}
};
mock.json = () => mock.data;
Assume that mock.json is set exactly once, and mock.data is mutated or updated. Then, mock.json = mock.data will only work correctly if mock.data is an object that stays the same reference.
const mock = {
data: {
"someKey": "someValue"
}
};
mock.json = mock.data;
console.log(mock.json); // { "someKey": "someValue" }, as expected
// …
mock.data.someKey = "someOtherValue"
console.log(mock.json); // { "someKey": "someOtherValue" }, as expected
// Either of these reassignments will break up the connection between `mock.json` and `mock.data`:
mock.data = {"newKey": "something"};
mock.data = "Something else";
console.log(mock.json); // { "someKey": "someOtherValue" }, no longer updated
This doesn’t matter for mock.json = () => mock.data, though, as the function just returns the current value of mock.data.
I want to have a function with default parameters inside nested objects, and I want to be able to call it either f() or specifying only individual parameters.
// A function with nested objects with default parameters:
function f({ a = 1, callback = ({ name, param } = { name: "qwe", param: 123 }) } = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
// And I want to run it like this:
f();
f({ callback: { params: "456" } });
// But 'callback.name' becomes undefined.
When destructuring is mixed with default parameters, I admit the code is hard to read and write (especially when there are nested objects...).
But I think you are trying to do that:
function f({callback: {name = "cbFunction", params = "123"} = {}} = {}) {
console.log(name);
console.log(params);
}
f();
f({callback: {params: '789'}});
I found none of the answers here to be what he wanted. But it IS actually possible in a somewhat sexy way by doing this:
(EDIT: Simplified syntax and also show how to add default values for subobjects)
function f({
a = 1,
callback = {}
} = {}) {
callback = { // default values
name: "cbFunction",
params: "123",
...callback // overwrites it with given values
}
// do the same for any subobjects
callback.subObject = {
arg1: 'hi',
arg2: 'hello',
...callback.subObject
}
console.log("a:", a)
console.log("callback:", callback)
}
f()
f({a: 2, callback: {params: '789', subObject: {arg2: 'goodbye'}}})
Turned out to call it like this solves the problem, but is it the best way?
function f({
a = 1,
callback = ({
name,
param
} = {
name: "qwe",
param: 123
})
} = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
f();
f({ callback: { name, params: "456" } });
Answer by #Badacadabra is nearly correct but missing the other top level parameter specified in the question.
function f({a = 1, callback: {name = "qwe", params = "123"} = {}} = {}) {
console.log(a);
console.log(name);
console.log(params);
}
However note that within the function body the properties of callback are addressed without the containing object. You could reconstitute them into such an object if you wanted with the line:
const callback = { name, params }
Either way, from the invocation point this works to fill in all missing values from all levels such as:
f({a: 2})
f({a: 2, callback: { name: "abc"}})
f({a: 2, callback: { params: "456" }})
etc.
EDIT
In response to Joakim's comment:
TotalAMD also said in a comment that "I want to use several nested objects with same fields name". So if he tries that approach with callback1 and callback2 as arguments then he would have to use different field names in them.
I missed that original requirement. One way to maintain the desired, duplicated nested names within the function interface would be to alias them within the scope of the function, as follows:
function f({
a = 1,
callback1: {name: name1 = "abc", params: params1 = "123"} = {},
callback2: {name: name2 = "def", params: params2 = "456"} = {},
} = {}) {
console.log(a);
console.log(name1);
console.log(params1);
console.log(name2);
console.log(params2);
}
You can then call the function with the designed interface and expected results:
f ({ callback1: { name: "One" }, callback2: { name: "Two" } })
Caveat: Whilst technically possible and potentially useful, this could get messy at deeper nesting levels. It might then be worth looking for an alternative interface design with less indirection.