Setting this inside the functions Javascript [duplicate] - javascript

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 3 months ago.
I have the next code:
const obj = {
name: 'Hi',
first: () => {
return this
},
other: {
name: 'last',
sec: function() {
this.c = '2'
return function() {
this.s = '3'
return this; // expect {s:3}
}
}
}
}
const t = obj.other.sec()
console.log(t())
In console.log i expect {s:3} but there is not that object. Question: Why there is not {s:3}? Or function() should have its own context

It's not there because this in JS is dynamic and depends on the scope.
Arrow functions don't have their own scope, so obj.first() will not return you obj but instead returns the global context (in a browser, usually window) or undefined when running in strict mode!
Functions via function do have a dedicated scope. However, when you call obj.other.sec(), the context of this in the sec function points to obj.other, not obj.
The function returned by obj.other.sec() does (or rather: can) create a new scope, since it's called without a context. But since it's also called without new, this points to the global context (see point 1).
Depending on what you want to achieve, the simplest solution is to replace this with the correct context. For example, if you want all function to run in the context of obj, just replace every this with obj:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.c = '2'
return function() {
obj.s = '3'
return obj;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj` with a `c` and an `s` property added
Or maybe you want varying context's:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
obj.other.s = '3'
return obj.other;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj.other` with a `c` and an `s` property added
And in case obj.other.sec() should return a new object that is not related to obj or obj.other, then... well... just return a new object:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
return { s: 3 };
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs a new object with an `s` property
You can read more about the dynamic nature of this on MDN

Related

Javascript pass by reference to be overwritten in another file at a later date

Here is a dummy example:
const obj = {foo: '123'};
function logObj(obj) {
setInterval(function() {
console.log(obj);
}, 100)
}
function overWrite(obj) {
setTimeout(function(){
console.log('overwriting')
obj = {foo: 'bar'}
}, 1000);
}
logObj(obj)
overWrite(obj)
I'm expecting to see { foo: '123' } every 100ms until overwriting is called and then to see { foo: 'bar' }. However the object is never over-written and I always see 123.
EDIT
I do not want to change just one of the keys; I do in fact want to replace the entire object, so doing obj.foo = 'bar' is not a solution
Instead of obj = {foo: 'bar'} do obj.foo= 'bar'
const obj = {
foo: '123'
};
function logObj(obj) {
setInterval(function() {
console.log(obj);
}, 100)
}
function overWrite(obj) {
setTimeout(function() {
console.log('overwriting')
obj.foo = 'bar'
}, 1000);
}
logObj(obj)
overWrite(obj)
To get a better understanding of what's happening let's rename the variables.
const a = { foo: '123' };
function logObj(b) {
setInterval(function () {
console.log(b);
}, 1000);
}
function overWrite(c) {
setTimeout(function () {
console.log('overwriting');
c = { foo: 'bar' };
}, 5000);
}
logObj(a);
overWrite(a);
Variables passed to a function are copies of the primary value.
First you call logObj with a copy of the value of a (a reference to object { foo: '123' }). This reference will be available as b in the function.
Then you call overWrite with a copy of the value of a (a reference to object { foo: '123' }). This reference will be available as c in the function. In the callback you replace the contents of c with a new reference. However this does not effect the contents of a or b and is only available during the function body.
Here is an simplified example:
The solution is simple, change your const obj to var obj = {...}.
As you can see in this article there's a difference when declaring variables with var, const or let.
const cannot be updated or re-declared

Update fields in nested objects in Typescript / Javascript

In Firestore you can update fields in nested objects by a dot notation (https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0#update_fields_in_nested_objects). I wonder how to make that work in Typescript / Javascript.
For example the following object:
const user = {
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: true
}
},
token: {
custom: 'safghhattgaggsa',
public: 'fsavvsadgga'
}
}
How can I update this object with the following changes:
details.email.verified = false;
token.custom = 'kka';
I already found that Lodash has a set function:
_.set(user, 'details.email.verified', false);
Disadvantage: I have to do this for every change. Is their already a method to update the object with an object (like firestore did)?
const newUser = ANYFUNCTION(user, {
'details.email.verified': false,
'token.custom' = 'kka'
});
// OUTPUT for newUser would be
{
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: false
}
},
token: {
custom: 'kka',
public: 'fsavvsadgga'
}
}
Does anyone know an good solution for this? I already found more solutions if I only want to change one field (Dynamically set property of nested object), but no solution for more than one field with one method
I think you are stuck with using a function but you could write it yourself. No need for a lib:
function set(obj, path, value) {
let parts = path.split(".");
let last = parts.pop();
let lastObj = parts.reduce((acc, cur) => acc[cur], obj);
lastObj[last] = value;
}
set(user, 'details.email.verified', false);
if what you want to do is merge 2 objects then it is a bit trickier:
function forEach(target, fn) {
const keys = Object.keys(target);
let i = -1;
while (++i < keys.length) {
fn(target[keys[i]], keys[i]);
}
}
function setValues(obj, src) {
forEach(src, (value, key) => {
if (value !== null && typeof (value) === "object") {
setValues(obj[key], value);
} else {
obj[key] = value;
}
});
}
let obj1 = {foo: {bar: 1, boo: {zot: null}}};
let obj2 = {foo: {baz: 3, boo: {zot: 5}}};
setValues(obj1, obj2);
console.log(JSON.stringify(obj1));
One solution in combination with lodash _.set method could be:
function setObject(obj, paths) {
for (const p of Object.keys(paths)) {
obj = _.set(obj, p, paths[p]);
}
return obj;
}

Detecting when an object called is called by . accessor or as function call

I have the following object
let o = {
fn:()=>1,
a:2
}
if i say o() i want the call to be proxied to fn and if i say o.a the value of a should be returned, is it possible exactly as it is presented?
its more like having an unnamed default function for an object
Since functions in JS are objects, you can extend them with properties:
const o = Object.assign(() => o.fn(), {
a: 5,
fn: () => 1
});
console.log(o());
console.log(o.a);
If you need to overwrite non-writable Function properties, such as name, you can 1st create the function, and then use Object.defineProperties() to add properties, and change non-writables to writables.
Note: This feels hackish enough, without missing with the Function's non-writable properties. I recommend avoiding it.
const o = () => o.fn();
Object.defineProperties(o, {
fn: { value: () => 1 },
a: { value: 5 },
name: { writable: true, value: 'whatever' },
});
console.log(o());
console.log(o.a);
console.log(o.name);
Is it possible exactly as it is presented?
No, you can't because that's only a key-value object and not a function.
You need to declare that object as a function:
Declare the function fn within the function o.
Set an attribute called a to the current function o.
let o = () => {
fn = () => 1;
o.a = 2;
return fn();
};
console.log(o());
console.log(o.a);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Cross Communication Between Objects within an Object JavaScript

Forgive me if this has been answered before, but my searching has yielded no results. This is likely because I lack the terminology needed.
I have been working on a text based RPG as a side project and have been experimenting with closures in JavaScript. Below is a very basic example of what I am trying to accomplish:
app.js
var app = {};
This is where everything will be mounted to preserve namespace.
character.js
app.character = (function() {
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
return {
createCharacter: _createCharacter
};
})();
The character object is pretty straightforward, but what gets tricky is this next part.
items.js
app.items = {};
The items object will hold other objects; one for each item type because they all behave differently. The types will be: gear, consumable, quest. For simplicity I have only included gear.
gear.js
app.items.gear = (function() {
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
return {
createGear: _createGear
};
})();
So this object is two objects deep on the app object.
game.js
app.game = (function() {
var _player = {};
// closed functions in this area
function _giveWeapon() {
_player.weapon = this.item.gear.createGear();
}
function _newPlayer(name) {
_player = this.character.createCharacter(name);
_giveWeapon();
}
function getPlayer() {
return _player;
}
return {
getPlayer: _getPlayer,
newPlayer: _newPlayer
};
})();
This is the object that will pilot the game, i.e. call to other objects for functions as needed.
int.js
app.game.newPlayer('Ryan');
// should create { name: 'Ryan', stats: { health: 10, level: 1 }, weapon {} under game._player
This simply starts the game.
When ran it creates an error before anything even gets off the ground, because the game object can't see items.gear or character objects and returns them as undefined. What I am trying to get to is having them all able to communicate with one another inside the app object while remaining closures. Also, any advice on building single namespace complex JavaScript apps would be extremely helpful.
So I modified a few functional points, and a few aesthetic ones.
functional
- pass the app object into your game closure and use it instead of this
- change theApp.item.gear -> theApp.items.gear
aesthetic
- use function hoisting to create more skimmable code. Init your closure with the necessary variables, then return your api. After that you can declare your functions which only need to be referenced if detail is needed.
I can't help much with namespace principles since I avoided them in favor of bundlers and 'require' or more recently 'import' statements.
var app = {};
app.character = (function() {
return {
createCharacter: _createCharacter
};
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
})();
app.items = {};
app.items.gear = (function() {
return {
createGear: _createGear
};
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
})();
app.game = (function(theApp) {
var _player = {};
return {
getPlayer: _getPlayer,
createPlayer: _createPlayer
};
// closed functions in this area
function _giveWeapon() {
_player.weapon = theApp.items.gear.createGear();
}
function _createPlayer(name) {
_player = theApp.character.createCharacter(name);
_giveWeapon();
}
function _getPlayer() {
return _player;
}
})(app);
// should create { name: 'Ryan', stats: { health: 10, level: 1 }, weapon {} under game._player
app.game.createPlayer('Ryan');
var ryan = JSON.stringify(app.game.getPlayer(), null, 2);
document.getElementById('ryan').innerHTML = ryan;
<pre id="ryan"></pre>
Below code should get you going:
Basically, the reason why your code was not working earlier was that when you hit this line this.character.createCharacter(name);, the binding to this is with game but game object doesn't contain chatacter object.
Similar logic applis to the line this.item.gear.createGear();.
var app = {};
app.character = (function() {
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
return {
createCharacter: _createCharacter
};
})();
app.items = {};
app.items.gear = (function() {
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
return {
createGear: _createGear
};
})();
app.game = (function() {
var _player = {};
//this.character = character;
// closed functions in this area
function _giveWeapon() {
_player.weapon = app.items.gear.createGear();
}
function _newPlayer(name) {
//_player = this.character.createCharacter(name);
_player = app.character.createCharacter(name);
_giveWeapon();
}
function _getPlayer() {
return _player;
}
return {
getPlayer: _getPlayer,
newPlayer: _newPlayer
};
})();
app.game.newPlayer('Ryan');
console.log(app.game.getPlayer());

Why is the fat arrow in a class method not binding to the parent scope's this?

I've got an ES2015 code snippet where I'm trying to dynamically populate the objects this.data.pageCategoryL1~3 keys with different data depending on the state of the originalData. I'm passing class methods as a callback to _categoryMapper, which isn't binding the callback to class's this - it's only passing a pointer to the function without binding it even though it's a method. That alone seems weird odd that it isn't automatically bound to the instance.
The truly puzzling part though: inside of _categoryMapper's reduce function, the fat arrow function's this is undefined. I thought fat arrows were supposed to bind to their parent this's scope?
class AnalyticsData {
constructor(originalData) {
this.data = {};
this.originalData = originalData;
}
mapCategories() {
debugger;
let mappedCategories = {
pageCategoryL1: '',
pageCategoryL2: '',
pageCategoryL3: ''
};
if (this.originalData.search && this.originalData.search.refinements) {
mappedCategories = this._categoryMapper({
pageCategoryL1: 'categoryl1',
pageCategoryL2: 'categoryl2',
pageCategoryL3: 'categoryl3'
},
this._getSomeCategory); // if i bind only here it will work, because it doesn't use fat arrow's this
} else if (this.originalData.items) {
mappedCategories = this._categoryMapper({
pageCategoryL1: 'a',
pageCategoryL2: 'b',
pageCategoryL3: 'c'
},
this._getSomeOtherCategory);
}
return mappedCategories;
}
_categoryMapper(mapping, callback) {
return Object.keys(mapping).reduce((acc, key) => {
// fat arrow in reduce should be implicitly bound to this
console.log(this);
let category = callback(mapping[key]).bind(this);
acc[key] = category ? category : '';
return acc;
}, {});
}
_getSomeCategory(categoryKey) {
// No access to this as currently written
console.log(this)
let refinements = this.originalData.search.refinements;
let matchedObj = refinements.find(({
refinement
}) => categoryKey === refinement.name);
return matchedObj && matchedObj.refinement.value;
}
_getSomeOtherCategory(categoryKey) {
let id = Object.keys(this.originalData.items)[0];
return this.originalData.items[id][categoryKey];
}
}
window.x = new AnalyticsData({
search: {
refinements: [{
refinement: {
name: 'categoryl1',
value: 'yup'
}
}]
}
}).mapCategories()
console.log(x)
/* this.data should be: {
pageCategoryL1: 'yup',
pageCategoryL2: '',
pageCategoryL3: ''
};*/
You're misusing bind here.
let category = callback(mapping[key]).bind(this);
bind creates a copy of a function with this set to whatever you passed it and zero or more arguments preloaded.
function log(argument1) {
console.log(this);
console.log(argument1);
}
let f = log.bind({ a: 1 }, 'a');
let g = log.bind({ b: 2 }, 'b');
f();
g();
What you probably want to use is call which calls a function with this set to it's first argument.
function log(argument1) {
console.log(this);
console.log(argument1);
}
log.call({ a: 1 }, 'a');
log.call({ b: 2 }, 'b');
The reason this === undefined is that callback is not defined with an arrow function nor does it have any other way of defining what this should be. This is essentially what you're doing.
'use strict';
let obj = {
a: 1,
log() {
console.log(this);
}
};
function callCallback(callback) {
callback();
}
// This is what you want to happen
callCallback(obj.log.bind(obj));
// This is what you're doing
callCallback(obj.log);

Categories

Resources