Operation DOM for reading Zepto source

Keywords: Javascript Fragment Attribute github

This article remains a domain-related approach, focusing on how to manipulate the domain.

Read the Zepto Source Series article already on github, welcome star: reading-zepto

Source Version

The source code for reading this article is zepto1.2.0

.remove()

remove: function() {
  return this.each(function() {
    if (this.parentNode != null)
      this.parentNode.removeChild(this)
    })
},

Delete elements from the current collection.

If the parent node exists, the removeChild method of the parent node is used to delete the current element.

Similar Method Generator

after, prepend, before, append, insertAfter, insertBefore, appendTo, and prependTo in zepto are generated through this similar method generator.

Define Containers

adjacencyOperators = ['after', 'prepend', 'before', 'append']

First, you define an array of similar operations. Note that there are only after, prepend, before, append method names in the array. As you will see later, after generating these methods, insertAfter, insertBefore, appendTo, and prependTo will call the methods generated earlier.

Auxiliary method traverseNode

function traverseNode(node, fun) {
  fun(node)
  for (var i = 0, len = node.childNodes.length; i < len; i++)
    traverseNode(node.childNodes[i], fun)
}

This method recursively traverses the child nodes of a node, leaving the node to be handled by the callback function fun.This assistive method will be used later.

Core Source

adjacencyOperators.forEach(function(operator, operatorIndex) {
  var inside = operatorIndex % 2 //=> prepend, append

  $.fn[operator] = function() {
    // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
    var argType, nodes = $.map(arguments, function(arg) {
      var arr = []
      argType = type(arg)
      if (argType == "array") {
        arg.forEach(function(el) {
          if (el.nodeType !== undefined) return arr.push(el)
          else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
          arr = arr.concat(zepto.fragment(el))
        })
        return arr
      }
      return argType == "object" || arg == null ?
        arg : zepto.fragment(arg)
    }),
        parent, copyByClone = this.length > 1
    if (nodes.length < 1) return this

    return this.each(function(_, target) {
      parent = inside ? target : target.parentNode

      // convert all methods to a "before" operation
      target = operatorIndex == 0 ? target.nextSibling :
      operatorIndex == 1 ? target.firstChild :
      operatorIndex == 2 ? target :
      null

      var parentInDocument = $.contains(document.documentElement, parent)

      nodes.forEach(function(node) {
        if (copyByClone) node = node.cloneNode(true)
        else if (!parent) return $(node).remove()

        parent.insertBefore(node, target)
        if (parentInDocument) traverseNode(node, function(el) {
          if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
              (!el.type || el.type === 'text/javascript') && !el.src) {
            var target = el.ownerDocument ? el.ownerDocument.defaultView : window
            target['eval'].call(target, el.innerHTML)
          }
        })
          })
    })
  }

Call Method

Before analyzing, look at the usage of these methods:

after(content)
prepend(content)
before(content)
append(content)

The parameter content can be an html string, a dom node, or an array of nodes.After inserts content after each collection element, beforeOn the contrary, before each collection element, prepend inserts content at the initial position of each collection element, and append inserts content at the end of each collection element.It is important to note that before and after inserted contents are outside elements, while prepend and append inserted contents are inside elements.

Convert the parameter content to an array of node s

var inside = operatorIndex % 2 //=> prepend, append

Iterate through adjacencyOperators to get the corresponding method name operator and the index operatorIndex of the method name in the array.

An inside variable is defined, and when operatorIndex is even, the inside value is true, that is, when the operator value is prepend or append, the inside value is true.This can be used to distinguish whether a content is inserted inside or outside an element.

$.fn[operator] sets the corresponding property value (method name) for the $.fn object.

var argType, nodes = $.map(arguments, function(arg) {
  var arr = []
  argType = type(arg)
  if (argType == "array") {
    arg.forEach(function(el) {
      if (el.nodeType !== undefined) return arr.push(el)
      else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
      arr = arr.concat(zepto.fragment(el))
    })
    return arr
  }
  return argType == "object" || arg == null ?
    arg : zepto.fragment(arg)
}),

The variable argType holds the type of variable variable, that is, the type of content.nodes are an array of node s converted from content.

This uses $.map arguments to get the parameter content, there is only one parameter, so why not use arguments[0] to get it?This is because $.map flattens arrays, which you can see here. Tool functions for reading zepto sources>.

First, the type of the parameter is obtained by using the internal function type. For the implementation of type, in the Internal method for reading Zepto source The book has already been analyzed.

If the parameter content, that is, when the Arg type is an array, traverses through the arg, if the element in the array has a nodeType attribute, it is determined to be a node node, then push it into the container arr; if the element in the array is a zepto object (judged by $.zepto.isZ), the method has already been implemented in the Read the magic of Zepto Source$ Without passing a parameter, call the get method, return an array, then call the concat method of the array to merge the array, get method in the Collection operation for reading Zepto source Has been parsed; otherwise, for an html string, call zepto.fragment processing and combine the returned numbers, `zepto.fragment'in the Read the magic of Zepto Source$ There is analysis in the book.

If the parameter type is object (that is, zepto object) or null, it is returned directly.

Otherwise, it is an html string, calling zepto.fragment processing.

parent, copyByClone = this.length > 1
if (nodes.length < 1) return this

A parent variable is also defined to hold the parent node inserted by the content; when the number of elements in the collection is greater than 1, the variable copyByClone has a value of true, which is added later.

If the number of nodes is less than 1, that is, when the node you need to insert is empty, no further processing is done and this is returned so that you can chain it.

Simulate all operations with insertBefore

return this.each(function(_, target) {
  parent = inside ? target : target.parentNode

  // convert all methods to a "before" operation
  target = operatorIndex == 0 ? target.nextSibling :
  operatorIndex == 1 ? target.firstChild :
  operatorIndex == 2 ? target :
  null

  var parentInDocument = $.contains(document.documentElement, parent)
  ...
})

each traversal of a collection

parent = inside ? target : target.parentNode

If the node needs to be inserted inside the target element target, parent is set to the target element, otherwise it is set to the parent element of the current element.

target = operatorIndex == 0 ? target.nextSibling :
  operatorIndex == 1 ? target.firstChild :
  operatorIndex == 2 ? target :
  null

This section simulates all operations using the dom native method insertBefore.If operatorIndex == 0 is after, the node should be inserted after the target element target, in front of the next sibling element of target; when operatorIndex == 1 is prepend, the node should be inserted at the beginning of the target element, in front of the first child element of target; when operatorIndex == 2 is before, insertBefore is justIt corresponds to the element itself.When the second parameter of insertBefore is null, insertBefore inserts the node at the end of the child node, which corresponds to append.See the documentation: Node.insertBefore()

var parentInDocument = $.contains(document.documentElement, parent)

Call the $.contains method to detect whether the parent node parent is in the document.The $.contains method is used in the Tool functions for reading zepto sources This has already been analyzed.

Insert an array of node s into an element

nodes.forEach(function(node) {
  if (copyByClone) node = node.cloneNode(true)
  else if (!parent) return $(node).remove()

  parent.insertBefore(node, target)
  ...
})

If a node needs to be replicated (that is, when the number of elements in the collection is greater than 1), the node is replicated using the node method cloneNode, and the parameter true indicates that the information about the node's children and attributes is to be replicated as well.Why duplicate nodes when a collection element is greater than 1?Because insertBefore inserts a reference to a node, traversing through all elements in the collection will insert the same reference to each element if the node is not cloned, and only the node will be inserted into the last element.

If the parent node does not exist, the node is deleted and no further action is taken.

Insert the node into the element using the insertBefore method.

Processing scripts within script tags

if (parentInDocument) traverseNode(node, function(el) {
  if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
      (!el.type || el.type === 'text/javascript') && !el.src) {
    var target = el.ownerDocument ? el.ownerDocument.defaultView : window
    target['eval'].call(target, el.innerHTML)
  }
})

If the parent element is within the document, traverseNode is called to process all the child nodes of the node and node.It is primarily to detect whether a node or its children are script tags that do not point to external scripts.

el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT'

This section is used to determine whether it is a script tag, which is determined by whether the nodeName property of the node is a script.

!el.type || el.type === 'text/javascript'

There is no type attribute, or the type attribute is'text/javascript'.This means that only JavaScript is processed, because the type attribute is not necessarily specified as text/javascript, and only when it is specified as test/javascript or empty will it be processed as javascript.see MDN Document <script>

!el.src

There is no external script.

var target = el.ownerDocument ? el.ownerDocument.defaultView : window

Whether the ownerDocument property exists or not, the ownerDocument returns the root node of the element, that is, the document object. The defaultView property of the document object returns the window object associated with the document object. This mainly deals with script s in iframe, since there are separate windows objects in the iframe.If this property does not exist, the current window object is used by default.

target['eval'].call(target, el.innerHTML)

Finally, the eval method of window is called to execute the script, which is obtained using el.innerHTML.

Why do you want to do this with script elements alone?For security reasons, when a script is inserted into the dom by insertBefore, it will not execute, so eval is required to handle it.

Generate insertAfter, prependTo, insertBefore, and appendTo methods

Let's first look at how these methods are called

insertAfter(target)
insertBefore(target)
appendTo(target)
prependTo(target)

These methods insert elements from the collection into the target element target, exactly the opposite of after, before, append, and prepend.

Their correspondence is as follows:

after    => insertAfter
prepend  => prependTo
before   => insertBefore
append   => appendTo

So you can call the appropriate methods to generate them.

$.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function(html) {
  $(html)[operator](this)
  return this
}
inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')

This is the generation method name. If prepend or append, splice To at the back and insert at the front if Before or After.

$(html)[operator](this)

Simply invoke the corresponding method in reverse.

At this point, this similar method generator generates eight methods, after, prepend, before, append, insertAfter, insertBefore, appendTo, and prependTo, which are fairly efficient.

.empty()

empty: function() {
  return this.each(function() { this.innerHTML = '' })
},

The function of empty is to empty the contents of all collection elements, invoking the innerHTML property of the node to be set to empty.

.replaceWith()

replaceWith: function(newContent) {
  return this.before(newContent).remove()
},

Replace all collection elements with the specified content newContent, which is of the same type as the parameter type of before.

ReplceWidth first calls before to insert the newContent before the corresponding element, and then deletes the element so that the replacement is achieved.

.wrapAll()

wrapAll: function(structure) {
  if (this[0]) {
    $(this[0]).before(structure = $(structure))
    var children
    // drill down to the inmost element
    while ((children = structure.children()).length) structure = children.first()
    $(structure).append(this)
  }
  return this
},

Wraps all elements in a collection into a specified structure.

If the set element exists, that is, this[0], then follow up, otherwise return this for chain operation.

Call the before method to insert the specified structure before the first collection element, that is, before all collection elements

while ((children = structure.children()).length) structure = children.first()

Find the child elements of the structure, and if the child elements exist, assign the structure to the first child element of the structure until the first child element at the deepest level of the structure is found.

Insert all elements in the collection at the end of the structure, and if there are child elements in the structure, insert them at the end of the first child element at the deepest level.This wraps all the elements in the collection inside the structure.

.wrap()

wrap: function(structure) {
  var func = isFunction(structure)
  if (this[0] && !func)
    var dom = $(structure).get(0),
        clone = dom.parentNode || this.length > 1

    return this.each(function(index) {
      $(this).wrapAll(
        func ? structure.call(this, index) :
        clone ? dom.cloneNode(true) : dom
      )
    })
},

For each element in a collection to be wrapped with the specified structure, a structure can be a single or nested element, an html element or a dom node, or a callback function that takes two parameters, the current element and the current element's index in the collection, and returns a qualified wrapping structure.

var func = isFunction(structure)

Determining whether a structure is a function

if (this[0] && !func)
  var dom = $(structure).get(0),
      clone = dom.parentNode || this.length > 1

If the collection is not empty and the structure is not a function, the structure is converted to a node, converted by $(structure).get(0), and assigned to the variable dom.If the parentNode of the DOM exists or the number of collections is greater than 1, the clone value is true.

return this.each(function(index) {
  $(this).wrapAll(
  func ? structure.call(this, index) :
  clone ? dom.cloneNode(true) : dom
  )
})

Traverse the collection, call the wrapAll method, and if the structure is a function, pass the result returned by the callback function to wrapAll as a parameter.

Otherwise, if clone is true, the dom, or a copy of the wrap element, is passed to wrapAll; otherwise, the DOM is passed directly to wrapAll.The reason for passing the copy here is the same as in the generator and avoids referencing the DOM node.If the parentNode of the DOM exists, it indicates that the DOM has always belonged to a node, and if DOM is used directly, it will destroy the original structure.

.wrapInner()

wrapInner: function(structure) {
  var func = isFunction(structure)
  return this.each(function(index) {
    var self = $(this),
        contents = self.contents(),
        dom = func ? structure.call(this, index) : structure
    contents.length ? contents.wrapAll(dom) : self.append(dom)
  })
},

Wraps the contents of each element in the collection with the specified structure structure.Structure has the same parameter type as wrap.

Traverse the collection, call the contents method, and get the content of the element. The contents method is in the Reading Zepto Source Collection Element Lookup "Has been analyzed.

If the structure is a function, the result returned by the function is assigned to dom, otherwise the structure is directly assigned to dom.

If contents.length exists, that is, the element is not empty, wrapAll method is called to wrap the element's content in the dom; if it is empty, the DOM is inserted directly at the end of the element and the DOM is wrapped inside the element.

.unwrap()

unwrap: function() {
  this.parent().each(function() {
    $(this).replaceWith($(this).children())
  })
  return this
},

When the wrapping of all elements in the collection is removed, the parent element is also removed, but the child elements of the parent element are retained.

It is also easy to implement by traversing the parent element of the current element and replacing it with the child element of the parent element.

.clone()

clone: function() {
  return this.map(function() { return this.cloneNode(true) })
},

Each element in each collection creates a copy and returns the copy collection.

Traverse through the element collection, calling node's native method cloneNode to create a copy.Note that cloneNode does not copy elements'original data and event handlers into a copy.

Series articles

  1. Read the code structure of the Zepto source

  2. Internal method for reading Zepto source

  3. Tool functions for reading Zepto sources

  4. Read the magic of Zepto Source$

  5. Collection operation for reading Zepto source

  6. Reading Zepto Source Collection Element Lookup

Reference resources

License

Finally, all articles will be sent to the WeChat public number synchronously. Welcome to your attention and comments:

Author: Diagonal other side

Posted by Chips on Tue, 25 Jun 2019 10:24:54 -0700