CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: Question about using Map() object  (Read 147 times)

jmrker

  • Junior Member
  • *
  • Posts: 29
  • Karma: +1/-0
    • View Profile
Question about using Map() object
« on: 28 Apr 2020, 05:40:55 pm »
This is a "Can I do this..." question.

I want to use the E6 Map() object but instead of "key, value" pairs I want to define "key, function" pairs.
I have the following test code, and although the object says it has a value using  the Map.has(),
it returns an 'undefined' when I try to access it: Map[key]

Is my desire an impossible dream or can the following be modified to produce the actions desired.
I have seen this done using a regular object, but like the methods of the Map() object.

Code: [Select]
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" />
<title> Dispatch Buttons with Map() </title>
<!--
 See also: https://www.javascripttutorial.net/es6/javascript-map/
      and: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
-->
</head>
<body>
<h1> Map() dispatch test - NFG </h1>
<pre id="debug"></pre>

<script>
  console.clear();
  const doc = (IDS) => document.getElementById(IDS);
  const msg = (IDS, ...message) => doc(IDS).innerHTML += message.join(' ');

  var commandTable = new Map();       // dispatch table function definitions
      commandTable.set( 'btn1', btnAction("Hello btn 1") );
      commandTable.set( 'btn2', btnAction("Btn 2", "hello") );
      commandTable.set( 'clr' , btnClear() );
      commandTable.set( 'default', btnAction("Invalid key\n\n") );

  function btnAction() { let args=[...arguments];  doc('debug').innerHTML = args.join('\n'); }
  function btnClear() { doc('debug').innerHTML = ''; }


// Test section
  msg('debug', 'KEY'.padEnd(10," "), 'HAS   VALUE\n');
  msg('debug', 'Error     ', commandTable.has('Error'), commandTable['Error'], ' null??? \n\n');
// expect above to not be found as it is NOT defined anywhere, ie; error is expected here

// All of following returns an 'undefined' as a function for each key of commandTable (???)
  msg('debug', 'KEY'.padEnd(10," "), 'HAS   VALUE\n');
  for (let [k, v] of commandTable.entries()) {
    msg('debug', `${k.padEnd(10,' ')} ${commandTable.has(k)}  ${v}\n`);
  }

/* Following won't work because the "commandTable" functions are 'undefined' - fix this and then test
  var processCAkey = function(doWhat) {
    const thingToDo = commandTable.has(doWhat) ? doWhat : "default";
    msg('debug', '\n'+doWhat+'\t'+thingToDo+'\t'+commandTable.has(doWhat)+'\n'+commandTable[doWhat]);
    commandTable[thingToDo]();
  }
  processCAkey('btn1');  // Expect: btn1 btn1 true btnAction('Hello btn 1')    <- get 'undefined' ???
  processCAkey('error'); // Expect: error error true btnAction('Invalid key')   <- get 'undefined' ???
/* */
</script>
</body>
</html>
The last section of the code /* commented out */
is an attempt to use the commandTable key,function definitions
« Last Edit: 28 Apr 2020, 05:50:33 pm by jmrker »

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 333
  • Karma: +36/-1
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
Re: Question about using Map() object
« Reply #1 on: 28 Apr 2020, 10:17:15 pm »
First off, is this supposed to be client-side code? If so you can't use half the ES6 stuff you have in there -- like the derpy "wah wah, I don't want to type 'function'" arrow trash, or the insanely pointless LET statement with its equally herpaderp inducing of memory thrashing due to pointless "scope", the functions as variables that do not appear to be something you want to have overwriteable...

As to "Map" in this case I don't know what it provides that a simple iterable object or object of arrays does not. The big flaw in your logic is that you're CALLING the function and using what it returns as the result, instead of PASSING the function to map or whatever it is you want to run.

In your case, an Object of Array might fit the pattern better, as then you can pass the function by its name and then the value(s) to be passed to it.

Something more along the lines of:

Code: [Select]
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
<title>Dispatch Buttons with Objects</title>
</head><body>
<h1>Object dispatch test - NFG </h1>
<noscript>
<p>
This page requires JavaScript to Function
</p>
</noscript>

<script>
// put it in a IIFE to isolate scope and make 'document' easier to work with
(function(d) {

console.clear();

var

// Scripting only elements have ZERO business in your markup!
debug = d.body.appendChild(d.createElement('pre')),

// I like to store things liek colWidth once so I can adjust as needed.
colWidth = 12,

/*
Object of Arrays instead of Map, since Map seems completely the wrong
construct for what you're trying to do, not providing ANYTHING in this
case that an old-school JS object does not!

Command, then all later elements are values to pass to the command as
arguments via function.apply()
*/
commandTable = {
'btn1' : [ btnAction, 'Hello btn 1' ],
'btn2' : [ btnAction, 'Btn 2', 'hello' ],
'clr' : [ btnClear ],
'default' : [ btnAction, 'Invalid Key']
};

function append(e, value) {
e.appendChild(
'object' == typeof value ? value : d.createTextNode(value)
);
}

function btnAction() {
dWriteLn(Array.from(arguments).join(', '));
}

// perform node flush, USUALLY faster/better than innerHTML = '';
function btnClear() {
while (debug.firstChild) debug.removeChild(debug.firstChild);
}

// do this on the DOM, **NOT** innerHTML!
function dWrite() {
for (
var i = 0, iLen = arguments.length; i < iLen; i++
) append(debug, String(arguments[i]).padEnd(colWidth));
}

function dWriteLn() {
dWrite.apply(null, arguments);
append(debug, '\r\n');
}

function keyTest(key, shouldBe) {
dWriteLn(
key,
commandTable.propertyIsEnumerable(key) ? 'true' : 'false',
shouldBe ? 'true' : 'false',
commandTable.propertyIsEnumerable(key) === shouldBe ? 'passed' : 'failed'
);
}

dWriteLn('KEY', 'EXISTS', 'SHOULD BE', 'TEST');
keyTest('error', false);
keyTest('btn1', true);
dWriteLn();
dWriteLn('AVAiLABLE KEYS');
dWriteLn(Object.keys(commandTable).join(', '));
dWriteLn();

function processKey(cmd) {
// output what was requested
dWrite(cmd);
if (!commandTable.propertyIsEnumerable(cmd)) cmd = 'default';
// output what is actually being called
dWrite(cmd);
// Function.prototype.apply and Array.slice save the day here.
commandTable[cmd][0].apply(null, commandTable[cmd].slice(1));
}

dWriteLn('COMMAND', 'RUNNING', 'VALUES');
processKey('btn1');
processKey('btn2');
processKey('error'); // runs default

})(document);
</script>

</body></html>

A LOT of the new fangled stuff in ECMAScript is like a LOT of the new stuff in HTML 5. Sure, some of it is awesome, but a lot of it is redundant, convoluted, and in general makes you work harder, not smarter.

Particularly since as written I THINK this should work all the way back to legacy IE versions we no longer even give a flying purple fish about in 2020.

In production, I'd consider making a custom object that has methods for adding/removal/execution that's more in-line with the task at hand. The above is kind-of a kludge.
« Last Edit: 30 Apr 2020, 08:53:31 pm by Jason Knight »
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Junior Member
  • *
  • Posts: 29
  • Karma: +1/-0
    • View Profile
Re: Question about using Map() object
« Reply #2 on: 29 Apr 2020, 12:05:23 am »
Thanks for the reply and the alternate script.  I enjoy you code approaches and tend to learn from them.

But keep in mind, I was asking for learning purposes of the Map() object
and not necessarily for production code at this time.
I understand your point that it may not work in legacy browsers,
and I appreciate you in pointing that out
should I ever need to test those versions and wonder why the working code crashes.

I'm not sure I see the need to trash "arrow" functions and "let" statements, as they seem to work. 
Plus I can write the alternate older functions and prototypes (as you did) if really necessary,
but again this was for testing/learning of the Map() object. 
For "quick and dirty" scripts I don't mind using .innerHTML for .innerText for brief displays of results.

Sure, I could use the normal object as you propose,
but the reason for the question was to learn something new
and to understand how it works if used in projects by others.

I will study on your solution an see what new approaches I might implement.

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 333
  • Karma: +36/-1
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
Re: Question about using Map() object
« Reply #3 on: 29 Apr 2020, 01:20:05 am »
But keep in mind, I was asking for learning purposes of the Map() object
Well, the task you were trying to use it for is a total mismatch. Map would generally be best used for things like lookup tables of complex mathematical calculations.

Mind you, I said WOULD be best used, but there's a problem. JavaScript array-likes are painfully and agonizingly slow to the point that unless the calculations/operations performed are really, REALLY complex, it's faster to just run the calculation than  to store it in an Array or Map... because neither are actual arrays, they're pointered lists. This is how when you delete a JS array entry the indexes change...

Because to access the tenth element in an array via arrayLink[100] it actually sits there counting through the first 99 elements. You'd THINK they'd just make it an array of pointer, instead of array of Object, so indexing is a simple mul, but apparently that never occurred to anyone.

I really have not seen a legitimate usage case for map() that couldn't be better handled better, simpler, cleaner, and more backwards compatible by a normal every-day object.

I'm not sure I see the need to trash "arrow" functions and "let" statements, as they seem to work. 
Arrow functions will INSTANTLY make your javascript crash -- halting ALL scripts on your page -- by its mere PRESENCE in all versions of IE. Even IE11 flat out doesn't know what it is, and utterly loses its mind when it sees it.

But even legacy aside, they seem to exist for two reasons -- NEITHER of them good. One being the "wah wah, eye dunz wunna type" mental midgetry, but WORSE? They promote the use of callbacks in situations -- like the derpy "foreach" that results in fatter, slower, more memory hungry code.

EVERY time you make a callback, you're adding function call overhead to the program, and one of the fastest ways to speed things up is to stop using functions where they aren't warranted, or to just make a normal everyday plain-Jane function. In that way -- like a lot of ECMAScript of late -- it reeks of being created by people coming from other languages like Java and being unwilling or unaware of what JS is, how it works, or why it does things the way it does.

... and the result of this crap is fat, bloated, slow cryptic, hard to maintain codebases.

Same for the derpy let, 99.99% of the time serving ZERO legitimate purpose over var. Oh noes, var's not released until garbage collecttion is sure it is, and it's only scoped to function block, not every damned block. Wah wah wah. Again, crutches for the people too stupid to handle scope, and guaranteed to make you work harder, not smarter. LET -- and to a degree CONST -- serve no purpose in the majority of codebases other than to make the code consume more memory, more CPU, and take longer to run. They are stupid dumbass additions to the language it REALLY didn't need.

As evidenced by the code halfwits, morons and fools vomit up using it, since if I see one more instance of:

Code: [Select]
function test() {
let myVar = 0;

My boot is going so far up that person's backside we need to call an orthodontist to handle the extraction. Since LITERALLY in that case LET would/should perform just like var except for garbage collection gets called more often.

For "quick and dirty" scripts I don't mind using .innerHTML for .innerText for brief displays of results.
NOT a good habit to get into, since much like document.write they are archaic relics that should have gone the way of the dodo about the same time we stopped giving a flying purple fish about Netscape 4.

Really though the logic failure in your code was that you were trying to assign a function that was being RUN.

commandTable.set( 'btn1', btnAction("Hello btn 1") );

would RUN btnAction() right then and there meaning you're getting NULL back, since btnAction has no RETURN statement. You're storing the result of RUNNING the function, what it does a RETURN of (in this case nothing, so null). That's why you have to store the values separate from the function.

Hmm... you know, another option that WOULD work with map? Make btnAction a constructor that when passed parameters assigns them to self, but when not passed parameters outputs its values.

Something like:

Code: [Select]
function BtnAction(callback) {
if (arguments) {
this.callback = callback;
this.args = arguments;
return;
}
this.callback(arguments);
}

Where you would do a:
Code: [Select]
commandTable.set( 'btn1', new BtnAction('btn1', 'Hello btn 1'));
Since that would return your object/constructor, which could then be called again. No idea how well or even if that would work, and it seems like a lot of effort for nothing.

Attaching properties to an object using defineProperty might be another option, making the values be an indexed array that's non-iterable attached to the document, whilst the methods themselves are iterable.
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 333
  • Karma: +36/-1
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
Re: Question about using Map() object
« Reply #4 on: 29 Apr 2020, 01:24:37 am »
Actually this might fly:

Code: [Select]
function newBtnAction() {
var args = arguments; // we have to store arguments since the anonF will have its own!
return function() { btnAction.apply(null, args); };
}
Might seem a bit odd, but since a normal function call's locals are retained so long as a reference created inside it exists -- like a new function -- that would give you a function back with the values you want inside it.

So with that added, this:

Code: [Select]
commandTable.set( 'btn2', newBtnAction('Btn 2', 'hello') );
SHOULD actually work, since it will pass a anonymous function back that calls the regular btnAction, since every time you call a function any VAR reference are preserved. (saving garbage collection for the end, something again LET pisses on from orbit)
« Last Edit: 29 Apr 2020, 01:33:02 am by Jason Knight »
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Junior Member
  • *
  • Posts: 29
  • Karma: +1/-0
    • View Profile
Re: Question about using Map() object
« Reply #5 on: 29 Apr 2020, 11:19:07 am »
Your observation:
Quote
Really though the logic failure in your code was that you were trying to assign a function that was being RUN.
was more insightful than my meager attempt in the last /* commented */ portion of my first post.
That was actually going to be my overall goal whilst I tried to understand the Map() designs.

I don't always seem to ask the right question in my zealous search for understanding.

Secondary question:  Where do I look to see if a method like the "arrow" function works in various browsers like IE11 and before.  I know about the "can i use" site, but don't always understand their layouts for giving answers to my particular problem.  Either that or I am unable to express my questions in a way that uses the correct nomenclature to obtain the answer I can understand.
This is why I enjoy this (and other) forum(s) for their teaching value.

I have never much liked MS browsers like IE and Edge and have only developed scripts in FF or Chrome. 
Probably shortsighted on my part, but I tend to want to understand the underlying principles of JS
Perhaps that is why I enjoy you detailed explanations as to why I should avoid certain habits.
Thanks for the education. :)

jmrker

  • Junior Member
  • *
  • Posts: 29
  • Karma: +1/-0
    • View Profile
Re: Question about using Map() object
« Reply #6 on: 29 Apr 2020, 11:39:27 pm »
Looking over you command table script, I think I understand most of what you recommend.
I do however have a question about one of the functions:
Quote
   // perform node flush
   function btnClear() {
      while (e.firstChild) e.removeChild(e.firstChild);
   }
Is the e reference in the function and element?
If yes, where is it coming from or how is it used?
I do not see it referenced in your script example.
btnClear() shows an error in the console.log when I call it

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 333
  • Karma: +36/-1
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
Re: Question about using Map() object
« Reply #7 on: 30 Apr 2020, 01:48:43 am »
Oops, yeah, that's a typo. Replace "e" with "debug". Normally I have that as a standalone function as I call it a good deal. Did ^C^V and forgot to normalize.
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Junior Member
  • *
  • Posts: 29
  • Karma: +1/-0
    • View Profile
Re: Question about using Map() object
« Reply #8 on: 30 Apr 2020, 08:42:53 am »
Thanks.

 

Advertisement