跟着Zepto学dom(二)

上期中我们看了元素选择器、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的兼容性一览](https://caniuse.com/#search=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节点或者节点组成的数组,如下所示:'<p>这是一个dom节点</p>',document.createElement('p'),['<p>这是一个dom节点</p>',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-z\d])([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去获取:

getComputedStyle.png

其与style的区别在于,后者是可写的,同时后者只能获取元素style属性中的CSS样式,而前者可以获取最终应用在元素上的所有CSS属性对象。该方法的兼容性不错,能够兼容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
//当获取的元素就是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 该方法用于获取某个元素相对于视窗的位置集合。
这个属性特别需要注意的是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六个属性,但是在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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,727评论 1 92
  • 原文 链接 关注公众号获取更多资讯 一、基本类型介绍 1.1 Node类型 DOM1级定义了一个Node接口,该接...
    程序员poetry阅读 3,928评论 7 34
  • 本文整理自《高级javascript程序设计》 DOM(文档对象模型)是针对HTML和XML文档的一个API(应用...
    SuperSnail阅读 562评论 0 1
  • 刚组建就打击 我还想着独立后,我做产品,姜做后台,林做前端,三个人一起好好做出东西,然后找刘总投资呢。 一独立后,...
    悟静家阅读 442评论 1 0
  • iOS实际上算是unix的一个分支,所以iOS上的多线程可以使用pthread。不过Apple另外提供了GCD来简...
    ax4c阅读 442评论 0 0