Last active
December 17, 2015 21:59
-
-
Save robotlolita/5679050 to your computer and use it in GitHub Desktop.
Redesigning jQuery's API
This file contains hidden or 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
// There are four main design concerns here: | |
// * The API should abstract only DOM manipulations | |
// — law of Do Just One Thing. | |
// * The API should not allow plugins modifying jQuery, | |
// these should be distinct modules/objects | |
// — law of Modularity. | |
// * The API should not use the DOM as a data model, | |
// because this makes all operations inherently slow | |
// — law of Orthogonality. | |
// * The API should be simple, consistent and easy to use | |
// — law of Simplicity. | |
// With this in mind, we'll have a single type, that we'll call a Collection. | |
// Functions in jQuery will only deal with this type, and if you have something | |
// different, you'll have to wrap it `$(foo)`. | |
// Collection is a list of DOM nodes, the internal representation is not important: | |
// type Collection | |
// length: UInt32 | |
// items: [Node] | |
// The Collection function is what takes something that looks like an array, and | |
// creates a Collection out of it. This should be performant and deal with host | |
// objects, so we need to iterate over the object rather than just calling | |
// Array.slice on it: | |
function Collection(as) { | |
var xs = new Array(as.length) | |
for (var i = 0; i < as.length; ++i) xs[i] = as[i] | |
return xs | |
} | |
// Once we have a collection, we can have our functions to deal with. Since creating | |
// a collection from a CSS selector is rather common-place, we define it first: | |
// query: String, Node? -> Collection | |
function query(selector, context) { | |
return Collection((context || document).querySelectorAll(selector)) | |
} | |
// Now we get to manipulate the DOM, we can define the usual functions for this: | |
// Collection -> Collection | |
function clone(as) { | |
return as.map(function(a){ return a.cloneNode(true) }) | |
} | |
// as:Collection, bs:Collection -> as | |
function append(as, bs) { | |
as.forEach(function(a) { | |
clone(bs).forEach(function(b) { | |
a.appendChild(b) | |
}) | |
}) | |
return as | |
} | |
function before(as, bs) { | |
as.forEach(function(a) { | |
clone(bs).forEach(function(b) { | |
if (a.parentNode) a.parentNode.insertBefore(b, a) | |
}) | |
}) | |
return as | |
} | |
function prepend(as, bs) { | |
as.forEach(function(a) { | |
clone(bs).forEach(function(b) { | |
a.insertBefore(b, a.firstChild) | |
}) | |
}) | |
return as | |
} | |
function after(as, bs) { | |
as.forEach(function(a) { | |
clone(bs).forEach(function(b) { | |
if (a.parentNode) a.parentNode.insertBefore(b, a.nextSibling) | |
}) | |
}) | |
return as | |
} | |
function remove(as, bs) { | |
as.forEach(function(a) { | |
bs.forEach(function(b) { | |
a.removeChild(b) | |
}) | |
}) | |
return as | |
} | |
function detach(as) { | |
as.forEach(function(a) { | |
if (a.parentNode) a.parentNode.removeChild(a) | |
}) | |
return as | |
} | |
function replace(as, bs) { | |
as.forEach(function(a) { | |
if (bs.length) { | |
a.parentNode.replaceChild(b[0], a) | |
after(Collection([b[0]]), bs.slice(1)) | |
} | |
}) | |
return as | |
} | |
function clear(as) { | |
as.forEach(function(a) { | |
while (a.firstChild) a.removeChild(a.firstChild) | |
}) | |
return as | |
} | |
function wrap(as, b) { | |
as.forEach(function(a) { | |
if (a.parentNode) { | |
var bClone = b.cloneNode(true) | |
a.parentNode.insertBefore(bClone, a) | |
append(Collection([bClone]), as) | |
} | |
}) | |
return as | |
} | |
function attr(as, name) { | |
return as.length? as[0].getAttribute(name) : void 0 | |
} | |
function setAttr(as, name, value) { | |
as.forEach(function(a){ a.setAttribute(name, value) }) | |
return as | |
} | |
var TEXT = function(){ 'textContent' in document.createElement('div') ? 'textContent' : 'innerText' }() | |
function text(as, name) { | |
return as.length? as[0][TEXT] : void 0 | |
} | |
function setText(as, name, value) { | |
as.forEach(function(a){ a[TEXT] = value }) | |
return as | |
} | |
function html(as, name) { | |
return as.length? as[0].innerHTML : void 0 | |
} | |
function setHtml(as, name, value) { | |
as.forEach(function(a){ a.innerHTML = value }) | |
return as | |
} | |
/* ... and so on and so forth. Other functions are easily derivable, but I'm tired so... */ | |
var classOf = Function.call.bind({}.toString) | |
function isSelector(a){ return classOf(a) == '[object String]' } | |
function isCollection(a){ return Array.isArray(a) } | |
function isArrayLike(a){ return a && typeof a.length == 'number' } | |
function $(as) { | |
return isSelector(a)? new jQuery(query(a)) | |
: isCollection(a)? new jQuery(a) | |
: isArrayLike(a)? new jQuery(Collection(a)) | |
: function(){ throw new Error('not supported: ' + as) } | |
} | |
function jQuery(as) { | |
this.items = as | |
} | |
jQuery.prototype = methodise({ | |
clone: clone | |
, append: append | |
, before: before | |
, prepend: prepend | |
, after: after | |
, remove: remove | |
, detach: detach | |
, replace: replace | |
, clear: clear | |
, wrap: wrap | |
, setAttr: setAttr | |
, setHtml: setHtml | |
, setText: setText | |
}) | |
jQuery.prototype.attr = attr | |
jQuery.prototype.text = text | |
jQuery.prototype.html = html | |
// This allows libraries to extend jQuery in their own prototypes, just inherit from it. | |
jQuery.of = function(as) { return new jQuery(as) } | |
function methodise(o) { | |
return Object.keys(o).reduce(function(proto, key) { | |
proto[key] = function() { | |
return this.of(o[key].apply(this, arguments)) | |
} | |
}, {}) | |
} |
This file contains hidden or 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
var items = $('ul#foo .item') | |
items.append($('.item-content')) | |
.text() | |
// and so on and so forth |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment