Last active
October 8, 2015 09:31
-
-
Save james-jlo-long/b486602fce22068cb8e6 to your computer and use it in GitHub Desktop.
Douglas Crockford mentioned that when JavaScript has proper tail calls, he'll stop using while. This gist is me going through some of my old functions to see how to remove while. This is mainly me thinking aloud, but feel free to comment any changes or suggestions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Array first and last | |
// | |
// var array = [1, 'two', 3, 'four', 5]; | |
// function isString(o) { | |
// return typeof o === 'string'; | |
// } | |
// util.Array.first(array); // -> 1 | |
// util.Array.first(array, isString); // -> 'two' | |
// util.Array.last(array); // -> 5 | |
// util.Array.last(array, isString); // -> 'four' | |
// Uses while | |
var util = (function () { | |
'use strict'; | |
function isFunction(func) { | |
return typeof func === 'function' && | |
Object.prototype.toString.call(func) === '[object Function]'; | |
} | |
function first(array, filter, context) { | |
var i = 0, | |
il = array.length, | |
hasOwn = Object.prototype.hasOwnProperty, | |
index = 0; | |
if (isFunction(filter)) { | |
index = -1; | |
while (i < il) { | |
if (hasOwn.call(array, i) && filter.call(context, array[i])) { | |
index = i; | |
break; | |
} | |
i += 1; | |
} | |
} | |
return array[index]; | |
} | |
function last(array, filter, context) { | |
var il = array.length, | |
hasOwn = Object.prototype.hasOwnProperty, | |
index = il - 1; | |
if (isFunction(filter)) { | |
index = -1; | |
while (il) { | |
if (hasOwn.call(array, il) && filter.call(context, array[il])) { | |
index = il; | |
break; | |
} | |
il -= 1; | |
} | |
} | |
return array[index]; | |
} | |
return { | |
Array: { | |
first, | |
last | |
} | |
}; | |
}()); | |
// Uses recursion. | |
var util = (function () { | |
'use strict'; | |
var arrayFilter = Array.filter || function (array, handler, context) { | |
return Array.prototype.filter.call(array, handler, context); | |
}; | |
function isFunction(func) { | |
return typeof func === 'function' && | |
Object.prototype.toString.call(func) === '[object Function]'; | |
} | |
function first(array, filter, context) { | |
return isFunction(filter) | |
? first(arrayFilter(array, filter, context)) | |
: array[0]; | |
} | |
function last(array, filter, context) { | |
return isFunction(filter) | |
? last(arrayFilter(array, filter, context)) | |
: array[array.length - 1]; | |
} | |
return { | |
Array: { | |
first, | |
last | |
} | |
}; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Array.chunk | |
// | |
// var arr = [1, 2, 3, 4, 5, 6, 7, 8]; | |
// arrayChunk(arr, 3) | |
// // -> [[1, 2, 3], [4, 5, 6], [7, 8]]; | |
// Uses while | |
var arrayChunk = (function () { | |
'use strict'; | |
return function (array, size) { | |
var copy = Array.from(array), | |
chunked = [], | |
index = 0, | |
length = copy.length; | |
while (index < length) { | |
chunked.push(copy.slice(index, index + size)); | |
index += size; | |
} | |
return chunked; | |
}; | |
}()); | |
// Uses times (see times.js) | |
var arrayChunk = (function () { | |
'use strict'; | |
return function (array, size) { | |
var copy = Array.from(array), | |
chunked = []; | |
times(Math.ceil(copy.length / size), function () { | |
chunked.push(copy.splice(0, size)); | |
}); | |
return chunked; | |
}; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PrototypeJS style Element.identify() | |
// | |
// <div></div> | |
// <span id="anonymous-1"></span> | |
// <article id="anonymous-2"></article> | |
// <section></section> | |
// | |
// identify(document.querySelector('div')) // -> "anonymous-0" | |
// identify(document.querySelector('section')) // -> "anonymous-3" | |
// Uses while. | |
var identify = (function () { | |
'use strict'; | |
var seed = 0; | |
function uniqid(prefix) { | |
var id = (prefix || '') + seed; | |
seed += 1; | |
return id; | |
} | |
function identify(element) { | |
var id = element.id; | |
if (!id) { | |
do { | |
id = uniqid('anonymous-'); | |
} while (document.getElementById(id)); | |
element.id = id; | |
} | |
return id; | |
} | |
return identify; | |
}()); | |
// Uses recursion. | |
var identify = (function () { | |
'use strict'; | |
var seed = 0; | |
function uniqid(prefix) { | |
var id = (prefix || '') + seed; | |
seed += 1; | |
return id; | |
} | |
function generateId() { | |
var id = uniqid('anonymous-'); | |
if (document.getElementById(id)) { | |
id = generateId(); | |
} | |
return id; | |
} | |
function identify(element) { | |
var id = element.id; | |
if (!id) { | |
id = generateId(); | |
element.id = id; | |
} | |
return id; | |
} | |
return identify; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jQuery style $().parents() and $().closest() | |
// | |
// <html> | |
// <body> | |
// <div class="two"> | |
// <section class="one"> | |
// <article class="two"> | |
// </article> | |
// </section> | |
// </div> | |
// </body> | |
// </html> | |
// | |
// var article = document.querySelector('article'); | |
// dom.closest(article, '.two'); // -> <article class="two"> | |
// dom.closest(article, 'div'); // -> <div class="two"> | |
// dom.parents(article, '.two'); | |
// // -> [<div class="two">] | |
// dom.parents(article); | |
// // -> [<section class="one">, <div class="two">, <body>, <html>] | |
// Uses while. | |
var dom = (function () { | |
'use strict'; | |
var dom = {}; | |
// A proper library would use Element.matches() with a fallback. This is | |
// just a quick function for demonstration. Same throughout this file. | |
function is(element, selector) { | |
return Array.prototype.indexOf.call( | |
document.querySelectorAll(selector), | |
element | |
) > -1; | |
} | |
function isElement(element) { | |
return element instanceof HTMLElement; | |
} | |
function parents(element, selector) { | |
var parents = [], | |
original = element; | |
if (typeof selector !== 'string') { | |
selector = '*'; | |
} | |
while (isElement(element)) { | |
if (element !== original && is(element, selector)) { | |
parents.push(node); | |
} | |
element = element.parentNode; | |
} | |
return parents; | |
} | |
function closest(element, selector) { | |
var closest; | |
while (isElement(element)) { | |
if (is(element, selector)) { | |
closest = element; | |
break; | |
} | |
element = element.parentNode; | |
} | |
return closest; | |
} | |
Object.assign(dom, { | |
parents, | |
closest | |
}); | |
return dom; | |
}()); | |
// Uses recursion. | |
var dom = (function () { | |
'use strict'; | |
var dom = {}; | |
function is(element, selector) { | |
return Array.prototype.indexOf.call( | |
document.querySelectorAll(selector), | |
element | |
) > -1; | |
} | |
function isElement(element) { | |
return element instanceof HTMLElement; | |
} | |
function upTheDom(node, handler, context) { | |
if (isElement(node) && handler.call(context, node) !== false) { | |
upTheDom(node.parentNode, handler, context); | |
} | |
} | |
function parents(element, selector) { | |
var parents = []; | |
if (typeof selector !== 'string') { | |
selector = '*'; | |
} | |
upTheDom(element, function (node) { | |
if (node !== element && is(node, selector)) { | |
parents.push(node); | |
} | |
}); | |
return parents; | |
} | |
function closest(element, selector) { | |
var closest; | |
upTheDom(element, function (node) { | |
if (is(node, selector)) { | |
closest = node; | |
} | |
return !closest; | |
}); | |
return closest; | |
} | |
Object.assign(dom, { | |
parents, | |
closest | |
}); | |
return dom; | |
}()); | |
// Uses array filter. | |
// I was always taught to treat a DOM lookup like a toll road and reduce the | |
// number of times it's done. Although I'm unlikely to use this technique, I | |
// admit that the code is certainly straight-forward. | |
var dom = (function () { | |
'use strict'; | |
var dom = {}; | |
function is(element, selector) { | |
return Array.prototype.indexOf.call( | |
document.querySelectorAll(selector), | |
element | |
) > -1; | |
} | |
function isElement(element) { | |
return element instanceof HTMLElement; | |
} | |
function upTheDom(node, handler, context) { | |
if (isElement(node) && handler.call(context, node) !== false) { | |
upTheDom(node.parentNode, handler, context); | |
} | |
} | |
function getAllParents(element) { | |
var parents = []; | |
upTheDom(element, function (node) { | |
parents.push(node); | |
}); | |
return parents; | |
} | |
function parents(element, selector) { | |
var parents = getAllParents(element); | |
if (typeof selector !== 'string') { | |
selector = '*'; | |
} | |
return parents.filter(function (parent) { | |
return parent !== element && is(parent, selector); | |
}); | |
} | |
function closest(element, selector) { | |
return getAllParents(element).filter(function (parent) { | |
return is(parent, selector); | |
})[0]; | |
} | |
Object.assign(dom, { | |
parents, | |
closest | |
}); | |
return dom; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PrototypeJS style Number.times | |
// | |
// times(-4.2, console.log, console); | |
// // logs: 0 | |
// // logs: 1 | |
// // logs: 2 | |
// // logs: 3 | |
// Uses while. | |
var times = (function () { | |
'use strict'; | |
function toPosAbs(number) { | |
return Math.floor(Math.abs(number)); | |
} | |
function isNumeric(number) { | |
return !isNaN(parseFloat(number)) && isFinite(number); | |
} | |
function toNumber(number, defaultNumber) { | |
return toPosAbs(isNumeric(number) ? number : defaultNumber); | |
} | |
function times(count, handler, context) { | |
var i = 0, | |
il = toNumber(count, 0); | |
while (i < il) { | |
handler.call(context, i); | |
i += 1; | |
} | |
} | |
return times; | |
}()); | |
// Uses array. | |
var times = (function () { | |
'use strict'; | |
function toPosAbs(number) { | |
return Math.floor(Math.abs(number)); | |
} | |
function isNumeric(number) { | |
return !isNaN(parseFloat(number)) && isFinite(number); | |
} | |
function toNumber(number, defaultNumber) { | |
return toPosAbs(isNumeric(number) ? number : defaultNumber); | |
} | |
function times(count, handler, context) { | |
Array(toNumber(count, 0)).fill(1).forEach(function (ignore, i) { | |
handler.call(context, i); | |
}); | |
} | |
return times; | |
}()); | |
// Uses recursion. | |
var times = (function () { | |
'use strict'; | |
function toPosAbs(number) { | |
return Math.floor(Math.abs(number)); | |
} | |
function isNumeric(number) { | |
return !isNaN(parseFloat(number)) && isFinite(number); | |
} | |
function toNumber(number, defaultNumber) { | |
return toPosAbs(isNumeric(number) ? number : defaultNumber); | |
} | |
function handleTimes(count, max, handler, context) { | |
if (count < max) { | |
handler.call(context, count); | |
handleTimes(count + 1, max, handler, context); | |
} | |
} | |
function times(count, handler, context) { | |
handleTimes(0, toNumber(count, 0), handler, context); | |
} | |
return times; | |
}()); | |
// Also uses recursion, doesn't have a private function but does expose a | |
// potential for breaking by exposing the `max` argument. | |
var times = (function () { | |
'use strict'; | |
function toPosAbs(number) { | |
return Math.floor(Math.abs(number)); | |
} | |
function isNumeric(number) { | |
return !isNaN(parseFloat(number)) && isFinite(number); | |
} | |
function toNumber(number, defaultNumber) { | |
return toPosAbs(isNumeric(number) ? number : defaultNumber); | |
} | |
function times(count, handler, context, max) { | |
count = toNumber(count, 0); | |
if (!isNumeric(max)) { | |
max = count; | |
} | |
if (count > 0) { | |
handler.call(context, max - count); | |
times(count - 1, handler, context, max); | |
} | |
} | |
return times; | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment