Skip to content

Instantly share code, notes, and snippets.

@robotlolita
Last active December 17, 2015 21:59
Show Gist options
  • Save robotlolita/5679050 to your computer and use it in GitHub Desktop.
Save robotlolita/5679050 to your computer and use it in GitHub Desktop.
Redesigning jQuery's API
// 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))
}
}, {})
}
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