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
  1. $('#nav') creates an initial jQuery object representing #nav. Its prevObject is the root document wrapper.
  2. .find('.active') calls pushStack(), producing a new collection of .active elements where prevObject references $('#nav').
  3. .css('color', 'red') modifies the active elements and returns the same Level 2 object (this).
  4. .end() accesses this.prevObject and returns the $('#nav') collection from Level 1.
  5. .addClass('loaded') executes on #nav rather than .active.

By utilizing prevObject, .end() avoids re-querying the DOM and restores previous selection contexts efficiently.