How jQuery .end() Reverts the Chain State
This article explains how the jQuery .end() method works
internally to revert chained operations back to their previous selection
state. You will learn about jQuery's internal element stack, the role of
the prevObject property, and how traversal methods interact
with this mechanism to allow flexible, multi-level DOM manipulation
without repeated queries.
The Mechanism Behind Method Chaining
Every jQuery method call that queries or traverses the DOM returns a
jQuery object. When you chain multiple methods together, you often alter
the set of matched elements. For example, calling .find()
or .filter() narrows down the elements currently
selected.
To make this reversible, jQuery does not simply discard the original set of matched elements when a new selection is made. Instead, it treats the chain like a stack data structure, preserving the history of your selections as you traverse the DOM tree.
The Role of
pushStack() and prevObject
Whenever a traversal or filtering method creates a new jQuery
selection, jQuery internally calls its pushStack() method.
Examples of these destructive methods include:
.find().filter().children().parent()/.parents().closest().siblings()
Inside pushStack(), jQuery builds a new jQuery object
containing the newly targeted elements. Before returning this new
object, jQuery assigns the current jQuery object (the one that called
the method) to a property named prevObject on the new
object:
// Simplified representation of jQuery's internal pushStack implementation
jQuery.fn.pushStack = function(elems) {
var ret = jQuery.merge(this.constructor(), elems);
ret.prevObject = this; // Preserves the previous state
return ret;
};This reference chain links the new selection directly back to the selection that produced it, effectively forming a linked list or stack of jQuery selections.
How .end() Restores the
State
The .end() method is straightforward because
prevObject has already done the heavy lifting. When
invoked, .end() simply inspects the current jQuery object
and returns the value stored in its prevObject
property:
// Simplified representation of jQuery's internal end implementation
jQuery.fn.end = function() {
return this.prevObject || this.constructor();
};If prevObject exists, .end() returns that
previous jQuery object, popping the current state off the stack. If
there is no previous state (such as calling .end()
immediately on a fresh selector), it falls back to an empty jQuery
collection.
Practical Example
Consider the following chained code:
$('#nav') // Level 1: Targets #nav
.find('.active') // Level 2: Targets .active inside #nav (prevObject points to Level 1)
.css('color', 'red') // Operates on .active
.end() // Reverts to Level 1 (#nav) via prevObject
.addClass('loaded'); // Operates on #nav$('#nav')creates an initial jQuery object representing#nav. ItsprevObjectis the root document wrapper..find('.active')callspushStack(), producing a new collection of.activeelements whereprevObjectreferences$('#nav')..css('color', 'red')modifies the active elements and returns the same Level 2 object (this)..end()accessesthis.prevObjectand returns the$('#nav')collection from Level 1..addClass('loaded')executes on#navrather than.active.
By utilizing prevObject, .end() avoids
re-querying the DOM and restores previous selection contexts
efficiently.