Replacing a function deeply nested inside an object in javascript - javascript

I have an object containing functions that are nested potentially deeply (could be more levels than this so I need a solution that will work at any depth):
x = {
a: function() { console.log ("original function 1") },
b: {
c: {
d: function() { console.log ("original function 2") }
}
}
}
I want to be able to pass the function's keypath as an argument to another function and replace the original function with a new one:
g = function( functionAddress ) {
functionAddress = function() { console.log( "replacement function" )}
}
so that after executing:
g("x.b.c.d");
x.b.c.d() // replacement function
g("x.a");
x(a) // replacement function
I've been able to process the argument using split, shift and reduce so I can access the function at the specified address as in:
keypath = x["b"]["c"]["d"]
but if I try to use newFunc to replace the function at that location, it only replaces the value of keypath, not the function at that address.
Any help would be appreciated. Thanks!

There's no straightforward way to do that. Instead, you have to implement your function to do so.
Here's a more-or-less simple way, using Array#split() and Array#reduce():
const x = {
a: function() { console.log ("original function 1") },
b: {
c: {
d: function() { console.log ("original function 2") }
}
}
}
const setDeep = (object, path, value)=>{
const pathArray=path.split('.')
pathArray.slice(0,-1).reduce(Reflect.get,object)[pathArray[pathArray.length-1]]=value
}
setDeep(x, 'b.c.d', function(){console.log('replacement function')});
x.b.c.d() //replacement function

Related

How to handle nested refrence passed to a function in javascript?

I have some objects:
var a = {
toString: () => 'a'
}
var b = {
toString: () => 'b'
}
function someFunc(...params) {
params.forEach((p)=>{
console.log(p); //consoles - {toString: ƒ toString()} for both objects
})
}
someFunc(a,b);
I want to pass these objects to some functions like memoize function, isEqual function, deepCopy function etc. I don't want to use any third party library such as lodash. I want to understand how do we differentiate between these objects inside someFunc?
I have tried : JSON.parse(JSON.stringify()) but this doesn't work in case of objects having functions.
Codesandbox
Edit:
I have tried Implementing the object refrence method.
function someFunc() {
let cache = {};
return function (...params) {
var ObjectReference = [];
let set = {};
params.forEach((p) => {
ObjectReference.push(p);
set["ObjectReference." + ObjectReference.indexOf(p)+p] = true;
});
let key = JSON.parse(JSON.stringify(set))
console.log(key);
if (cache[key]) {
console.log("cached");
} else {
cache[key] = true;
console.log("Not cached");
}
};
}
mem(a, b); //not cached
mem(b, a); //cached - should be **not cached**
console.log(key); gives:
{ObjectReference.0a: true, ObjectReference.1b: true}
{ObjectReference.0b: true, ObjectReference.1a: true}
As we can see the two objects are different. I'm unable to understand why it goes inside cached block?
Edit 2 : The above is happening because the key is getting set as [object object]. To avoid this I tried using Map and WeakMap but they are failing for
mem(a, b); //not cached
mem(a, b); // not cached

JavaScript: Write Function that take an object and an array of functions as input args and return an array

I want to take an object and an array of functions and return an array. I am trying to use the for loop here.
I have the following code below:
const fnArr = [
function firstName() {
return this.first;
},
function lastName() {
return this.last;
},
];
const obj = { first: 'Nimit', last: 'Maru' };
function callAll(obj, fnArr){
let newArray = [];
for (let i=0; i<fnArr.length; i++){
let eachFunc = fnArr[i];
return newArray.push(eachFunc.call(obj))
}
}
callAll(obj, fnArr)
My expected output is:
['Nimit', 'Maru']
But the output from my personal code is returning: 1
Question 1: What am I doing wrong here?
////////////////////////////////////////////////////////////////////////////////////////////////////////////
Additionally, the solution I was given is below:
const fnArr = [
function firstName() {
return this.first;
},
function lastName() {
return this.last;
},
];
const obj = { first: 'Nimit', last: 'Maru' };
const callAll = (obj, fnArr) => {
return fnArr.map(fn => {
return fn.call(obj);
});
};
It produces the right answer.
Question 2: In the solution code above, why do I need the call method in "return fn.call(obj)"?
A conceptual explanation of when you need or don't need call in these types of situations would be greatly appreciated.
You are returning in each loop. So after the first loop the the function ends and code doesn't execute further.
It returns 1 because push() method returns the length of the array after adding elements to it. Initally array was empty when 1 element is added it returns 1.
You don't need to necessarily use map() just push() the element(don't return). And return the newArray after loop.
const fnArr = [
function firstName() {
return this.first;
},
function lastName() {
return this.last;
},
];
const obj = { first: 'Nimit', last: 'Maru' };
function callAll(obj, fnArr){
let newArray = [];
for (let i=0; i<fnArr.length; i++){
let eachFunc = fnArr[i];
newArray.push(eachFunc.call(obj))
}
return newArray
}
console.log(callAll(obj, fnArr))
In the solution code above, why do I need the call method in "return fn.call(obj)"?
The this binding to the function depends upon how the function is called. If the function is called as the method of the object then the object will be binded to that method.
In the above code this inside your both functions will refer to window object if they are called normally. So we want this to refer the object so we use call
Why eachFunc(obj) returns [undefined, undefined]?
When you don't use call this will refer to window object. So there is not property named first and last on window object so it returns undefined

Javascript function at key value assignment in Angular 7

I am trying to find a solution for the next problem I encountered. I am trying to build a dynamic string by using Amchart3 in my project, and I need to do something like this:
return [{
id: 'Rate',
alphaField: 'alpha',
balloonText: function(graphDataItem, graph): String {
let stringNew = 'smth';
return stringNew;
},
}];
The problem I encountered is that the function written in the ballonText value isn't triggered (I put a breakpoint to check) and doesn't return anything when calling the function that uses the returned list.
My question is, how could I write a function like
key: function(a,b) {
return a + b;
}
in Angular 7, like it is done in Javascript?
Your problem is the way you assign the function 'value' to your key ballonText. If using es6, which lets you shorthand key/value names, you can assign the function like so:
const es6Assign = {
id: 'Rate',
myFunc(str) {
return str.toUpperCase();
}
}
// prints out HELLO
console.log('calling es6Assign func: ', es6Assign.myFunc('hello'));
If you want to do it the vanilla way, you should name the function even if its the value of a key:
const explicitAssign = {
id: 'Rate',
myFunc: function myFunc(str) {
return str.toLowerCase();
}
}
// prints out hello
console.log('calling explicitAssign func: ', explicitAssign.myFunc('HELLO'));
As to the why?; my guess is that even though to you the key is the 'name' of the function, to the code, a function that has no name doesn't exist, i.e. function() { ... } will return undefined. I added the case in the sample Fiddle so you can see what happens.

"Uncaught TypeError: this._isDateType is not a function" within Arrow Function

I've got every time a type error that a function definition could not be found. The code looks as follow:
return BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
onInit: function () {
console.log(this._isDateType(new Date()));
let oHbox = this.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = this._getLastDayOfMonth(oTodayDate);
},
_getLastDayOfMonth: (oBegin) => {
if (this._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
},
_isDateType: (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
},
});
The problem is the _isDateType function that could not be found when it is called inside the _getLastDayOfMonth function.
I set the break point:
and as you can see, the function is undefined and I do not know why.
At the beginning of the onInit function, I called the _isDateType function:
console.log(this._isDateType(new Date()));
and it supply the result as expected.
What am I doing wrong?
Replace the arrow function
_getLastDayOfMonth: (oBegin) => {
// this....
},
with the normal function expression:
_getLastDayOfMonth: function(oBegin) {
// this...
},
By this, the _getLastDayOfMonth can freely access other methods within the Controller instance.
Why it didn't work with arrow function
First of all, it's important to know that arrow functions bind their context lexically.
An arrow function expression has a shorter syntax than a function expression and does not have its own this. [source]
For example, it's not possible to call .bind on arrow functions. They get their this from the closure when evaluated.
Since this was not an instance of the Controller but rather the window object when BaseController.extend was called, calling this._isDateType inside the arrow function was equivalent to window._isDateType.
What you cannot do is refer to a property of an "under construction" object from elsewhere in the object literal syntax. In cases where you want to do that, you do need one or more separate assignment statements.
For example, move your code as follows:
var temp = BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
onInit: function () {
console.log(this._isDateType(new Date()));
let oHbox = this.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = this._getLastDayOfMonth(oTodayDate);
}
});
temp._isDateType = (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
};
temp._getLastDayOfMonth = (oBegin) => {
if (this._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
}
return temp;
The idea is to split function assignments into several statements;
The element this can be used inside a function to get the temporary value of the element. To use the _isDateType method you should create an attribute inside the method and fill it with the 'this' value.
return BaseController.extend("ch.micarna.weightprotocol.controller.Calendar", {
var temp= null;
onInit: function () {
temp = this;
console.log(temp._isDateType(new Date()));
let oHbox = temp.byId("calendar-container");
let oTodayDate = new Date();
let oEndDate = temp._getLastDayOfMonth(oTodayDate);
},
_getLastDayOfMonth: (oBegin) => {
if (temp._isDateType(oBegin)) {
throw new TypeError("The given parameter is not type of date.");
}
return new Date(oBegin.getFullYear(), oBegin.getMonth() + 1, 0);
},
_isDateType: (oDate) => {
return Object.prototype.toString.call(oDate) === "[object Date]";
}

Convert function parameter in string into parameter in object

I am using node.js.
I have a function that can be called this way;
add_row({location:'L1', row_name:'r1', value:'18.4'});
I have a string like this;
var str_param = "location:'L1', row_name:'r1', value:'18.4'";
I tried to do something like this to keep my code simple;
add_row(str_param);
It did not work. What is a good way to use str_param to call add_row?
You could convert the string to an object that the function accepts.
function toObj(str) {
const a = str.split(/,.?/g);
return a.reduce((p, c) => {
const kv = c.replace(/'/g, '').split(':');
p[kv[0]] = kv[1];
return p;
}, {});
}
toObj(str); // { location: "L1", row_name: "r1", value: "18.4" }
DEMO
I think this may be your issue:
{location:'L1', row_name:'r1', value:'18.4'} // Object
var str_param = "location:'L1', row_name:'r1', value:'18.4'"; // Not object
var str_param = "{location:'L1', row_name:'r1', value:'18.4'}"; // Object String
I do not use Node JS but just taking a shot in dark. If not you could just make function like:
function addRow(pLocation, pRowName, pValue) {
var row = {
location: pLocation,
row_name: pRowName,
value: pValue
}
// Logic ....
}
If that does not work try using Object string and look at function ParseJSON I believe it's called.

Categories