Higher order functions

I’m reading Functional Programming – Eloquent JavaScript at the moment.


// call function for each item
function forEach(items, fn) {
	for (var i=0, ii=items.length; i<ii; i++) fn(items[i]);
}

// reduce an array to a single value by calling a function on each item and adding to a base value
function reduce(fn, base, array) {
	forEach(array, function (item) {
		base = fn(base, item);
	});
	return base;
}

// calls a function on each item in array
function map(fn, array) {
	var result = [];
	forEach(array, function (element) {
		result.push(fn(element));
	});
	return result;
}

// creates an array from iterable items starting at an index
function asArray(items, start) {
	var result = [];
	for (var i=start || 0, ii=items.length; i<ii; i++) result.push(items[i]);
	return result;
}

// define a new function that calls a fn with a list of arguments at the start pre-specified.
function partial(fn) {
	var fixedArgs = asArray(arguments, 1);
	return function() {
		return fn.apply(null, fixedArgs.concat(asArray(arguments)));
	}
}

// define a new function that calls fn1 and fn2 on the result.
function compose(fn1, fn2) {
	return function() {
		return fn2(fn1.apply(null, arguments)); // I switched the order around here, doesn't this make more sense?
	};
}

Certain functions I see would be very useful:
forEach & map I would commonly write our with for loops and arrays but I see that I’ve been wasting a lot of time writing these loops by hand.

Any other functions like these you guys use regularly?

String.trim() is a useful one, along with [URL=“https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf”]Array.indexOf() and [URL=“https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter”]Array.filter()
The array pages provide some compatibility code which becomes very useful.

Other common functions that are used regularly are addClass(), removeClass(), hasClass() as well as [url=“http://www.quirksmode.org/js/cookies.html#script”]createCookie(), readCookie(), eraseCookie()

Thanks Paul, I use those often as well.

I was more interested in specific alogithms or functions that change how you actually write your javascript.
The functions I listed are all functions within functons, things like that have always done my head in but I’m finding a few of them really useful.

Ahh, I now see that the two functions you can’t see below the fold are functions within functions.
Yes, they are useful, and I too agree that my head sometimes explodes due to them.

Have you seen the one about deriving the y-combinator in seven easy steps? That one is a quite instructional favourite.

WTF? :slight_smile:

Thanks.

I like this one for creating elements with attributes easily:

function $c(el, attrs, str) {
  el = document.createElement(el);
  if (attrs && attrs.constructor === Object) for (var a in attrs) el[a] = attrs[a];
  if (str !== undefined) el.appendChild(document.createTextNode(str));
  return el;
}

// example:

var el = $c('a', {
  href:'http://sitepoint.com',
  title:'Sitepoint',
  onclick:function(){
    alert('hi');
  }
}, 'Click me');

I also wrote my own map function to cope with objects as well as arrays:

function map(iterable, prop, f) {
  var i, j;
  prop = prop ? prop.toString() : '';
  if (!iterable.length || typeof f !== 'function') return iterable;
  function iterate(x) {
    if (prop in iterable[x]) iterable[x][prop] = f(iterable[x][prop]);
    else if (prop) return;
    else iterable[x] = f(iterable[x]);
  }
  if (iterable.constructor === Object) for (i in iterable) iterate(i);
  else for (i = 0, j = iterable.length; i < j; i++) iterate(i);
  return iterable;
}

I also wrote an ajax function that kept trying (up to 3 times) if something failed, and could upload stuff (for newer browsers - it doesn’t check if the browser is capable).

var ajax = function(data, callback, url, current, prog) {
  var req, requests = ajax.requests = ajax.requests || [], attempt, upload = current === 'upload', ctype = upload ? "application/octet-stream" : "application/x-www-form-urlencoded";
  if (window.XMLHttpRequest) req = new XMLHttpRequest();
  else {
    try {req = new ActiveXObject('Msxml2.XMLHTTP')}
    catch (e) {
      try {req = new ActiveXObject('Microsoft.XMLHTTP')}
      catch (e) {}
    }
  }
  if (current === undefined) {
    current = requests.push([]) - 1;
  }
  if (!upload) {
    attempt = requests[current].length;
    requests[current][attempt] = {timer:null, req:req};
  }
  req.onreadystatechange = function() {
    if (!upload) window.clearTimeout(requests[current][attempt].timer);
    if (!upload && req.readyState < 4 && attempt < 3) {
      requests[current][attempt].timer = window.setTimeout(function() {
        ajax(data, callback, url, current);
      }, 5000);
    }
    if (req.readyState === 4) {
      if (req.status === 200) {
        if (callback) callback(req.responseText);
        if (!upload) for (var i = 0; i < requests[current].length; i++) {
          requests[current][i].req.abort();
          window.clearTimeout(requests[current][i].timer);
        }
      }
      else if (!upload && attempt > 2) {
        switch(req.status) {
          case 404:
            error(req.status + " Not Found.\
\
URL for XMLHttpRequest appears to be wrong. Check it is correct:\
\
" + url);
            break;
          case 403:
            error(req.status + " Forbidden.\
\
Looks like accessing " + url + " isn't allowed. Perhaps a password is required?");
          case 500:
            error(req.status + " Server Error.\
\
Something wrong on the server end meant this request failed - even after three attempts!\
\
");
            break;
        }
      }
    }
  }
  if (upload && req.upload) {
    if (prog) req.upload.onprogress = function(e) {
      if (e.lengthComputable) prog(e.loaded, e.total);
    }
  }
  req.open('POST', url, true);
  req.setRequestHeader("Content-type", ctype);
  if (upload) req.setRequestHeader("X-File-Name", data.name);
  if (upload && data.getAsBinary) req.sendAsBinary(data.getAsBinary());
  else req.send(data);
  return req;
}

// Every up-to date browser (including IE9) includes these Array methods, and String.trim.
// I add them to older browsers, when I need them, by including them in a script file
// if the client does not define Array.prototype.reduce.

(function(){
    var AP= Array.prototype, SP= String.prototype, equalizer={
        every: function(fun, scope){
            var len= this.length;
            if(typeof fun== 'function'){
                for(var i= 0; i < len; i++){
                    if(i in this){
                        if(fun.call(scope, this[i], i, this)== false) return false;
                    }
                }
                return true;
            }
        },
        filter: function(fun){
            var A= [], i= 0, itm, L= this.length;
            if(typeof fun== 'function'){
                while(i< L){
                    itm= this[i];
                    if(!!fun(itm, i, this)) A[A.length]= itm;
                    ++i;
                }
            }
            return A;
        },
        forEach: function(fun, scope){
            var L= this.length, i= 0;
            if(typeof fun== 'function'){
                while(i< L){
                    if(i in this){
                        fun.call(scope, this[i], i, this);
                    }
                    ++i;
                }
            }
            return this;
        },
        indexOf: function(what, i){
            i= i || 0;
            var L= this.length;
            while(i< L){
                if(this[i]=== what) return i;
                ++i;
            }
            return -1;
        },
        lastIndexOf: function(what, i){
            var L= this.length;
            i= i || L-1;
            if(isNaN(i) || i>= L) i= L-1;
            if(i< 0) i += L;
            while(i> -1){
                if(this[i]=== what) return i;
                --i;
            }
            return -1;
        },
        map: function(fun, scope){
            var L= this.length, A= Array(L), i= 0;
            if(typeof fun== 'function'){
                while(i< L){
                    if(i in this){
                        A[i]= fun.call(scope, this[i], i, this);
                    }
                    ++i;
                }
                return A;
            }
        },
        reduce: function(fun, temp, scope){
            var i= 0, T= this, len= T.length, temp;
            if(scope== undefined) scope= window;
            if(typeof fun=== 'function'){
                if(temp== undefined) temp= T[i++];
                while(i < len){
                    if(i in T) temp= fun.call(scope, temp, T[i], i, T);
                    i++;
                }
            }
            return temp;
        },
        reduceRight: function(fun, temp, scope){
            var T= this.concat().reverse();
            return T.reduce(fun, temp, scope);
        },
        some: function(fun, scope){
            var t= this, L= t.length;
            if(typeof fun== "function"){
                for(var i= 0; i < L; i++){
                    if(i in t && fun.call(scope, t[i], i, t))
                    return true;
                }
                return false;
            }
        }
    };
    for(var p in equalizer){
        if(!AP[p]) AP[p]= equalizer[p];
    }
    if(!SP.trim){
        SP.trim=function(){
            return this.replace(/^\\s+|\\s+$/g, '');
        }
    }
})();

Thanks guys,

Another one I use regularly is extend for copying properties from one object to another.


function extend(dest, src) {
  for (var prop in src || {}) dest[prop] = src[prop];
  return dest;
}

Useful for providing default options for a function and merging in the overriding options.

Is it worthwhile using compatability code for a more recent native version of the same task, called defineProperties() ?

Interesting, I didn’t know that existed.

It’s doing a bit more than extend, not sure i’d fallback to that because of the differences…

The only extra stuff it’s doing is to perform sanity checks on the provided objects, and to ensure that they are allowed to have properties added.

You can always make the code simpler, so that it does just enough to work according to how you use it, but I do think that it’s important to allow the code you write to be capable of making use of native web browser methods when they become available.

The following code is very much simplified from actual compatibility code, but it might meet the needs for much of what it’s used for.



if (!Object.prototype.defineProperty) {
    Object.prototype.defineProperty = function (prop, descriptor) {
        this.prop = descriptor;
    };
}
if (!Object.prototype.defineProperties) {
    Object.prototype.defineProperties = function (props) {
        var prop;
        for (prop in props) {
            if (props.hasOwnProperty(prop)) {
                this[prop] = props[prop];
            }
        }
    };
}

// setup
var car = {};

// test
car.defineProperties({
    make: 'Tesla',
    model: 'Roadster',
    color: 'Brilliant yellow'
});

Good points, I can see how that works.

Some others I have thought of are bind() and times() which can avoid you rewriting logic for it all over the place:


Function.prototype.bind = function(scope) {
  var _function = this;
  return function() {
    return _function.apply(scope, arguments);
  }
}
function times(n, fn, arg) {
  for (var i= 0; i < n; ++i) fn(arg);
}