Skip to content

Latest commit

 

History

History
275 lines (204 loc) · 11.6 KB

README.md

File metadata and controls

275 lines (204 loc) · 11.6 KB

#nes a javascript selector with incredible scalability, but still very fast

1 支持度

1.1 框架、加载器

  1. nes在NEJ目前是以匿名模块的方式,只要加载nej里的选择器适配模块{lib}util/query/query.js,就可以直接在nej中使用选择器如: _v._$addEvent('#abc','click',function) 具体适配方式请参考NEJ
  2. amd
  3. commonjs
  4. 其他注册到全全局

1.2 浏览器

  1. ie6+
  2. 其他浏览器的最新版,即我没有测试chrome、opera的低版本

1.3 选择器

移步Wiki页

简而言之,除了部分伪类、全部伪元素没有支持外,其余都支持(其实更多选择器的支持不难如果JS是可以实现的话,关键是整个流程的逻辑不要错)

1.4 速度测试

  1. 剽窃了sizzle的速度测试用例,直接在sizzle目录的speed里运行,貌似不能本地运行。推荐使用puer工具可以帮助你在当前路径下建立一个自动刷新的静态服务器。速度被querySelectorAll秒杀,但是秒杀sizzle(当然Sizzle的关键是它的稳定性)。

  2. 由于这个测试用例貌似不支持ie,所以你也可以再test目录下运行test.html检查它在IE下的兼容性,由于出来仓促、难免有违覆盖的地方,也希望大家能提供测试BUG

2 API

这里你可以慢慢看到nes的真正的特别之处,不仅仅是 速度,我从来不以为做一个最快的选择器为目标,或许一次简单的缓动动画对UI的影响就超过了你使用几千次选择器,不要牺牲了代码和接口的质量换取一点点速度评分上的提升。nes源码中除了一两个主函数刚刚超过20行,其余代码都在15行以内, 一点点速度降低换来了优秀的接口和可维护性高的源码

2.1 标准API

标准API有4个:(1)one、(2)all、(3)matches、(4)create。前三个分别对应JS selector level 2的querySelector、querySelectorAll与matches(其中matches大部分的现代浏览器都还不支持). 第四个用来通过选择器生成dom节点

  • nes.one(String selector,Element context): 返回第一个匹配selector(在context的subtree中)的元素

  • nes.all(String selector,Element context): 返回所有匹配匹配selector(在context的subtree中)的元素,如a

nes.one("tr:nth-child(even) > td:nth-child(odd)",someTable) //-> 取得someTable下的所有偶数列中奇数行
  • nes.matches(Element node,String selector)(selector API level 2): 返回node是否匹配selector这个选择器。如利用事件代理时,你不需要再去调用标准dom方法去测试节点是否满足某种条件,直接使用matches进行判断, 如:
container.addEventListener("click", function(e){
        if(nes.matches(e.target, ".signup a.top")){//直接利用选择器判断是否是注册表单下的置顶按钮
            //_onTop() ==> 处理逻辑
        }
    }
},false)
  • nes.create(String selector)(同mootools的new Element):通过选择器生成简单节点,如nes.create(ul#id.class1.class2>li.item>a[alt=haah])输出:
<ul id="id" class="class1 class2">
  <li class="item">
    <a alt="haah"></a>
  </li>
</ul>

注意: 只支持单节点的嵌套和'>'连接符,复杂逻辑请用模版或手动生成

一般开发人员看到这里就可以结束, 需要有更深入了解和扩展需求的继续向下。

2.2 私有API

这几个API主要为了测试

  1. nes.parse: 解析选择器(字符串)使其可以方便的被find使用, nes.all依赖方法 比如 div.example[class] p:nth-child(2n),p.content ~ span 返回的data是:
[
  [
    {"tag":"div","classList":["example"],"attributes":[{"key":"class"}],"combo":" "}
    {"tag":"p","pesudos":[{"name":"nth-child","param":{"start":2,"step":2}}]}
  ],
  [
    {"tag":"p","classList":["content"],"combo":"~"},
    {"tag":"span"}
  ]
]
  1. nes.find: 从parse传入的parseData进行节点查找,nes.all依赖方法

  2. nes._get: 相当于nes.all, 即不去调用原生querySelector直接用nes查找(纯测试用)

2.3 扩展API

扩展分为两个层级,一种为内建扩展,即在内部语法之上的扩展。另一种为语法扩展,即创建一个与Id选择符、伪类选择符等价的Simple Selector,下面会以__使用场景__的形式介绍这几种扩展,这些场景都是建立在浏览器已经实现了querySelector的前提下, 用来证明选择器扩展的必要性

注意:这里不会描述所有应用场景,大家可以去test目录查看扩展范例

2.3.1 内建扩展API

内建扩展分为三类: (1) 对伪类的扩展(pesudos); (2) 对属性的扩展(operators——因为属性只有操作符这一个动点); (3) 对连接符的扩展(combos)

注意1:内建扩展是一种动态扩展,是安全的,它会把你的关键字添加到内部的正则式中

注意2:所有的扩展都可以传入一个Object, 来实现一次扩展多个

2.3.1.1 内建扩展——伪类nes.pesudos(String name,Function matcher)

场景描述:你需要获取所有的ul元素,这个元素中包含有满足(li.trigger a[href])的a标签

原始做法:

var lists = document.querySelectorAll("ul")
for(var i = lists.length; i--;){
    var list = lists[i]
    if(!list.querySelectorAll("li.trigger a[href]")){
        lists.splice(i, 1)
    }
}
return lists

理想做法: nes.all('ul:include(li.trigger a[href])')

你需要做的扩展是:

// 其中node表示当前遍历到的节点, param代表pesudo的参数如本例的`li.trigger a[href]`
nes.pesudos("include", function(node, param){
  return !!nes.one( param, node) // 返回bool值证明这个节点是否
})
2.3.1.2 内建扩展——操作符nes.combos(String name,Function matcher)

场景描述: 坑爹啊,标准selector竟然不提供__不等于__操作符的支持(!=),让我们用一行代码搞定它

理想做法: nes.all('div[class!=made_up]')

你需要做的扩展是:

// 与伪类扩展一样 ,第一个参数表示当前遍历到的节点,你要决定它是否通过
// key 代表属性名(如本例的class)、value代表属性值(如本里的made_up)
nes.operators("!=", function(node, key, value){
  return node.getAttribute(key) !== value
})
2.3.1.3 内建扩展——连接符nes.combos(String name,Function finder)

注意combo的扩展与上面两个扩展都不同,因为它是连接符而不是前两个的Simple Selector,它传入的是finder函数,目的是找到你满足的元素

场景描述: 你需要获得 ul.test li.trigger节点 前的所有li节点(即连接符~的相反版)

原生方法: &$@&$&@($(!)!)##!&$^!@#$%^&(&^%$#@#$%^&()(%$!@#$% (真的很烦,你们可以私下去尝试下)

理想做法: nes.all ('ul.test1 li.trigger & li')

你需要做的扩展是:

// 这里直接一起扩展了~、+的相反版
nes.combos({
  // 相当于 ~ 的相反版 , match是一个动态产生的方法,它代表这个节点,是否满足选择器条件,
  // nes在match里封装了所有的递归操作,你无需考虑复杂的选择器匹配
  // 但是你仍然要告诉nes,你要找的是哪个元素,比如~要做的是: 1)找到前面中的节点 2)
  // 这个节点满足剩余的选择器,你在扩展里需要描述清楚的就是这个匹配的节点
  "&":function(node,match){
    while(node = node.nextSibling){
      if(node.nodeType ===1 && match(node)){
        return node  // 如果节点是元素节点,并且满足match匹配规则
      }
    }
    return null //如果没有则返回null,此轮匹配结束
  },
// 与 + 相反
  "%":function(node,match){
    while(node = node.nextSibling){
      if(node.nodeType ===1) return match(node)? node :null
    }
  }
})

注意: 如果找到了你要求的位置的节点,并且它满足传入match函数的测试,返回它,否则返回null(或直接不return)

2.3.2 规则扩展API

nes.addRule(String name, Object def)——创建一个全新Simple Selector语法(与属性、id、class、pesudo等是等价的)

__API__介绍:

  • name: 规则名(如classList)
  • def 包含三个部分 :
    1. (String || RegExp) reg : 你规则的正则定义 (必须输入)
    2. Void Function action: (可忽略)
      与parse相关的函数,参数即你正则里匹配到的子匹配,你要做的是把所有的匹配塞到应该去的地方
    3. Bool Function filter: (可忽略)
      与find相关的函数,针对单节点的过滤函数,返回Boolean

场景描述: 你需要获取ul.test1中所有在同级节点中所处位置大于1,但是小于9的li标签

原生方法: ul.test1 > li:nth-child(n+1):not(:nth-last-child(n+10))

理想做法: ul.test1 > li{1,9} ===> 此语法没有定义会报解析错误

你需要做的扩展是:

// 在进行语法扩展时reg是必须的,
// action与filter至少需要需要二选其一(根据场景不同)才能完成一个自定义规则的实施, 否则虽然不会报错
// 但是只是匹配了,让解析器不报错,但是这个选择器什么都不会做
nes.addRule("range",{
  reg:/\s*\{\s*(\d*),(\-?\d*)\s*\}\s*/, 
  // action中需要注意两个部分一个是this.error()打印出解析错误信息,
  // 一个this.current()永远返回当前的Simple Selector对应的data部分这是个hash表,
  // 你往里放key value对就行
  action:function(all, a, b){ //注意这里的a,b是郑则匹配到的子匹配,all是完整匹配
    var current = this.current(), //1. this.current返回当前匹配的simple selector
      pesudos = current.pesudos || (current.pesudos = []) 
    if(!a && !b) this.error("range中的参数不能同时为空") //2. this.error
    a = a && parseInt(a) || 1
    b = b && parseInt(b) || 0
    pesudos.push({  //a 如果不存在 视为
      name:"nth-child",
      param:{start:a, step:1 }
    })
    if(b>0){
      pesudos.push({ //意思小于b
        name:"not",
        param:":nth-child(" + (b+1) + ")"
      })
    }else{
      pesudos.push({  
        name:"nth-last-child",
        param:{start:b?-b:1, step:1 }
      })
    }
  }
})

注意:扩展的语法的正则式要描述清楚,否则可能会覆盖内建的规则。

2.4 包装器

提供几个常用的便利接口,: 警告: 可能会立刻被移除, 因为这不是一个纯洁的选择器该做的事

API:

nes(sl).one(sl2) == nes.all(sl2, nes.all(sl))

nes(sl).all(sl2) == nes.all(sl2, nes.all(sl))

nes(sl).parent(sl2) 返回最近的满足sl2的父节点

nes(sl).filter(sl2) 返回符合sl2的节点集

nes(sl).next(sl2) 返回下一个符合sl2的兄弟节点

nes(sl).prev(sl2) 返回上一个符合sl2的兄弟节点

扩展包装器: nes.fn.xxx= fn 类似于jQuery,如果觉得这个有用,可以给我提意见,因为选择器跟jQuery这种包装对象不同,它必须返回节点集,而jQuery的包装对象本身就可以操作。

2.5 几个可能会用到的配置属性

  1. nes.parseCache.length: 控制最大parse缓存,默认200
  2. nes.nthCache.length : 控制最大nth伪类参数的parse缓存,默认100

3 另外的乱七八糟的东西

  1. 求使用啊、求BUG啊、求Fork啊、求Push啊...
  2. docs里有注释的源码(未完成。本周抽空写完)
  3. API对commonjs、amd、全局暴露的支持我已经都做了,你可以直接用。同时网易的童鞋,NEJ也是支持的......