跟著Zepto學dom(二)

前端一小卒發表於2019-02-22

上期中我們看了元素選擇器、attr和class的原始碼,今天我們來看下其append等的操作是如何進行的。

clone: function(){
  return this.map(function(){ return this.cloneNode(true) })
}複製程式碼

this.cloneNode(flag) 其返回this的節點的一個副本flag為true表示深度克隆,為了相容性,該flag最好填寫。

children: function(selector){
  return filtered(this.map(function(){ return children(this) }), selector)
}
function filtered(nodes, selector) {
//當selector不為空的時候。
  return selector == null ? $(nodes) : $(nodes).filter(selector)
}
function children(element) {
//為了相容性
  return `children` in element ?
//返回 一個Node的子elements,在IE8中會出現包含註釋節點的情況,
//但在此處並不會呼叫該方法。
    slice.call(element.children) :
//不支援children的情況下
    $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}
filter: function(selector){
  if (isFunction(selector)) return this.not(this.not(selector))
  return $(filter.call(this, function(element){
    return zepto.matches(element, selector)
  }))
}
//這也是一個重點,下面將重點分拆這個
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)
  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
}複製程式碼

children方法中的`children` in element是為了檢測瀏覽器是否支援children方法,children相容到IE9
Element.matches(selectorString)如果元素被指定的選擇器字串選擇,否則該返回true,selectorString為css選擇器字串。該方法的相容性不錯element.matches的相容性一覽,在移動端中完全可以放心使用,當然在一些老版本上就不可以避免的要新增上一些字首。如element.matches || element.webkitMatchesSelector ||
element.mozMatchesSelector || element.oMatchesSelector ||element.matchesSelector
所示。

注:其實在IE8中也可以通過polyfill的形式去實現該方法,如下所示:

if (!Element.prototype.matches) {
    Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {}
            return i > -1;
        };
}複製程式碼

children方法就如上面所示,其實其內部只是呼叫了幾個不同的函式而已。

closest

closest: function(selector, context){
  var nodes = [], collection = typeof selector == `object` && $(selector)
  this.each(function(_, node){
//當node存在同時(collection中擁有node元素或者node中匹配到了selector)
    while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
//如果給定了context,則node不能等於該context
//注意只要node===context或者isDocument為true,那麼node則為false
      node = node !== context && !isDocument(node) && node.parentNode
    if (node && nodes.indexOf(node) < 0) nodes.push(node)
  })
  return $(nodes)
}
//檢測其是否為document節點
//DOCUMENT_NODE的更多用法可以前往https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
function isDocument(obj)
  { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }複製程式碼

after prepend before append

這幾種方法都是先通過呼叫zepto.fragment該方法統一將content的內容生成dom節點,再進行插入動作。同時需要注意的是傳入的內容可以為html字串,dom節點或者節點組成的陣列,如下所示:`

這是一個dom節點

`,document.createElement(`p`),[`

這是一個dom節點

`,document.createElement(`p`)]。

var adjacencyOperators = [ `after`, `prepend`, `before`, `append` ];
adjacencyOperators.forEach(function(operator, operatorIndex) {
//prepend和append的時候inside為1
  var inside = operatorIndex % 2
  $.fn[operator] = function(){
    var argType, nodes = $.map(arguments, function(arg) {
          var arr = []
//判斷arg的型別,var arg = document.createElement(`span`);此時div型別為object
//var arg = $(`div`),此時型別為array
//var arg = `<div>這是一個div</div>`,此時型別為string
          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){
//當為prepend和append的時候其parent為target
      parent = inside ? target : target.parentNode
//將所有的動作全部調成before的動作,其只是改變parent和target的不同
      target = operatorIndex == 0 ? target.nextSibling :
               operatorIndex == 1 ? target.firstChild :
               operatorIndex == 2 ? target :
               null
//檢測parent是否在document
      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,parent)其在當前節點的某個子節點之前再插入一個子節點
//如果parent為null則node將被插入到子節點的末尾。如果node已經在DOM樹中,node首先會從DOM樹中移除
        parent.insertBefore(node, target)
//如果父元素在 document 內,則呼叫 traverseNode 來處理 node 節點及 node 節點的所有子節點。主要是檢測 node 節點或其子節點是否為 script且沒有src地址。
        if (parentInDocument) traverseNode(node, function(el){
          if (el.nodeName != null && el.nodeName.toUpperCase() === `SCRIPT` &&
             (!el.type || el.type === `text/javascript`) && !el.src){
//由於在iframe中有獨立的window物件
//同時由於insertBefore插入指令碼,並不會執行指令碼,所以要通過evel的形式去設定。
            var target = el.ownerDocument ? el.ownerDocument.defaultView : window
            target[`eval`].call(target, el.innerHTML)
          }
        })
      })
    })
  }
  //該方法生成了方法名,同時對after、prepend、before、append、insertBefore、insertAfter、prependTo八個方法。其核心都是類似的。
  $.fn[inside ? operator+`To` : `insert`+(operatorIndex ? `Before` : `After`)] = function(html){
    $(html)[operator](this)
    return this
  }
})
zepto.fragment = function(html, name, properties) {
  var dom, nodes, container,
  containers = {
    `tr`: document.createElement(`tbody`),
    `tbody`: table, `thead`: table, `tfoot`: table,
    `td`: tableRow, `th`: tableRow,
    `*`: document.createElement(`div`)
  },
    singleTagRE = /^<(w+)s*/?>(?:</1>|)$/,
    tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([w:]+)[^>]*)/>/ig
//如果其只為一個節點,裡面沒有文字節點和子節點外,類似<p></p>,則dom為p元素。
  if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
  if (!dom) {
//對html進行修復,如果其為<p/>則修復為<p></p>
    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
//設定標籤的名字。
    if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
    if (!(name in containers)) name = `*`
    container = containers[name]
    container.innerHTML = `` + html
    dom = $.each(slice.call(container.childNodes), function(){
//從DOM中刪除一個節點並返回刪除的節點
      container.removeChild(this)
    })
  }
//檢測屬性是否為物件,如果為物件的化,則給元素設定屬性。
  if (isPlainObject(properties)) {
    nodes = $(dom)
    $.each(properties, function(key, value) {
      if (methodAttributes.indexOf(key) > -1) nodes[key](value)
      else nodes.attr(key, value)
    })
  }
  return dom
}複製程式碼

css

css: function(property, value){
//為讀取css樣式時
  if (arguments.length < 2) {
    var element = this[0]
    if (typeof property == `string`) {
      if (!element) return
      return element.style[camelize(property)] || getComputedStyle(element, ``).getPropertyValue(property)
    } else if (isArray(property)) {
      if (!element) return
      var props = {}
      var computedStyle = getComputedStyle(element, ``)
      $.each(property, function(_, prop){
        props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
      })
      return props
    }
  }

  var css = ``
  if (type(property) == `string`) {
    if (!value && value !== 0)
      this.each(function(){ this.style.removeProperty(dasherize(property)) })
    else
      css = dasherize(property) + ":" + maybeAddPx(property, value)
  } else {
    for (key in property)
      if (!property[key] && property[key] !== 0)
        this.each(function(){ this.style.removeProperty(dasherize(key)) })
      else
        css += dasherize(key) + `:` + maybeAddPx(key, property[key]) + `;`
  }

  return this.each(function(){ this.style.cssText += `;` + css })
}
//css將font-size轉為駝峰命名fontSize
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : `` }) }
//將駝峰命名轉為普通css:fontSize=>font-size
function dasherize(str) {
  return str.replace(/::/g, `/`)
         .replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`)
         .replace(/([a-zd])([A-Z])/g, `$1_$2`)
         .replace(/_/g, `-`)
         .toLowerCase()
}
//可能對數值需要新增`px`
function maybeAddPx(name, value) {
  return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
}複製程式碼

getComputedStyle
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
其是一個可以獲取當前元素所有最終使用的CSS屬性值,返回一個樣式物件,只讀,其具體用法如下所示,同時讀取屬性的值是通過getPropertyValue去獲取:

其與style的區別在於,後者是可寫的,同時後者只能獲取元素style屬性中的CSS樣式,而前者可以獲取最終應用在元素上的所有CSS屬性物件。該方法的相容性不錯,能夠相容IE9+,但是在IE9中,不能夠讀取偽類的CSS。

相關文章