Javascript getter/setter scope access [duplicate] - javascript

This question already has an answer here:
Object.assign getters and setters in constructor
(1 answer)
Closed 3 years ago.
I'm trying to use JS with classic prototypical inheritance, instead of the new ES6 class model, mainly to be able to access the closure scope.
In the example bellow, I want to expose a variable current declared inside the function Counter trough this object created by the new operator.
function Counter(start, stop) {
var current = start;
function inc() { if (current < stop) return current++ }
function getCurrent() { return current }
Object.assign(this, { inc, getCurrent,
get current() { return current }, set current(value) { current = value }
})
}
counter = new Counter(0, 3)
while ((v = counter.inc()) !== undefined)
console.log(counter.getCurrent(), counter.current)
I expected the following output:
1 1
2 2
3 3
Because counter.current & counter.getCurrent() should both return the same result. But instead, I'm receiving
1 0
2 0
3 0
If I replace that Object.assign(...) with the code bellow, it works as expected.
Object.assign(inc, getCurrent })
Object.defineProperty(Counter.prototype, 'current',
{ get: () => { return current }, set: (value) => { current = value }
I could use this model, (and currently using), but I would like to use the former, because is simpler and less verbose. It seems that there are 2 different scopes here.
I tested with node 10, Chrome 73 & Firefox 68, and received the same results.
What I'm missing here?
In the example above, I tried to be as terser as I could. But to be more precise and better illustrate the point, follow a more complete test, with some commented things I've tried.
Here, I renamed the variable current to _current in order to avoid confusion with the current property, but that shouldn't be obligatory.
function Counter(start, stop) {
var _current = start;
function inc() { if (_current < stop) return _current++ }
function getCurrent() { return _current }
// Object.assign(this.constructor.prototype,
// Object.assign(this.__proto__,
// Object.assign(Counter.prototype, {
Object.assign(this, {inc, getCurrent,
get current() { return _current }, set current(value) { _current = value }
// get current() { return current } // supposed to be read-only, but not
})
// This works as expected
// Object.defineProperty(Counter.prototype, 'current',
// { get: () => { return _current }, set: (value) => { _current = value } })
}
counter = new Counter(0, 3)
while ((v = counter.inc()) !== undefined) {
console.log(counter.getCurrent(), counter.current)
counter.current -= 0.5
}
the output of the code above is:
1 0
2 -0.5
3 -1
Where that counter.current -= 0.5 is storing its value?

When your code calls Object.assign(), it's passing in an object that has a getter and setter for current. Thus the Object.assign() process will itself invoke that getter to get the value for the property "current" while it's copying the property values to the new object. Thus, the Counter object ends up without the getter and setter, which explains your results. Its "counter" property is just a simple property with a copy of the value of the local counter variable at the time the constructor code ran.
Object.assign() just copies property values, accessing them the same way any other code would.
Note that if you don't call Object.assign() at all, and just return the object you're passing into it, you'll get a working object that behaves like you expect.

Related

Pass variable into function to be defined [duplicate]

This question already has answers here:
What's the difference between passing by reference vs. passing by value?
(18 answers)
Closed last year.
EDIT 2: Why has this very specific question been marked as a duplicate of this very conceptual one: What's the difference between passing by reference vs. passing by value?. If someone else had the same question as me, they wouldn't end up with this one on Google by searching for it and - even if they did - it wouldn't answer their question.
EDIT: Thanks everyone for your help so far. Pretty difficult stuff to understand from my point of view. The reason I'm doing this is that I've set up a function which I'm calling multiple times and in which I define a variable (a unique one with each call). I need to be able to refer back to each unique variable afterwards. Here is my actual attempted code below. What would the right way to do this be?
let newSeq1
let newSeq2
function sequenceClip(sample, length, sequenceVariable) {
let currentPosition = Tone.Transport.position
let whenToStart
if (currentPosition === '0:0:0') {
whenToStart = '0:0:0'
} else {
const barToStartOn = +currentPosition.slice(0, currentPosition.indexOf(':'))
whenToStart = `${barToStartOn + 1}:0:0`
}
sequenceVariable = new Tone.Sequence((time, note) => {
sampler.triggerAttackRelease(note, '4m', time)
}, [sample], length).start(whenToStart);
}
loop1.addEventListener('mousedown', () => {
sequenceClip("C3", '1m', newSeq1)
})
loop2.addEventListener('mousedown', () => {
sequenceClip("C#3", '4m', newSeq2)
})
How do I pass a variable into a function in Javascript to be assigned a value. E.g.:
Why does the variable not get assigned the value 5? And what's the way around this?
let a
function defineVariable(var2beDefined) {
var2beDefined = 5
}
defineVariable(a)
console.log(a === 5)
You would typically write an initializer function that returns a value and assign that return value to the relevant global variable. For example:
let newSeq1
let newSeq2
function sequenceClip(sample, length, sequenceVariable) {
let currentPosition = Tone.Transport.position
let whenToStart
if (currentPosition === '0:0:0') {
whenToStart = '0:0:0'
} else {
const barToStartOn = +currentPosition.slice(0, currentPosition.indexOf(':'))
whenToStart = `${barToStartOn + 1}:0:0`
}
return new Tone.Sequence((time, note) => {
sampler.triggerAttackRelease(note, '4m', time)
}, [sample], length).start(whenToStart);
}
loop1.addEventListener('mousedown', () => {
newSeq1 = sequenceClip("C3", '1m')
})
loop2.addEventListener('mousedown', () => {
newSeq2 = sequenceClip("C#3", '4m')
})
Note that both newSeq1 and newSeq2 will be undefined until the first mousedown/mouseup events.
Reassigning an identifier, by itself, never has any side-effects (in most circumstances) - the only change resulting from someIdentifier = someNewValue will be when other parts of the code reference that same someIdentifier.
If you want to pass in something to be assigned to, pass in a function which, when called, assigns to the outer variable.
let a;
function defineVariable(callback) {
callback(5);
}
defineVariable(newVal => a = newVal);
console.log(a === 5)
The only time that assigning to an identifier will have side effects is if:
Inside a function with a simple argument list, you reassign one of the parameters (in which case the arguments object will be changed as well)
You're using ES6 modules, and you reassign an identifier that's being exported, in which case other modules that import it will see the change as well

Can I use a property of an instance to update other properties?

I'm trying to update a property of a constructor based on other property values that are getting changed when a certain function fires.
I've tried to create a function as a property and I will still get the same result.
class Rps {
constructor () {
this.userChoice
this.appChoice
this.userPoints = 0
this.pcPoints = 0
this.score = `${this.userPoints} - ${this.pcPoints}`
console.log(score)
this.registeredChoice = []
}
}
After I call a function that increments the user and pc Points, the properties will be updated after i try to console.log() the new object but it will not be the same for the score. The value of the score will not change.
A getter is perfect for this:
class Rps {
constructor () {
this.userChoice
this.appChoice
this.userPoints = 0
this.pcPoints = 0
this.registeredChoice = []
}
get score() {
return `${this.userPoints} - ${this.pcPoints}`
}
}
Alternatively, don't just update properties of the object in that function you have, but call setUser(points) and setPc(points) methods that can update the score property.

Class static getter which returns class in Javascript

I have some code:
//Main class
class Tools {
//Subclass implemented by static getter
static get SUPPORTED_UNITS () {
return class {
//Another one
static get Size () {
return class {
//Some data (constants in my case)
};
}
};
}
}
I want to use it like Tools.SUPPORTED_UNITS.Size and I want to receive some data as a result of it.
I use WebStorm for writing JavaScript code, so it shows me this when I tried to use it.
I don't know exactly, maybe I just haven't done some setup to my WebStorm, but.. What I need to do that I can use construction like Class.Subclass.SubClassMethod/SubSubclass.
I need that:
It be not a method
It be static
It be immutable (without Object.freeze or something like that) - so I can't change my Class.Subclass by typing Class.Subclass = foo;
It be indexing by IDE - so I have some help from IDE (like I type Class.Su and she suggest Class.Subclass)
Is it real in ES6 and WebStorm? Or maybe I have to use ES7 and Babel or someting like that?
P.S. That code is working, but I need suggestions from IDE or it will be like a hell.
Following #estus and #Felix Kling suggestion... (likely being misrepresented here)
const TOOLS = {
get SUPPORTED_UNITS() {
return {
get SIZE() {
return {
get Bar() {
return 1;
}
}
}
}
}
};
console.log(TOOLS.SUPPORTED_UNITS.SIZE.Bar) // 1;
TOOLS.SUPPORTED_UNITS.SIZE = "Hello";
// TOOLS.SUPPORTED_UNITS.SIZE = "Hello";
// ^
// TypeError: Cannot set property SIZE of #<Object> which has only a getter
That works but as remarked by #bergi, it creates a new object in every lookup:
console.log(TOOLS.SUPPORTED_UNITS === TOOLS.SUPPORTED_UNITS); // false
Yep...
An alternative still using getters would be encapsulate all those objects in an IIFE:
const TOOLS = (function () {
const SIZE = {
get Bar() { return 1; }
}
const SUPPORTED_UNITS = {
get SIZE() {
return SIZE;
}
}
return {
get SUPPORTED_UNITS() {
return SUPPORTED_UNITS;
}
}
}());
console.log(TOOLS.SUPPORTED_UNITS.SIZE.Bar === TOOLS.SUPPORTED_UNITS.SIZE.Bar); // true
console.log(TOOLS.SUPPORTED_UNITS.SIZE === TOOLS.SUPPORTED_UNITS.SIZE); // true
console.log(TOOLS === TOOLS); // true
TOOLS.SUPPORTED_UNITS = "hello"; // still throws an error
Third update:
The second example solves the issue related to creation of a new object every lookup but it also does not take in consideration that the access to every nested property results in a function call to reevaluate something that is not supposed to change:
//A naive measurement:
for(let counter = 0; counter < 5; counter++) {
let start = Date.now();
for(let i = 0; i < 200000; i++){
let somevar = Tools.SUPPORT_UNITS.SIZE;
}
let end = Date.now();
console.log(`Elapsed using nested getters: ${end - start}ms.`);
}
// Something like:
/** Elapsed using nested getters: 20ms.
* Elapsed using nested getters: 14ms.
* Elapsed using nested getters: 9ms.
* Elapsed using nested getters: 10ms.
* Elapsed using nested getters: 10ms.
*
// While the same as ordinary properties:
* Elapsed ordinary properties: 2ms.
* Elapsed ordinary properties: 5ms.
* Elapsed ordinary properties: 1ms.
* Elapsed ordinary properties: 0ms.
* Elapsed ordinary properties: 0ms.
*/
So it is also better to check alternatives before using this approach if running 200000 times loops is in the foreseeable future.

Javascript Setter Being Bypassed

Say I have this code:
function test() {
this._units = {};
}
test.prototype = {
get units() { return this._units; },
set units(val) {
this._units = val;
for (var unit in this._units) {
if (this._units[unit] === 0)
delete this._units[unit];
}
}
};
Now, I can assign a unit via the following:
x = new test();
x.units['foo'] = 1;
This all works. However, when I do this,
x.units['foo'] = 0;
// x.units = { 'foo' : 0 }
It doesn't remove foo from the units as it should. How can I change this?
Thanks!
You cannot intercept when a property is created, you'd need a Proxy for that. Unfortunately, it is only a harmony draft and currently only supported in Firefox' Javascript 1.8.5.
Your setter only detects an assignment to x.units = {foo:1}, but not to x.units.foo. You could create setters for every property on the units objects that you know of, but you need an extra method to make them known in the first place (assuming you're not only assigning objects to x.units).
However, you might better not do this at all. A property that deletes itself when being set to 0 is very counterintuitive. Think of
x.units.foo = -1; x.units.foo += 2;
Would you expect this to do the same as
x.units.foo = -1; x.units.foo++; x.units.foo++;
? The second would yield NaN instead of 1.

strange Javascript notation - calling a function

I am trying to use some code from this tutorial and it contains some strange javascript notation that I am not familiar with chart.attr = function(name, value) {... . More than it being unfamiliar to me, it is throwing errors. I am trying to figure out how it can be changes to work in pure javascript.
function LineChart(config) {
function chart() {
// Draw the line.
chartContainer.append("path")
.datum(p.data)
.attr("class", "line")
.attr("d", line);
}
// **** This is the notation I do not understand, and gives me errors ****
chart.attr = function(name, value) {
if (arguments.length == 1)
{
return p[name];
}
else if (arguments.length == 2)
{
p[name] = value;
}
return chart;
}
chart.update = function() {
}
return chart;
}
Your code is trying to use a variable p which is undefined. It should be defined in the LineChart function as:
function LineChart(config) {
var p =
{
parent : null,
labels : [ "X", "Y" ],
...
};
...
}
As for the notation that you don't understand, this is an anonymous function expression which is being assigned to the chart.attr property. Even though it can be called by chart.attr(), this is still an anonymous function because it doesn't have a name.
The purpose of this particular function is to be a getter and setter for properties of the p object. It looks at the arguments to determine the way the function should behave: if there is only one argument, then it needs to return the property value, if there are two arguments then it should set the property value.
Example usage would look like:
var c = new LineChart();
var parent = c.attr('parent'); // get the value of the parent property
c.attr('parent', $('#something')); // set the value of the parent property
Let's dissect that line of code:
//Define chart.attr as a function that by default takes 2 parameters;
chart.attr = function(name, value) {
//If the function only gets 1 argument (so the first one)
if (arguments.length == 1)
{
//return the element with key "name" from the array p
//effectively a getter
return p[name];
}
// else, check if there are 2 arguments, but no more
else if (arguments.length == 2)
{
Assign the value of "value" to the element with key "name" from p
effectively a setter;
p[name] = value;
}
//at the end, return the chart
return chart;
}
So what this piece of code does is that if you pass only 1 argument to chart.attr(), it retrieves the value associated with that key from the array p. If you pass 2 arguments, it uses the second argument as the value of the key-valuepair from the array p with the first argument as the key.
now, without knowing the error you get, it's hard to debug this. However, the only way in which this would give an error is if p is undefined. if p doesn't contain that key, it returns null if it's a getter, and creates if it's a setter.
There is another way for this code to fail. And since the op didn't provide the error I will just speculate.
This can fail if you call, for example, chart.attr('somekey','somevalue') before chart.attr = function(name,value) { } is executed. This happens because of function hoisting...you are assigning a value to a property in this line of code. You're not defining a function...you're assigning one.
If you call chart.attr('somekey','somevalue') in the above conditions, you'll get a chart.attr is not a function error.

Categories

Resources