Reading Zepto Source Collection Element Search

Keywords: Javascript Attribute github

This article is still about dom-related methods, focusing on methods related to collection element lookup.

Read the Zepto source series on github. Welcome star: reading-zepto

Source version

The source code for this article is zepto1.2.0

Internal approach

There's a chapter before that.< Internal method of reading Zepto source code > zepto is dedicated to the interpretation of internal methods that are not available for external use, but there are several methods related to dom that have not been interpreted. Here we will first interpret the methods used in this chapter.

matches

zepto.matches = function(element, selector) {
  if (!selector || !element || element.nodeType !== 1) return false
  var matchesSelector = element.matches || element.webkitMatchesSelector ||
      element.mozMatchesSelector || element.oMatchesSelector ||
      element.matchesSelector
  if (matchesSelector) return matchesSelector.call(element, selector)
  // fall back to performing a selector:
  var match, parent = element.parentNode,
      temp = !parent
  if (temp)(parent = tempParent).appendChild(element)
    match = ~zepto.qsa(parent, selector).indexOf(element)
    temp && tempParent.removeChild(element)
    return match
}

The matches method is used to detect whether an element matches a particular selector.

Browsers also have native matches methods, but they will not be supported until after IE9. See the document specifically: Element.matches()

if (!selector || !element || element.nodeType !== 1) return false

This section is to ensure that both selector and element parameters are passed, and the nodeType of the element parameter is ELEMENT_NODE. How to return false if the condition does not meet?

 var matchesSelector = element.matches || element.webkitMatchesSelector ||
     element.mozMatchesSelector || element.oMatchesSelector ||
     element.matchesSelector
 if (matchesSelector) return matchesSelector.call(element, selector)

This section is to check whether the browser supports matches method natively or matches method with private prefix. If so, call native matches and return the result.

var match, parent = element.parentNode,
    temp = !parent
if (temp)(parent = tempParent).appendChild(element)
  match = ~zepto.qsa(parent, selector).indexOf(element)
  temp && tempParent.removeChild(element)
  return match

If the native method does not support it, it falls back to the selector method.

Three variables are defined here, in which parent is used to store the parent node of an element and temp is used to determine whether an element has a parent. The value is temp =! Parent, and temp is false if the element has a parent.

First, we determine whether there is a parent element. If the parent element does not exist, then parent = tempParent, tempParent has been defined by a global variable, tempParent = document.createElement('div'), is actually a div empty node. The element is then inserted into the empty node.

Then, find all the elements in the parent that match the selector, and then find the index of the current element element element in the collection.

zepto.qsa(parent, selector).indexOf(element)

Then the index is reversed, so that the value of index value 0 becomes - 1, and the return of - 1 is 0, which ensures consistent performance with matches.

What I don't really understand is, why don't you return boolean values like native ones? It can be done clearly through zepto. QSA (parent, selector). indexOf (element) > - 1. Isn't it better for interfaces to behave consistently?

Finally, there is one step to clean up:

temp && tempParent.removeChild(element)

Clean up the sub-elements of empty contacts to avoid contamination.

children

function children(element) {
  return 'children' in element ?
    slice.call(element.children) :
  $.map(element.childNodes, function(node) { if (node.nodeType == 1) return node })
}

The child method returns a collection of element s.

Browsers also have child attributes, which are native support elements, and are not supported until IE9 or above. See the documentation. ParentNode.children

If the browser does not support it, the $. map method is used to degrade the node whose nodeType is ELEMENT_NODE in elementary childNodes. Because children returns only element nodes, but childNodes returns not only element nodes, but also text nodes, attributes and so on.

The $. map used here is different from the original method of the array map. The implementation of $. map is already in progress.< Tool functions for reading zepto source code > I've read it.

filtered

function filtered(nodes, selector) {
  return selector == null ? $(nodes) : $(nodes).filter(selector)
}

Filters elements that match the specified selector from the collection.

If no selector is specified, the collection is wrapped into zepto objects and returned. Otherwise, the filter method is called to filter out eligible elements to return. The filter method will be discussed shortly.

Elemental Method

The methods here are all those provided in $.fn.

.filter()

filter: function(selector) {
  if (isFunction(selector)) return this.not(this.not(selector))
  return $(filter.call(this, function(element) {
    return zepto.matches(element, selector)
  }))
}

A filter is a set of elements that are found to be eligible.

The parameter selector can be either a Function or a selector. When it is a Function, the call actually calls the not method twice, with the negative being positive. As for the not method, I'll see it shortly.

When used as a general selector, the filter method is invoked, and the callback function of the filter calls matches, returns elements that conform to the selector and wraps them back as zepto objects.

.not()

not: function(selector) {
  var nodes = []
  if (isFunction(selector) && selector.call !== undefined)
    this.each(function(idx) {
      if (!selector.call(this, idx)) nodes.push(this)
        })
    else {
      var excludes = typeof selector == 'string' ? this.filter(selector) :
      (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
      this.forEach(function(el) {
        if (excludes.indexOf(el) < 0) nodes.push(el)
          })
    }
  return $(nodes)
}

The not method is to find out the unqualified elements in the set.

There are three ways to call the not method:

not(selector)  ⇒ collection
not(collection)  ⇒ collection
not(function(index){ ... })  ⇒ collection

When the selector is Function and there is a call method (isFunction (selector) & selector. call!=== undefined), the relevant code is as follows:

this.each(function(idx) {
  if (!selector.call(this, idx)) nodes.push(this)
    })

Call the each method, and in the selector function, you can access the current element and the index of the element. If the value of the selector function is reversed to true, the corresponding elements are put into the nodes array.

When the selector is not a Function, a variable excludes is defined, which receives a set of elements to be excluded. Next comes a string of ternary expressions (zepto features ah)

typeof selector == 'string' ? this.filter(selector)

When the selector is string, call filter to find all the elements that need to be excluded

(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)

When I first read this paragraph, I was a little confused, mainly because I didn't understand the criterion of isFunction(selector.item), and then I checked the MDN document. HTMLCollection.item It's only clear that item is a method of HTMLCollection. This ternary expression means that if it's HTMLCollection, it calls slice.call to get a pure array, otherwise it returns a zepto object.

this.forEach(function(el) {
    if (excludes.indexOf(el) < 0) nodes.push(el)
})

Traverse the collection and push the element into nodes if it is not in the collection of elements that need to be excluded.

The not method eventually returns a zepto object.

.is()

is: function(selector) {
  return this.length > 0 && zepto.matches(this[0], selector)
}

Determines whether the first element in the collection matches the specified selector.

The code is also relatively simple. Select and judge that the collection is not empty, and then call matches to see if the first element matches.

.find()

find: function(selector) {
  var result, $this = this
  if (!selector) result = $()
  else if (typeof selector == 'object')
    result = $(selector).filter(function() {
      var node = this
      return emptyArray.some.call($this, function(parent) {
        return $.contains(parent, node)
      })
    })
    else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
    else result = this.map(function() { return zepto.qsa(this, selector) })
    return result
}

Find is to find all descendant elements in a collection that match the selector. If a given zepto object or dom element is given, it will return only if they are in the current collection.

fid has three ways of calling, as follows:

find(selector)   ⇒ collection
find(collection)   ⇒ collection
find(element)   ⇒ collection
if (!selector) result = $()

If no parameters are passed, an empty zepto object is returned.

else if (typeof selector == 'object')
  result = $(selector).filter(function() {
    var node = this
    return emptyArray.some.call($this, function(parent) {
      return $.contains(parent, node)
    })
  })

If the parameter is object, that is, zepto object collection and dom node element, the selector is wrapped into zepto object, and then filtered to return the elements contained in the current collection child node ($. contains (parent node).

else if (this.length == 1) result = $(zepto.qsa(this[0], selector))

If the current set has only one element, call the zepto.qsa method directly, and take the first element of the set, this[0], as the first parameter of qsa. As for the QSA method, it is already in use< Reading the Magic of Zepto Source Code$ > It has been analyzed. It's actually all the descendant elements of the first element.

else result = this.map(function() { return zepto.qsa(this, selector) })

Otherwise, call the map method, call the qsa method for each element in the collection, and get the descendant elements of all elements. In fact, this condition can be combined with the previous one, which should be separated for performance considerations.

.has()

has: function(selector) {
  return this.filter(function() {
    return isObject(selector) ?
      $.contains(this, selector) :
    $(this).find(selector).size()
  })
},

Determine whether there are any sub-elements in the set that contain the specified conditions, and return the qualified elements.

There are two ways to call

has(selector)   ⇒ collection
has(node)   ⇒ collection

Parameters can be selectors or nodes.

Has actually calls the filter method, which has been interpreted above. In the callback function of filter, different methods are called according to different parameters.

isObject(selector) is used to determine whether the selector is a node node, and if it is a node, the $. contains method is called, which is already in use.< Tool functions for reading Zepto source code > I said that.

If it is a selector, the find method is called, and then the size method, which returns the number of elements in the collection, is called. This is in the< Collection operations for reading Zepto source code > It has been said that if the number of sets is greater than zero, the condition is satisfied.

.eq()

eq: function(idx) {
  return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1)
},

Gets the specified element in the collection.

Here we call the slice method, which was in the previous article.< Collection operations for reading Zepto source code > I've already said that. If IDX is - 1, call this.slice(idx) directly, that is, take the last element, otherwise take the element between IDX and idx+1, that is, take only one element at a time. + The + sign in front of idx+1 is actually a type conversion, ensuring that IDX is Number type in addition.

.first()

first: function() {
  var el = this[0]
  return el && !isObject(el) ? el : $(el)
},

First is the first element in a collection. This method is very simple. It can be taken out with index 0, that is, this[0].

EL & isObject (el) is used to determine whether it is a zepto object or not. If not, wrap it in $(el) to ensure that it returns a zepto object.

.last()

last: function() {
  var el = this[this.length - 1]
  return el && !isObject(el) ? el : $(el)
},

Last is the last element in a collection. The principle is the same as first, but instead of taking the index value of this.length - 1, that is, the last element.

.closest()

closest: function(selector, context) {
  var nodes = [],
      collection = typeof selector == 'object' && $(selector)
  this.each(function(_, node) {
    while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
      node = node !== context && !isDocument(node) && node.parentNode
      if (node && nodes.indexOf(node) < 0) nodes.push(node)
        })
  return $(nodes)
},

Look up from the element itself and return the element that first meets the criteria.

There are also three ways to call this method

closest(selector, [context])   ⇒ collection
closest(collection)   ⇒ collection 
closest(element)   ⇒ collection 

If a zepto set or element is specified, only elements matching a given set or element are returned.

collection = typeof selector == 'object' && $(selector)

This section is to determine whether the selector is a collection or element, and if so, it is unified into a zepto set.

Then traverse the collection, in each node of the collection, use the while statement to look up the qualified elements.

node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))

This is the termination condition of the while statement. Node nodes must exist. If the selector is a zepto set or element, or collection exists, the node that exists in the collection (collection. indexOf (node) >= 0) must be found. Otherwise, the node must match the specified selector (node, selector)).

In the while loop, it is the process of looking up the nodes step by step:

node = node !== context && !isDocument(node) && node.parentNode

When the current node is not a specified context context and is not a document node, look up (node.parentNode)

if (node && nodes.indexOf(node) < 0) nodes.push(node)

After the while loop is completed, if the node node exists and there is no node in the nodes, the node push is put into the nodes.

Finally, return to the zepto collection.

.pluck()

pluck: function(property) {
  return $.map(this, function(el) { return el[property] })
},

Returns the attribute values specified by all elements in the collection.

This is a simple way to traverse the current collection and then take the property value specified by the element.

.parents()

parents: function(selector) {
  var ancestors = [],
      nodes = this
  while (nodes.length > 0)
    nodes = $.map(nodes, function(node) {
      if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
        ancestors.push(node)
        return node
      }
    })
    return filtered(ancestors, selector)
},

Returns all ancestor elements of all elements in the collection.

The initial value of nodes is the current set, and the condition of while loop is that the set is not empty.

Using map to traverse nodes, the node is reassigned to its parent element. If the parent element exists, and is not a document element, and does not exist in ancestors, the node is stored in ancestors that preserve ancestors. The return value of the map callback is node, which forms a new set to assign to nodes until all ancestors have traversed. You can exit the while loop.

Finally, call the filtered method mentioned above to find the ancestor elements that match the selector.

.parent()

parent: function(selector) {
  return filtered(uniq(this.pluck('parentNode')), selector)
},

Returns the parent element of all elements in the collection.

Parents return all ancestor elements, while parents return only parent elements.

The first call is this.pluck('parentNode'), which gets the ancestor elements of all elements, then calls uniq to de-duplicate the set, and finally calls filtered to return the set of elements matching selector.

.children()

children: function(selector) {
  return filtered(this.map(function() { return children(this) }), selector)
},

Returns the child elements of all elements in the collection.

Firstly, we traverse the current set, call the internal method children to get the child elements of the current element to form a new array, and then call the filter method to return the set of elements matching selector.

.contents()

contents: function() {
  return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
},

This method is similar to children, but child ren filters child Nodes and returns only element nodes. contents also return text nodes and comment nodes. Also returns iframe's contentDocument

.siblings()

siblings: function(selector) {
  return filtered(this.map(function(i, el) {
    return filter.call(children(el.parentNode), function(child) { return child !== el })
  }), selector)
},

Gets the sibling nodes of all elements in all collections.

The idea of getting sibling nodes is also simple, traversing the current set, finding the parent element el.parentNode of the current element, calling the child method, finding the child element of the parent element, filtering out the elements that are not equal to the current element is its sibling element.

Finally, the filter is called to filter out the sibling elements that match the selector.

.prev()

prev: function(selector) { return $(this.pluck('previousElementSibling')).filter(selector || '*') },

Gets the previous sibling node of each element in the collection.

This method is also simple. Call the pluck method to get the previousElementSibling attribute of the element, which is the previous sibling node of the element. Call filter again to return the element matching selector, and wrap it up as a zepto object to return

.next()

next: function(selector) { return $(this.pluck('nextElementSibling')).filter(selector || '*') },

The next method is similar to the prev method, except that it takes the nextElementSibling attribute and gets the next sibling node of each element.

.index()

index: function(element) {
  return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},

Returns the position of the specified element in the current collection (this.indexOf($(element)[0]), and if no element is given, the position of the current bright red in the sibling element is returned. this.parent().children() looks for sibling elements.

Series of articles

  1. Code structure for reading Zepto source code
  2. Internal method of reading Zepto source code
  3. Tool functions for reading Zepto source code
  4. Reading the Magic of Zepto Source Code$
  5. Collection operations for reading Zepto source code

Reference resources

License

Author: Diagonally on the other side

Posted by jigen7 on Wed, 26 Jun 2019 14:09:48 -0700