Jquery functional programming style - javascript

Is there a more elegant way to write this kind of function without having to initialize an array:
function getStuff () {
var some_array= [];
$("#some-id ul li span.key").each(function() {
some_array.push($(this).text());
});
return some_array;
}

jQuery.map
function getStuff () {
return $.map($("#some-id ul li span.key"), function(el) {
return $(el).text();
});
}
Fiddle
Performance-wise, the difference is minimal. Also, as noted by Lix, choose the method which you find more readable and maintainable. In the end, both will end up creating an array, iterating over elements, pushing string values to the array and returning the array.

Just another more functional feeling way to go about this might be:
Define a "method" function:
var method = function (method) {
return function (item) {
return $(item)[method]();
};
};
You can use it like this:
var text = method('text');
var html = method('html');
var fadeIn = method('fadeIn');
To re-work your getStuff function:
var getText = function (arr) {
return $.map(arr, text);
};
$.map iterates through the array that was passed in, applying the function that was returned from var text = method('text'), which is just the execution of $(array item).method().
I just threw this together to demonstrate a way you can use these kinds of techniques. You would probably name things differently and define the "method" function as a custom library/helper that is meant to be used app-wide.

Related

Compare functions in Javascript

I have an API that takes a function as an input, and then inside the API, the intent is to add the function to an Array if the function is not already added to the Array.
The call to the API is of the form:
myApiHandle.addIfUnique(function(){
myResource.get(myObj);
});
The API is:
myApiHandle.addIfUnique(myFunc) {
if (myArray.indexOf(myFunc) === -1) {
return;
}
// add to array
}
Now this obviously does not work as expected, since each time a new function is being passed in.
My Question is: Is there a way to pass in a function into the myApiHandle.addIfUnique call that will allow me to compare the existing functions in the array with this function that is currently passed in? The comparison should compare the function name and the object, and if both are the same, then not add the function to the array. I want to avoid adding another argument to the addIfUnique call if at all possible.
In other words, is the below possible:
myApiCall.addIfUnique (someFunc) {
}
If so, what is the someFunc. And what would be the logic inside the API to detect if the function already exists in myArray?
The same problem occurs with addEventListener and removeEventListener, where the callback must be identical (in the === sense) for removeEventListener to remove it.
As you've found, obviously if you call addIfUnique like this:
addIfUnique(function() { })
the function passed each time will be a unique object. The solution is to create the function once:
var fn = function() { };
addIfUnique(fn);
addIfUnique(fn);
A related problem occurs when the function being passed in is a method invocation, so I need to bind it:
var x = { val: 42, method: function() { console.log(this.val); } };
I want to pass a bound version of it, so
addIfUnique(x.method.bind(x));
addIfUnique(x.method.bind(x));
But again, each call to x.method.bind(x) will return a separate function. So I need to pre-bind:
var boundMethod = x.method.bind(x);
addIfUnique(boundMethod);
addIfUnique(boundMethod);
First of all, comparing functions is meaningless, even if two functions are literally different, they may be functionally the same.
And for your problem, you can compare whether it's exactly the same object, or you can compare it literally by using toString() function and regExp.
var addIfUnique = (function() {
var arr = [];
return function(func) {
if (~arr.indexOf(func)) return false;
var nameArr = [];
var funcName = func.name;
var funcRegExp = new RegExp('[^\{]+\{(.+)\}$', 'i');
var funcStr = func.toString().match(funcRegExp);
funcStr = funcStr && funcStr[1];
if (!funcStr) return false;
var strArr = arr.map(function(v){
nameArr.push(v.name);
return v.toString().match(funcRegExp)[1];
});
if (~strArr.indexOf(funcStr) && ~nameArr.indexOf(funcName)) return false;
arr.push(func);
};
}());

How to Define Map in Terms of ForEach

I have defined my own map function, which serves the same purpose as the method on Array objects. (I am doing this to learn). The code is below.
I would like to use Array's forEach method inside of my own definition of map, but I am missing something conceptually. How would I be able to do this using forEach?
Working without using forEach
// transforming with map
// NOTE: map is already defined in JavaScript
function map(array, transformation) {
var mapped = [];
for (var index in array) {
var currentElement = array[index];
var transformedElement = transformation(currentElement);
mapped.push(transformedElement);
}
return mapped;
}
My Attempt of using forEach
function mapForEach(array, transformation) {
var mapped = [];
array.forEach(transformation(currentVal, index, array, mapped));
return mapped;
}
function transformation(person, index, array, accumulator) {
var name = person.name;
accumulator.push(name);
}
The missing concept here is pass by value. The changes to accumulator will not be reflected in forEach. forEach is not actually designed to return a value for each iteration. For that you have map.
Taken from here,
foreach iterates over a list and applies some operation with side
effects to each list member (such as saving each one to the database
for example)
map iterates over a list, transforms each member of that list, and
returns another list of the same size with the transformed members
(such as converting a list of strings to uppercase)
Try this code:
function mapForEach(array, transformation) {
var mapped = array.map(transformation);
return mapped;
}
function transformation(person, index, array) {
var name = person.name;
return name;
}
Here is a JSFiddle
If you absolutely have to use forEach, then rather than passing a value, a global variable has to be used. This would be then
var accumulator =[];
function mapForEach(array, transformation) {
array.forEach(transformation);
return accumulator;
}
function transformation(person, index, array) {
var name = person.name;
accumulator.push(name);
}
JSFiddle - forEach
You're almost right with your method, but like #Katana314 says, you need to bind, not call.
Here's how I would do it:
function map(array,mapper) {
var ret=[];
array.forEach((val,key)=>{
//Since arrays are supposed to be sequential, we don't have to worry
//about keys matching, if they don't match, the user/developer did
//something horribly wrong.
ret.push(mapper(val,key,array));
});
return ret;
}
And here's how I would fix your way.
function mapForEach(array, func) {
var mapped = [];
array.forEach(mapForEachPusher.bind(null,mapped,func));
return mapped;
}
function mapForEachPusher(mapped,func,value,index,array) {
mapped.push(func(value,index,array));
}
function extract_name(person) {
return person.name;
}
mapForEach(
[{name:'Billy'},{name:'Bob'},{name:'Bridget'},{name:'Brittany'},{name:'"B word"'}]
,extract_name
);
//["Billy", "Bob", "Bridget", "Brittany", "\"B word\""]
It's effectively the same thing, your way moves the .push() call out of the main function, which means you have to pass data around, whereas I only need to reference from the outer scope. The only downside to my way is the overhead of the function, but it is an arrow function, so that might make it lightweight enough to not matter.

javascript method is undefined

I'm trying to learn javascript. As part of that effort, I am writing a basic minimax AI. I have the following methods:
Computer.prototype.expand = function(node) {
/* adds all state action pairs to the node.successors array */
};
Computer.prototype.getMove = function(boardAr) {
console.log("getMove");
var b2 = boardAr.slice();
var i;
var action;
this.root = new TNode(b2, this.mark);
this.root.AIPlayedLast = false;
this.expand(this.root);
this.root.successors.forEach(this.minVal);
action = maxNode(root.successors);
this.draw(action);
registerMove(action, this.mark);
};
Computer.prototype.minVal = function(node) {
if (node.isTerminal) {
return;
} else {
this.expand(node);
node.successors.forEach(maxVal);
node.utility = this.minNode(node.successors).utility;
}
};
When the getMove method is called the subsequent call to expand goes as expected. But, when expand is called from the minVal method I get: Uncaught TypeError: undefined is not a function. I'm utterly perplexed by this. Any help/suggestions would be greatly appreciated.
I think the reason is in this row:
this.root.successors.forEach(this.minVal);
You pass minVal as contextless reference, it will not be called in a context of your Computer instance (this)
Here is how you can improve it:
var self = this;
this.root.successors.forEach(function() {
self.minVal.apply(self,arguments);
})
The simplest and quickest solution is just to change
this.root.successors.forEach(this.minVal);
to
this.root.successors.forEach(this.minVal.bind(this))
This solves the problem in the same as the other answers, but in a way some might consider more compact.
Or, you can pass a "this" to the forEach function as the second argument, a somewhat under-utilized feature of forEach:
this.root.successors.forEach(this.minVal, this)
This feature is also available on other Array prototype methods that take functions, including map, filter, some, every (but not reduce and reduceRight).
ES6 arrow functions handle this differently, so you can do
this.root.successors(forEach(e => this.minVal(e)));
The forEach() method might be called for each of the successors. So, you pass the Computer::minVal method (this.minVal), but with the TNode(?) as this-pointer. Try:
var that = this;
this.root.successors.forEach(function(node) {
that.minVal(node));
});

How do I mix underscore functions to use a custom comparison algorithm for _.contains()?

Ideally, I want to pass a custom comparison function to _.contains() as a third argument, but it only accepts a collection and a value.
Code
I want to do this:
_.contains(['apples', 'oranges'], 'applesss', function (element, value) {
return new RegExp(element).test(value);
});
... but I can't, so what's the next best thing?
It sounds like you're looking for _.some, which returns true if the test passes for at least one element in the array:
_.some(['apples', 'oranges'], function (element) {
return new RegExp(element).test('applesss');
});
You can easily wrap it in your own function:
function test_regexes(arr, value) {
return _.some(arr, function (element) {
return new RegExp(element).test(value);
});
}
test_regexes(['apples', 'oranges'], 'applesss');

How do I create methods for an HTML element?

I'm trying to create a simple, small and basic javascript framework just for learning purposes.
But the thing is that i'm allready stuck at the very basics.
I'm trying to do something like this:
$('testdiv').testFunction();
And the code i've written for that:
var elementID;
var smallFramework = {
$:function(id) {
this.elementID = id;
},
testFunction:function() {
alert(this.elementID);
}
};
window.$ = smallFramework.$;
But in return I get:
$('testdiv) is undefined
Can anyone help me with this small and hopefully easy question?
To get the behavior you're expecting, you need the $ function to return an object with a method named testFunction.
Try:
var smallFramework = // an object for namespacing
{
$:function(id) // the core function - returns an object wrapping the id
{
return { // return an object literal
elementID: id, // holding the id passed in
testFunction: function() // and a simple method
{
alert(this.elementID);
}
};
}
};
Of course, there are many other ways to achieve the behavior you desire.
If you're trying to add methods to an HTML element you could do something along these lines.
$ = function( elementId ) {
var element = document.getElementById( elementId );
element.testFunction = function(){
alert( this.id );
return this; // for chaining
}
return element;
}
$('test').testFunction();
Try
smallFramework.$('testdiv');
instead. According to the code you posted, that's where your $ function ended up.
Or alternatively, it looks like you're trying to replicate something like jQuery. You might want to try something like this.
var $ = smallFramework = (function () {
var f =
{
find:function(id) {
f.elementID = id;
return f; //every function should return f, for chaining to work
},
testFunction:function() {
alert(f.elementID);
return f;
}
}
return f.find //the find function will be assigned to $.
//and also assigned to smallFramework.
//the find function returns f, so you get access to testFunction via chaining
// like $("blah").testFunction()
})() //note this function gets called immediately.
this code may look confusing to someone new to JavaScript because it depends heavily on the concept of closures. I suggest that if this doesn't make sense, spend some time at Douglas Crockford's JavaScript website. This is important because the code above will bite if you happen to use this in the find function because this won't be bound to f, as you may expect it to be when you use it from $ or smallFramework.

Categories

Resources