跟著Zepto學(xué)dom(二)

上期中我們看了元素選擇器泰鸡、attr和class的源碼暇唾,今天我們來看下其append等的操作是如何進(jìn)行的。

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

this.cloneNode(flag)其返回this的節(jié)點(diǎn)的一個(gè)副本flag為true表示深度克隆引镊,為了兼容性胸嘁,該flag最好填寫。

children: function(selector){
  return filtered(this.map(function(){ return children(this) }), selector)
}
function filtered(nodes, selector) {
//當(dāng)selector不為空的時(shí)候凹蜂。
  return selector == null ? $(nodes) : $(nodes).filter(selector)
}
function children(element) {
//為了兼容性
  return 'children' in element ?
//返回 一個(gè)Node的子elements馍驯,在IE8中會出現(xiàn)包含注釋節(jié)點(diǎn)的情況,
//但在此處并不會調(diào)用該方法玛痊。
    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)
  }))
}
//這也是一個(gè)重點(diǎn)汰瘫,下面將重點(diǎn)分拆這個(gè)
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選擇器字符串颈娜。該方法的兼容性不錯(cuò)[element.matches的兼容性一覽](https://caniuse.com/#search=matches)剑逃,在移動端中完全可以放心使用,當(dāng)然在一些老版本上就不可以避免的要添加上一些前綴官辽。如element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector ||element.matchesSelector所示蛹磺。

注:其實(shí)在IE8中也可以通過polyfill的形式去實(shí)現(xiàn)該方法,如下所示:

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方法就如上面所示同仆,其實(shí)其內(nèi)部只是調(diào)用了幾個(gè)不同的函數(shù)而已萤捆。

closest

closest: function(selector, context){
  var nodes = [], collection = typeof selector == 'object' && $(selector)
  this.each(function(_, node){
//當(dāng)node存在同時(shí)(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節(jié)點(diǎn)
//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

這幾種方法都是先通過調(diào)用zepto.fragment該方法統(tǒng)一將content的內(nèi)容生成dom節(jié)點(diǎn)俗或,再進(jìn)行插入動作市怎。同時(shí)需要注意的是傳入的內(nèi)容可以為html字符串,dom節(jié)點(diǎn)或者節(jié)點(diǎn)組成的數(shù)組辛慰,如下所示:'<p>這是一個(gè)dom節(jié)點(diǎn)</p>',document.createElement('p'),['<p>這是一個(gè)dom節(jié)點(diǎn)</p>',document.createElement('p')]区匠。

var adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ];
adjacencyOperators.forEach(function(operator, operatorIndex) {
//prepend和append的時(shí)候inside為1
  var inside = operatorIndex % 2
  $.fn[operator] = function(){
    var argType, nodes = $.map(arguments, function(arg) {
          var arr = []
//判斷arg的類型,var arg = document.createElement('span');此時(shí)div類型為object
//var arg = $('div'),此時(shí)類型為array
//var arg = '<div>這是一個(gè)div</div>',此時(shí)類型為string
          argType = type(arg)
//傳入為一個(gè)數(shù)組時(shí)
          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){
//當(dāng)為prepend和append的時(shí)候其parent為target
      parent = inside ? target : target.parentNode
//將所有的動作全部調(diào)成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)其在當(dāng)前節(jié)點(diǎn)的某個(gè)子節(jié)點(diǎn)之前再插入一個(gè)子節(jié)點(diǎn)
//如果parent為null則node將被插入到子節(jié)點(diǎn)的末尾驰弄。如果node已經(jīng)在DOM樹中,node首先會從DOM樹中移除
        parent.insertBefore(node, target)
//如果父元素在 document 內(nèi)速客,則調(diào)用 traverseNode 來處理 node 節(jié)點(diǎn)及 node 節(jié)點(diǎn)的所有子節(jié)點(diǎn)戚篙。主要是檢測 node 節(jié)點(diǎn)或其子節(jié)點(diǎn)是否為 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中有獨(dú)立的window對象
//同時(shí)由于insertBefore插入腳本溺职,并不會執(zhí)行腳本岔擂,所以要通過evel的形式去設(shè)置。
            var target = el.ownerDocument ? el.ownerDocument.defaultView : window
            target['eval'].call(target, el.innerHTML)
          }
        })
      })
    })
  }
  //該方法生成了方法名浪耘,同時(shí)對after乱灵、prepend、before点待、append阔蛉、insertBefore、insertAfter癞埠、prependTo八個(gè)方法状原。其核心都是類似的。
  $.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
//如果其只為一個(gè)節(jié)點(diǎn)苗踪,里面沒有文本節(jié)點(diǎn)和子節(jié)點(diǎn)外颠区,類似<p></p>,則dom為p元素通铲。
  if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
  if (!dom) {
//對html進(jìn)行修復(fù)毕莱,如果其為<p/>則修復(fù)為<p></p>
    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
//設(shè)置標(biāo)簽的名字。
    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中刪除一個(gè)節(jié)點(diǎn)并返回刪除的節(jié)點(diǎn)
      container.removeChild(this)
    })
  }
//檢測屬性是否為對象颅夺,如果為對象的化朋截,則給元素設(shè)置屬性。
  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樣式時(shí)
  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轉(zhuǎn)為駝峰命名fontSize
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
//將駝峰命名轉(zhuǎn)為普通css:fontSize=>font-size
function dasherize(str) {
  return str.replace(/::/g, '/')
         .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
         .replace(/([a-z\d])([A-Z])/g, '$1_$2')
         .replace(/_/g, '-')
         .toLowerCase()
}
//可能對數(shù)值需要添加'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))
其是一個(gè)可以獲取當(dāng)前元素所有最終使用的CSS屬性值吧黄,返回一個(gè)樣式對象部服,只讀,其具體用法如下所示拗慨,同時(shí)讀取屬性的值是通過getPropertyValue去獲壤恕:

getComputedStyle.png

其與style的區(qū)別在于奉芦,后者是可寫的,同時(shí)后者只能獲取元素style屬性中的CSS樣式剧蹂,而前者可以獲取最終應(yīng)用在元素上的所有CSS屬性對象声功。該方法的兼容性不錯(cuò),能夠兼容IE9+,但是在IE9中宠叼,不能夠讀取偽類的CSS先巴。

offset

offset: function(coordinates){
  if (coordinates) return this.each(function(index){
    var $this = $(this),
        coords = funcArg(this, coordinates, index, $this.offset()),
        parentOffset = $this.offsetParent().offset(),
        props = {
          top:  coords.top  - parentOffset.top,
          left: coords.left - parentOffset.left
        }

    if ($this.css('position') == 'static') props['position'] = 'relative'
    $this.css(props)
  })
  if (!this.length) return null
//當(dāng)獲取的元素就是document該元素
  if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0]))
    return {top: 0, left: 0}
  var obj = this[0].getBoundingClientRect()
  return {
    left: obj.left + window.pageXOffset,
    top: obj.top + window.pageYOffset,
    width: Math.round(obj.width),
    height: Math.round(obj.height)
  }
}

getBoundingClientRect 該方法用于獲取某個(gè)元素相對于視窗的位置集合。
這個(gè)屬性特別需要注意的是DOM元素到瀏覽器可視范圍的距離并不包括文檔卷起來的部分冒冬。所以為了獲取元素在頁面上的位置并且解決瀏覽器的兼容性問題就可以使用如下方法:

documentScrollTop = document.documentElement.scrollTop || window.pageYOffset
 || document.body.scrollTop;
documentScrollLeft = document.documentElement.scrollLeft || window.pageXOffset || document.body.scrollLeft;
X = document.querySelector("#box").getBoundingClientRect().top+documentScrollTop;
Y = document.querySelector("#box").getBoundingClientRect().left+documentScrollLeft;

在IE9及以上的IE瀏覽器中該集合包含left,top,bottom,right,height,width六個(gè)屬性筹裕,但是在IE8中,其缺少width,height窄驹,但是在IE8中可以使用如下代碼得到width和height屬性:

height = document.getElementById("box").getBoundingClientRect().right-document.getElementById("box").getBoundingClientRect().left;
width = document.getElementById("box").getBoundingClientRect().bottom-document.getElementById("box").getBoundingClientRect().top;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市证逻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖八千,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馍惹,死亡現(xiàn)場離奇詭異,居然都是意外死亡龙宏,警方通過查閱死者的電腦和手機(jī)棵逊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來银酗,“玉大人辆影,你說我怎么就攤上這事∈蛱兀” “怎么了蛙讥?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灭衷。 經(jīng)常有香客問我次慢,道長,這世上最難降的妖魔是什么翔曲? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任迫像,我火速辦了婚禮,結(jié)果婚禮上瞳遍,老公的妹妹穿的比我還像新娘闻妓。我一直安慰自己,他們只是感情好傅蹂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布纷闺。 她就那樣靜靜地躺著算凿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪犁功。 梳的紋絲不亂的頭發(fā)上氓轰,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音浸卦,去河邊找鬼署鸡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛限嫌,可吹牛的內(nèi)容都是我干的靴庆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼怒医,長吁一口氣:“原來是場噩夢啊……” “哼炉抒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稚叹,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤焰薄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扒袖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塞茅,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年季率,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了野瘦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飒泻,死狀恐怖鞭光,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蠢络,我是刑警寧澤衰猛,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站刹孔,受9級特大地震影響啡省,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜髓霞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一卦睹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧方库,春花似錦结序、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垃环。三九已至,卻和暖如春返敬,著一層夾襖步出監(jiān)牢的瞬間遂庄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工劲赠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涛目,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓凛澎,卻偏偏與公主長得像霹肝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子塑煎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案沫换? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,734評論 1 92
  • 原文 鏈接 關(guān)注公眾號獲取更多資訊 一、基本類型介紹 1.1 Node類型 DOM1級定義了一個(gè)Node接口最铁,該接...
    程序員poetry閱讀 3,930評論 7 34
  • 本文整理自《高級javascript程序設(shè)計(jì)》 DOM(文檔對象模型)是針對HTML和XML文檔的一個(gè)API(應(yīng)用...
    SuperSnail閱讀 567評論 0 1
  • 剛組建就打擊 我還想著獨(dú)立后,我做產(chǎn)品嗤无,姜做后臺震束,林做前端,三個(gè)人一起好好做出東西当犯,然后找劉總投資呢垢村。 一獨(dú)立后,...
    悟靜家閱讀 445評論 1 0
  • iOS實(shí)際上算是unix的一個(gè)分支嚎卫,所以iOS上的多線程可以使用pthread嘉栓。不過Apple另外提供了GCD來簡...
    ax4c閱讀 444評論 0 0