Overview
This analysis focuses on the core utility methods available within the Zepto library that facilitate element selection and collection traversal. Understanding these mechanisms provides insight into how lightweight jQuery alternatives handle DOM queries efficiently. The code examples below are derived from the architecture found in version 1.2.0.
Core Utility Functions
Before examining public API extensions, its necessary to understand the private helper functions utilized internally to perform low-level checks.
Element Matching Validation
The matches function determines whether a specific DOM node conforms to a CSS selector. It prioritizes native browser implementations before falling back to DOM tree traversal.
zepto.matchesSelector = function(targetNode, querySelector) {
// Validate inputs: selector must exist, target must be an Element Node
if (!querySelector || !targetNode || targetNode.nodeType !== 1) return false
var selectorMethod = targetNode.matches ||
targetNode.webkitMatchesSelector ||
targetNode.mozMatchesSelector ||
targetNode.oMatchesSelector
if (selectorMethod) {
return selectorMethod.call(targetNode, querySelector)
}
// Fallback mechanism using qsa if native support is missing
var currentParent = targetNode.parentNode
var orphaned = !currentParent
if (orphaned) {
var tempContainer = document.createElement('div')
currentParent = tempContainer
currentParent.appendChild(targetNode)
}
var matchesResult = ~zepto.qsa(currentParent, querySelector).indexOf(targetNode)
if (orphaned) {
tempContainer.removeChild(targetNode)
}
return Boolean(matchesResult)
}
The function first validates that both arguments are present and the target is an actual element node. It attempts to invoke the native matchesSelector implementation if available. If no native support exists, it temporarily appends the orphaned element to a temporary container, executes a query selector action (qsa) on the parent, checks the index of the element, and cleans up the temporary node.
Retrieving Children Nodes
The children utility returns only element nodes among immediate children, excluding text or comment nodes.
function getChildren(elementNode) {
if ('children' in elementNode) {
return Array.prototype.slice.call(elementNode.children)
}
return $.map(elementNode.childNodes, function(childNode) {
return (childNode.nodeType === 1) ? childNode : null
}).filter(Boolean)
}
When the browser supports the standard .children property, it returns that array directly. Otherwise, it iterates through all childNodes, mapping only those with a nodeType of 1 (ELEMENT_NODE).
Collection Filtering
A simple wrapper designed to convert raw node lists into Zepto objects or apply further filtering.
function applyFilter(nodesList, criteria) {
if (!criteria) return $(nodesList)
return $(nodesList).filter(criteria)
}
Public API Extensions
The following methods are attached to the Zepto prototype ($().) and operate on existing collections of DOM elements.
.filter(selector)
Selects a subset of the current collection matching a specific condition.
filter: function(criteria) {
if ($.isFunction(criteria)) {
return this.not(this.not(criteria))
}
return $(Array.prototype.filter.call(this, function(el) {
return zepto.matchesSelector(el, criteria)
}))
}
If the argument is a function, it effectively uses double negation to include matching items. Otherwise, it filters the current elements based on the selector criteria.
.not(selector)
Inverts the selection, returning elements that do not match the provided criteria.
not: function(exclusionCriteria) {
var exclusionNodes = []
if ($.isFunction(exclusionCriteria)) {
this.each(function(idx) {
if (!exclusionCriteria.call(this, idx)) {
exclusionNodes.push(this)
}
})
} else {
var excludedList = Array.isArray(exclusionCriteria)
? exclusionCriteria
: $(exclusionCriteria)
this.forEach(function(currentEl) {
if (excludedList.indexOf(currentEl) < 0) {
exclusionNodes.push(currentEl)
}
})
}
return $(exclusionNodes)
}
This method accepts functions, strings, or arrays. When given a function, it runs the callback against each item. If the result is falsy, the item is kept. For non-function inputs, it calculates the difference between the current set and the exclusion set.
.is(selector)
Checks if the very first element in the collection satisfies the selector.
is: function(query) {
return this.length > 0 && zepto.matchesSelector(this[0], query)
}
Returns true only if the collection contains data and the initial element passes the validation check.
.find(selector)
Searches for descendant elements relative to the current set.
find: function(query) {
if (!query) return $()
var self = this
if ($.isObject(query)) {
var potentialTargets = $(query)
return potentialTargets.filter(function() {
var candidate = this
return Array.prototype.some.call(self, function(parent) {
return $.contains(parent, candidate)
})
})
}
var results = []
if (self.length === 1) {
results = zepto.qsa(self[0], query)
} else {
self.forEach(function(node) {
results = results.concat(zepto.qsa(node, query))
})
}
return $(results)
}
Optimized for performance, this method handles single-element parents directly via qsa, whereas multiple parents require a map/reduce approach to gather descendants before returning the merged set.
.has(selector)
Filters the collection to only include elements possessing matching descendants.
has: function(itemQuery) {
return this.filter(function(contextNode) {
if ($.isObject(itemQuery)) {
return $.contains(contextNode, itemQuery)
}
return $(this).find(itemQuery).length > 0
})
}
Distinguishes between passing a specific node object versus a string selector, utilizing $.contains or nested search respectively.
.eq(index)
Retrieves a single element based on zero-indexed position.
eq: function(position) {
return this.slice(position, +position + 1)
}
Handles negative indices gracefully by default slice behavior, ensuring exactly one element is returned wrapped in a collection.
.first() and .last()
Access boundary elements within the collection.
first: function() {
var firstItem = this[0]
return firstItem && !$.isObject(firstItem) ? $(firstItem) : firstItem
},
last: function() {
var lastIndex = this.length - 1
var lastItem = this[lastIndex]
return lastItem && !$.isObject(lastItem) ? $(lastItem) : lastItem
}
.closest(selector)
Traverses upward to find the nearest ancestor matching criteria.
closest: function(criterion) {
var results = []
this.each(function(_, currentNode) {
var targetNode = currentNode
var ancestorsList = $(criterion)
while (targetNode && targetNode.parentElement) {
var isMatch = false
if (ancestorsList.length) {
if (ancestorsList.index(targetNode) >= 0) isMatch = true
} else if (zepto.matchesSelector(targetNode, criterion)) {
isMatch = true
}
if (isMatch) break
targetNode = targetNode.parentElement
}
if (targetNode && results.indexOf(targetNode) === -1) {
results.push(targetNode)
}
})
return $(results)
}
Iterates up the DOM tree. It checks against a provided collection of nodes or a selector string until a match is found or the root is reached.
.pluck(property)
Extracts values from a specified attribute across all selected elements.
pluck: function(attrName) {
return $.map(this, function(el) {
return el[attrName]
})
}
.parents(selector)
Aggregates all ancestors up the tree hierarchy.
parents: function(query) {
var ancestors = []
var scope = this
while (scope.length) {
scope = $.map(scope, function(node) {
var pNode = node.parentNode
if (pNode && pNode !== document && ancestors.indexOf(pNode) < 0) {
ancestors.push(pNode)
return pNode
}
})
}
return applyFilter(ancestors, query)
}
Uses a loop to progressively move up one level per iteration, preventing duplicates by tracking visited nodes.
.parent(selector)
Returns only immediate parents.
parent: function(query) {
return applyFilter($.unique(Array.from(this.pluck('parentNode'))), query)
}
Similar to parents but retrieves depth 1 only, utilizing deduplication logic.
.children(selector)
Accesses immediate child elements.
children: function(query) {
return applyFilter(this.map(getChildren), query)
}
.contents()
Retrieves all child nodes, including text and comments.
contents: function() {
return this.map(function(n) {
return n.contentDocument || Array.from(n.childNodes)
})
}
Unlike children, this includes whitespace text nodes and iframe documents.
.siblings(selector)
Identifies peer elements sharing the same parent.
siblings: function(query) {
return this.map(function(idx, el) {
return $(Array.from(children(el.parentNode)).filter(child => child !== el))
}).filter(query || '*')
}
Filters the sibling list by excluding the current element itself.
.prev(selector) & .next(selector)
Navigates to adjacent siblings.
prev: function(query) {
return $(this.pluck('previousElementSibling')).filter(query || '*')
},
next: function(query) {
return $(this.pluck('nextElementSibling')).filter(query || '*')
}
Directly accesses DOM adjacency properties.
.index(element)
Returns the numerical position of the element.
index: function(otherElement) {
if (otherElement) {
return this.indexOf($(otherElement)[0])
}
return this.parent().children().indexOf(this[0])
}
Calculates position relative to siblings if no external reference is provided.