nes.js | |
---|---|
!
function(win, doc) {
| |
nes命名空间 |
var nes = {version: "0.0.5"},
locals = {} //存放local属性...被global坑死了
|
常用属性local化 |
var ap = Array.prototype,
op = Object.prototype,
sp = String.prototype,
fp = Function.prototype,
slice = ap.slice,
body = doc.body,
testNode = doc.createElement("div"),
|
1. Helper(助手函数)将类数组(如Nodelist、Argument)变为数组 |
toArray = function(arr) {
return slice.call(arr)
},
|
够用的短小类型判断 |
typeOf = function(o) {
return o == null ? String(o) : op.toString.call(o).slice(8, -1).toLowerCase()
},
|
够用的简单对象扩展 |
extend = function(o1, o2, override) {
for (var i in o2) {
if (o1[i] == null || override) o1[i] = o2[i]
}
},
|
最简单先进先出缓存队列, max设置最大缓存长度, 为了不必要重复parse nes会多次用到这个方法创建cache |
createCache = function(max) {
var keys = [],
cache = {}
return {
set: function(key, value) {
if (keys.length > this.length) {
delete cache[keys.shift()]
}
cache[key] = value
keys.push(key)
return value
},
get: function(key) {
if (typeof key === "undefined") return cache
return cache[key]
},
length: max
}
},
|
让setter型函数fn支持object型的参数
即支持 |
autoSet = function(fn) {
return function(key, value) {
if (typeOf(key) == "object") {
for (var i in key) {
fn.call(this, i, key[i])
}
} else {
fn.call(this, key, value)
}
return this;
}
},
assert = function(fn) {
try {
return fn()
} catch (e) {
return false
} finally {
testNode = document.createElement("div");
}
}
|
Fixed: toArray 低于IE8的 Nodelist无法使用slice获得array |
try {
slice.call(doc.getElementsByTagName("body"))
} catch (e) {
toArray = function(arr) {
var result = [],
len = arr.length
for (var i = 0; i < len; i++) {
result.push(arr[i])
}
return result
}
}
|
扩展ES5 Native支持的函数,坑爹的远古浏览器 es5 trim |
var trimReg = /^\s+|\s+$/g
sp.trim = sp.trim || function() {
return this.replace(trimReg, "")
}
|
es5 bind |
fp.bind = fp.bind || function(context, args) {
args = slice.call(arguments, 1);
var fn = this;
return function() {
fn.apply(context, args.concat(slice.call(arguments)));
}
}
|
es5 Array indexOf |
ap.indexOf = ap.indexOf ||
function(a) {
for (var i = 0, len = this.length; i < len; i++) {
if (a === this[i]) return i
}
return -1
}
|
Parser 相关 |
var
|
抽离出字匹配数目 |
ignoredRef = /\(\?\!|\(\?\:/,
extractRefNum = function(regStr) {
var left = right = 0,
len = regStr.length,
ignored = regStr.split(ignoredRef).length - 1 //忽略非捕获匹配
for (; len--;) {
var letter = regStr.charAt(len)
if (len == 0 || regStr.charAt(len - 1) !== "\\") { //不包括转义括号
if (letter === "(") left++
if (letter === ")") right++
}
}
if (left !== right) throw regStr + "中的括号不匹配"
else return left - ignored
},
|
前向引用 如\1 \12 等在TRUNK合并时要做处理 |
refIndexReg = /\\(\d+)/g,
extractRefIndex = function(regStr, curIndex) {
return regStr.replace(refIndexReg, function(a, b) {
return "\\" + (parseInt(b) + curIndex)
})
},
|
// 生成默认的action,这个会将匹配到的参数推入一个同名的数组内 createAction = function(name) { return function(all) { var parsed = this.parsed, current = parsed[name] || (parsed[name] = []) current.push(slice.call(arguments)) } }, Object.keys 规则排序时的调用方法 |
keys = Object.keys || function(obj) {
var result = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) result.push(prop);
}
return result
},
|
将规则中的reg中的macro替换掉 |
cleanRule = function(rule) {
var reg = rule.reg
|
如果已经是regexp了就转为string |
if (typeOf(reg) === "regexp") reg = reg.toString().slice(1, -1)
|
将macro替换 |
rule.regexp = reg.replace(replaceReg, function(a, b) {
if (b in macros) return macros[b]
else throw new Error('can"t replace undefined macros:' + b)
})
return rule
},
cleanRules = function(rules) {
for (var i in rules) {
if (rules.hasOwnProperty(i)) cleanRule(rules[i])
}
return rules
}
|
2. Parser为何要抽象成一个类? 其实这里只用到了一个实例 事实上这个简单Parser还帮我实现了命令行参数解析, zen-coding的实现等等, 它可以帮助实现基于正则式的 字符串解析 |
function Parser(opts) {
opts = opts || {}
if (opts.macros) this._macros = opts.macros
this._links = {} //symbol link map
this._rules = {} //symbol def
this.TRUNK = null
this.cache = createCache(opts.maxCache || 200)
this.cache.set("", [[]]) //输入空字符串返回空数组
}
extend(Parser.prototype, {
|
解析输入字符串input、返回action定义的data数据 |
parse: function(input) {
|
清理input数据、因为parsed数据最终会被缓存, 我们要尽量让相同的选择器只对应一份parsed数据 |
input = clean(input)
|
先检查缓存中是否有数据 |
if (parsed = this.cache.get(input)) return parsed
|
如果没有: 初始化参数 |
var parsed = this.parsed = [
[null]
],
remain = this.input = input,
TRUNK = this.TRUNK
|
将remain进行this._process这里每匹配一个字符串都会进行一次reduce |
while (remain != (remain = remain.replace(TRUNK, this._process.bind(this)))) {}
|
如果没有被解析完 证明选择器字符串有不能被解析的部分 |
if (remain !== '') this.error(remain)
|
返回数据并推入cache |
return this.cache.set(input, parsed)
},
|
添加新规则 :在nes中你可以想象成添加一个与Id、className、pesudo等价简单选择符 |
on: function(rules) {
if (typeOf(rules) === "string") { //当不是hash传入时
var tmp = {}
tmp[rules] = arguments[1]
rules = tmp
}
|
可以同时接受object型或者key, value对的参数 |
for (var i in rules) {
var rule = rules[i]
if (typeOf(rule) !== "object") {
rule = {
regexp: rule
}
}
var reg = rule.regexp
if (typeOf(reg) === "regexp") {
rule.regexp = reg.toString().slice(1, -1)
}
|
初始化order |
if (rule.order === undefined) rule.order = 1
this._rules[i] = rule
}
|
每进行一次新规则监听,都重新组装一次 |
this.setup()
return this
},
|
删除规则 :删除对应规则, 即nes的规则都是在运行时可删可增的 |
off: function(name) {
if (typeOf(name) === "array") {
for (var i = name.length; i--;) {
this.off(name[i])
}
} else {
if (this._rules[name]) {
delete this._rules[name]
}
}
return this
},
|
获得当前解析位置所在的datum,你只需要在这个datum中塞数据即可 |
current: function() {
var data = this.parsed
var piece = data[data.length - 1],
len = piece.length
return piece[len - 1] || (piece[len - 1] = {
tag: "*"
})
},
error: function(info) {
throw Error("输入 " + this.input + " 含有未识别语句:" + info || "")
},
clone: function(parser) {
return new Parser().on(this._rules)
},
|
|
_process: function() {
var links = this._links,
rules = this._rules,
args = slice.call(arguments)
for (var i in links) {
var link = links[i],
retain = link[1],
index = parseInt(link[0])
if (args[index] && (rule = rules[i])) {
rule.action.apply(this, args.slice(index, index + retain))
return ""
}
}
return ""
},
|
组装 |
setup: function() {
cleanRules(this._rules)
var curIndex = 1,
|
当前下标 |
splits = [],
rules = this._rules,
links = this._links,
ruleNames = keys(rules).sort(function(a, b) {
return rules[a].order >= rules[b].order
}),
len = ruleNames.length;
for (; len--;) {
var i = ruleNames[len],
rule = rules[i],
retain = extractRefNum(rule.regexp) + 1 // 1就是那个all
if(rule.filter && !filters[i]){
filters[i] = rule.filter
console.log(i)
} //将filter转移到filters下
links[i] = [curIndex, retain] //分别是rule名,参数数量
var regexp = extractRefIndex(rule.regexp, curIndex - 1);
curIndex += retain;
splits.push(regexp)
}
this.TRUNK = new RegExp("^(?:(" + splits.join(")|(") + "))")
return this
}
})
|
parse的规则定义部分开始一些用到的正则式,但是又不属于parser的规则组成 |
var
replaceReg = /\{\{([^\}]*)\}\}/g,
|
替换rule中的macro |
nthValueReg = /^(?:(\d+)|([+-]?\d*)?n([+-]\d+)?)$/,
|
nth伪类的value规则 |
posPesudoReg = /^(nth)[\w-]*(-of-type|-child)/,
|
判断需要pos 第一个cache 用来装载nth伪类中的参数解析后的数据 如nth-child、nth-of-type等8个伪类 |
nthCache = createCache(100),
|
提取nthValue中的有用信息 比如an + b 我们需要提取出a以及,b并对额外情况如缺少a参数或b参数 或者有a、b小于0这些情况作统一处理,返回find适合使用的数据 |
extractNthValue = function(param) {
var step, start, res
|
如果无参数 当成是获取第一个元素 |
if (!param || !(param = param.replace(/\s+/g, ""))) {
return {
start: 1,
step: 0
}
}
if (res = nthCache.get(param)) return res
|
对even odd等转化为标准的a,b组合(即step与start) |
if (param == "even") {
start = 2
step = 2
} else if (param == "odd") {
step = 2
start = 1
} else {
res = param.match(nthValueReg)
|
对错误的nth参数抛出错误 |
if (!res) step = null // 无论其他参数,如果step为null,则永远为false
else {
if (res[1]) {
step = 0
start = parseInt(res[1])
} else {
if (res[2] == "-") res[2] = "-1"
step = res[2] ? parseInt(res[2]) : 1
start = res[3] ? parseInt(res[3]) : 0
}
}
}
if (start < 1) {
if (step < 1) {
step = null //标志false
} else {
start = -(-start) % step + step
}
}
return nthCache.set(param, {
start: start,
step: step
})
}
|
parse Rule 相关了解bison等解析器生成的同学可以把这部分看成是词法与语法定义的杂糅 很混乱也很不标准,但对于选择器这种最简单的DSL其实够用,并且有了奇效 整个Parser根据rules动态产生(即可在使用时发生改变) 具体的流程是下面的rules对象定义了一组语法(词法?)rule——如attribute, 你可以把每个rule中的reg想象成一个token(word?),这些token可能会有{{word}}这种占位符 占位符首先会被macros中对应的macro替换,然后这些token会被组装成一个大版的Regexp,即上面的 Trunk变量,这个过程没什么特殊,一般比较优秀的选择器都是基于这个方法。 在nes中,最终的Trunk可能是 这样的:
看到上面那长串,你大概理解了将regexp按词法分开这样做的第一个原因 : 维护. 第二个原因就是: 效率 一次大型正则的调用时间要远低于多次小型正则的匹配(前提它们做同样规模的匹配) |
var
|
一些macro |
macros = {
split: "\\s*,\\s*",
|
分隔符 |
operator: "[*^$|~!]?=",
|
属性操作符 如= 、!= |
combo: "[>\\s+~](?!=)",
|
连接符 如 > ~ 中文unicode范围http://baike.baidu.com/view/40801.htm#sub40801_3 |
word: "[\\\\\\w\\u4e00-\\u9fbf-]"
},
|
语法规则定义 |
rules = {
split: {
|
分隔符 如 , |
reg: "{{split}}",
action: function(all) {
this.parsed.push([null])
},
order: 0
},
|
id 如 #home |
id: {
reg: "#({{word}}+)",
action: function(all, id) {
this.current().id = id
}
},
|
节点类型选择符 如 div |
tag: {
reg: "\\*|[a-zA-Z-]\\w*",
|
单纯的添加到 |
action: function(tag) {
this.current().tag = tag.toLowerCase()
}
},
|
类选择符 如 .m-home |
classList: {
reg: "\\.({{word}}+)",
action: function(all, className) {
var current = this.current(),
classList = current.classList || (current.classList = [])
classList.push(className)
}
},
|
伪类选择符 如 :nth-child(3n+4) |
pesudos: {
reg: ":({{word}}+)" + //伪类名
"(?:\\(" + //括号开始
"([^\\(\\)]*" + //第一种无括号
"|(?:" + //有括号(即伪类中仍有伪类并且是带括号的)
"\\([^\\)]+\\)" + //括号部分
"|[^\\(\\)]*" + ")+)" + //关闭有括号
"\\))?",
|
关闭最外圈括号 |
action: function(all, name, param) {
var current = this.current(),
pesudos = current.pesudos || (current.pesudos = [])
name = name.toLowerCase()
if (param) param = param.trim()
if (posPesudoReg.test(name)) {
|
parse 的成本是很小的 尽量在find前把信息准备好 这里我们会把nth-child(an+b) 的 a 与 b 在不同输入下标准化 |
param = extractNthValue(param)
}
pesudos.push({
name: name,
param: param
})
}
},
|
属性选择符 如 [class=hahaha] 这里以属性选择符为例,说明下reg与action的关系 |
attributes: {
reg: "\\[\\s*({{word}}+)(?:({{operator}})[\'\"]?([^\'\"\\[]+)[\'\"]?)?\\s*\\]",
action: function(all, key, operator, value) {
var current = this.current(),
attributes = current.attributes || (current.attributes = [])
attributes.push({
key: key,
operator: operator,
value: value
})
}
},
|
伪元素可以实现么? 占位 |
combo: {
reg: "\\s*({{combo}})\\s*",
action: function(all, combo) {
var data = this.parsed,
cur = data[data.length - 1]
this.current().combo = combo
cur.push(null)
},
order: 0
}
}
var cleanReg = new RegExp("\\s*(,|" + macros.combo + "|" + macros.operator + ")\\s*", "g")
clean = function(sl) {
return sl.trim().replace(/\s+/g, " ").replace(cleanReg, "$1")
}
|
parser生成初始化parser实例 |
var parser = new Parser()
parser.on(rules) //生成规则
|
为了兼容前面版本,仍然提供这个parse函数, 也为了与find想对应 |
function parse(sl) {
return parser.parse(sl)
}
|
3. FinderUtil将nodelist转变为array DOM related Util |
var root = doc.documentElement || doc;
var attrMap = {
'for': "htmlFor",
"href": function(node) {
return "href" in node ? node.getAttribute("href", 2) : node.getAttribute("href")
},
"type": function(node){
return "type" in node ? node.getAttribute("type"):node.type
}
};
var checkTagName = assert(function() {
testNode.appendChild(doc.createComment(""));
|
有些版本的getElementsByTagName("*")会返回注释节点 |
return !testNode.getElementsByTagName("*").length ||
|
低版本ie下input name 或者 id为length时 length返回异常 |
typeof doc.getElementsByTagName("input").length !== "number"
});
|
form sizzleline200 判断getAttribute是否可信 |
var checkAttributes = assert(function() {
testNode.innerHTML = "<select></select>";
var type = typeOf(testNode.lastChild.getAttribute("multiple"))
|
IE8 returns a string for some attributes even when not present |
return type !== "boolean" && type !== "string";
});
|
将nth类方法的判断部分统一抽离出来, 其中type代表是否是nth-type-of类型的判断,下面类似 |
function checkNth(node, type) {
if (type) return node.nodeName === type
else return node.nodeType === 1
}
|
获得父节点node的顺数第n(从1开始)个子节点 |
function nthChild(node, n, type) {
var node = node.firstChild
if (!node) return
if (checkNth(node, type)) n--
return nthNext(node, n, type)
};
|
获得父节点node的倒数第n(从1开始)个子节点 |
function nthLastChild(node, n, type) {
var node = node.lastChild
if (!node) return
if (checkNth(node, type)) n--
return nthPrev(node, n, type)
};
|
获得节点node的第n(从1开始)个前置兄弟节点 |
function nthPrev(node, n, type) {
while (n && (node = node.previousSibling)) {
if (checkNth(node, type)) n--
}
return node
};
|
获得节点node的倒数第n(从1开始)个前置兄弟节点 |
function nthNext(node, n, type) {
while (n && (node = node.nextSibling)) {
if (checkNth(node, type)) n--
}
return node
};
|
获得节点node的key属性的值, 修改自from sizzle...蛋疼啊各浏览器的属性获取 |
function getAttribute(node, key) {
var map = attrMap[key]
if (map) return typeof map === "function" ? map(node) : node[map]
if(checkAttributes){return node.getAttribute(key);}
var attrNode = node.getAttributeNode(key);
|
对于selected checked 当返回为bool值时 将其标准化为 selected = "selected" 方便后续处理 |
return typeof node[key] === "boolean" ?
|
很多时候null可以作为标志位,nes中大部分特殊flag都用null标示 |
node[key] ? key : null :
attrNode && attrNode.specified && attrNode.value || null
};
|
数组去重 |
function distinct(array) {
for (var i = array.length; i--;) {
var n = array[i]
|
先排除 即 如果它是清白的 后面就没有等值元素 |
array.splice(i, 1, null)
if (~array.indexOf(n)) {
array.splice(i, 1); //不清白
} else {
array.splice(i, 1, n); //不清白
}
}
return array
}
|
从sizzle抄袭的 document sorter 将匹配元素集按文档顺序排列好 这很重要! |
var sortor = (doc.compareDocumentPosition) ?
function(a, b) {
if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
} : ('sourceIndex' in doc) ?
function(a, b) {
if (!a.sourceIndex || !b.sourceIndex) return 0;
return a.sourceIndex - b.sourceIndex;
} : function(a, b) {
var i = 0,
ap = [a],
bp = [b],
aup = a.parentNode,
bup = b.parentNode,
cur = aup;
if (a === doc) {
return -1;
} else if (b === doc) {
return 1;
} else if (!aup && !bup) {
return 0;
} else if (!bup) {
return -1;
} else if (!aup) {
return 1;
} else if (aup === bup) {
return siblingCheck(a, b);
}
while (cur) {
ap.unshift(cur);
cur = cur.parentNode;
}
cur = bup;
while (cur) {
bp.unshift(cur);
cur = cur.parentNode;
}
while (ap[i] === bp[i]) {
i++;
}
return siblingCheck(ap[i], bp[i]);
};
function siblingCheck(a, b) {
if (a && b) {
var cur = a.nextSibling;
while (cur) {
if (cur === b) {
return -1;
}
cur = cur.nextSibling;
}
}
return a ? 1 : -1;
};
|
nth position Cache 部分对于nth类型的查找,有时候一次节点查找会遍历到多次相同节点, 由于一次节点查找时,由于js的单线程,节点不可能发生改变,介于此,我们将 nth类的node的节点位置缓存起来,在本次查找结束后再清空 |
|
获得node的唯一标示 |
var getUid = (function(token) {
var _uid = 0
return function(node) {
return node._uid || (node._uid = token + _uid++)
}
})("nes_" + (+new Date).toString(36));
|
创建nth相关的Filter,由于都类似,就统一通过工厂函数生成了
参数有两个 |
function createNthFilter(isNext, isType) {
var next, prev, cacheKey, getStart
if (isNext) {
cacheKey =isType ? "type" : "child"
next = nthNext
prev = nthPrev
getStart = nthChild
} else {
|
Fixed:!!! 这里cache是首次生成的cache被写死了,即使后面clear了也没有用, 即永远无法被释放 |
cacheKey ="last" + (isType ? "type" : "child")
prev = nthNext
next = nthPrev
getStart = nthLastChild
}
|
实际返回函数, param即pesudo的action定义的参数形如
|
return function(node, param) {
var cache = nthPositionCache[cacheKey]
if (node === root) return false // 如果是html直接返回false 坑爹啊
var _uid = getUid(node),
parent = node.parentNode,
traverse = param.step > 0 ? next : prev,
step = param.step,
start = param.start,
type = isType && node.nodeName
|
Fixed |
if (step === null) return false //means always false
if (!cache[_uid]) {
var startNode = getStart(parent, 1, type),
index = 0
do {
cache[getUid(startNode)] = ++index
nthPositionCache.length++
} while (startNode = next(startNode, 1, type))
}
var position = cache[_uid]
if (step === 0) return position === start
if ((position - start) / step >= 0 && (position - start) % step == 0) {
return true
}
}
}
var nthPositionCache = {
length: 1
}
function clearNthPositionCache() {
if(nthPositionCache.length){
nthPositionCache = {
child:{},
lastchild:{},
type:{},
lasttype:{},
length:0
}
}
}
|
初始化positioncache |
clearNthPositionCache()
|
这里的几个finders是第一轮获取目标节点集的依赖方法 我没有对byClassName做细致优化,比如用XPath的方式 form sizzle line 147 |
var finders = {
byId: function(id) {
var node = doc.getElementById(id)
return node ? [node] : []
},
byClassName: doc.getElementsByClassName ?
function(classList, node) {
classList = classList.join(" ")
return toArray((node || doc).getElementsByClassName(classList))
} : null,
byTagName: checkTagName ?
function(tagName, node) {
var results = (node || doc).getElementsByTagName(tagName)
return toArray(results)
} : function(tagName, node) {
var results = (node || doc).getElementsByTagName(tagName)
var elem, tmp = [],
i = 0;
for (;(elem = results[i]); i++) {
if (elem.nodeType === 1) tmp.push(elem);
}
return tmp;
}
}
|
filter:Action中塞入的数据会统一先这里处理,可能是直接处理如id、class等简单的. 也可能是分发处理,甚至是多重的分发,如那些复杂的attribute或者是pesudo 这里简化到过滤单个节点 逻辑清晰 ,但可能性能会降低,因为有些属性会重复获取 |
var filters = {
id: function(node, id) {
return node.id === id
},
classList: function(node, classList) {
var len = classList.length,
className = " " + node.className + " "
for (; len--;) {
if (className.indexOf(" " + classList[len] + " ") === -1) {
return false
}
}
return true
},
tag: function(node, tag) {
if (tag == "*") return true
return node.tagName.toLowerCase() === tag
},
|
pesudos会分发到ExpandsFilter中pesudo中去处理 |
pesudos: function(node, pesudos) {
var len = pesudos.length,
pesudoFilters = expandFilters["pesudos"]
for (; len--;) {
var pesudo = pesudos[len],
name = pesudo.name,
filter = pesudoFilters[name]
if (!filter) throw Error("不支持的伪类:" + name)
if (!filter(node, pesudo.param)) return false
}
return true
},
|
attributes会分发到ExpandsFilter中的operator去处理 |
attributes: function(node, attributes) {
var len = attributes.length,
operatorFilters = expandFilters["operators"]
for (; len--;) {
var attribute = attributes[len],
operator = attribute["operator"],
filter = operatorFilters[operator],
nodeValue = getAttribute(node, attribute.key)
if(nodeValue === null){
if(operator !== "!=" ) return false
continue
}
if(!operator) continue
if (!filter) throw Error("不支持的操作符:" + operator)
if (!filter(attribute.value, nodeValue+"")) return false
}
return true
}
}
|
expandFilters原生可扩展的方法 |
var expandFilters = {
|
扩展连接符: 选择符filter 与其他filter不同 node 同样是当前节点 区别是 如果成功返回成功的上游节点(可能是父节点 可能是兄弟节点等等) 其中 match(node) 返回 这个上游节点是否匹配剩余选择符(内部仍是一个递归) |
combos: {
">": function(node, match) {
var parent = node.parentNode
if (match(parent)) return parent
},
"~": function(node, match) {
var prev = nthPrev(node, 1)
while (prev) {
if (match(prev)) return prev
prev = nthPrev(prev, 1)
}
},
" ": function(node, match) {
var parent = node.parentNode
while (parent) {
var pass = match(parent)
if (pass) return parent
if (pass === null) return null
parent = parent.parentNode
}
return null
},
"+": function(node, match) {
var prev = nthPrev(node, 1)
if (prev && match(prev)) return prev
}
},
|
扩展操作符 : |
operators: {
"^=": function(value, nodeValue) {
if (nodeValue == null) return false
return nodeValue.indexOf(value) === 0
},
"=": function(value, nodeValue) {
return nodeValue === value
},
"~=": function(value, nodeValue) {
if (nodeValue == null) return false
return ~(" " + nodeValue + " ").indexOf(value)
},
"$=": function(value, nodeValue) { //以value结尾
return nodeValue.substr(nodeValue.length - value.length) === value
},
"|=": function(value, nodeValue) { // 连接符
return ~("-" + nodeValue + "-").indexOf("-" + value + "-")
},
"*=": function(value, nodeValue) { //出现在nodeValue的任意位置
return ~(nodeValue).indexOf(value)
},
"!=": function(value, nodeValue) {
return nodeValue !== value
}
},
|
扩展伪类: |
pesudos: {
|
TODO:这里如果出自 SELECtorAPI 标注下处处 |
"not": function(node, sl) {
return !matches(node, sl)
},
"matches": function(node, sl) {
return matches(node, sl)
},
|
child pesudo 统一由工厂函数生成 |
"nth-child": createNthFilter(1, 0),
"nth-last-child": createNthFilter(0, 0),
"nth-of-type": createNthFilter(1, 1),
"nth-last-of-type": createNthFilter(0, 1),
"nth-match": function(node, param) {
var tmp = param.split(/\s+of\s+/),
nth = parseInt(tmp[0]),
sl = tmp[1],
start = node.parentNode.firstChild
do {
if (start.nodeType === 1 && nes.matches(start, sl)) nth--
} while (nth && (start = start.nextSibling))
return !nth && node === start
},
"first-child": function(node) {
return !nthPrev(node, 1)
},
"last-child": function(node) {
return !nthNext(node, 1)
},
"last-of-type": function(node) {
return !nthNext(node, 1, node.nodeName)
},
"first-of-type": function(node) {
return !nthPrev(node, 1, node.nodeName)
},
"only-child": function(node) {
return !nthPrev(node, 1) && !nthNext(node, 1)
},
"only-of-type": function(node) {
return !nthPrev(node, 1, node.nodeName) && !nthNext(node, 1, node.nodeName)
},
|
找出有具体text内容的节点 |
"contains": function(node, param) {
return ~ (node.innerText || node.textContent || '').indexOf(param)
},
"checked": function(node) {
return !!node.checked || !! node.selected
},
"selected": function(node) {
return node.selected
},
"enabled": function(node) {
return node.disabled === false
},
"disabled": function(node) {
return node.disabled === true
},
"empty": function(node) {
var nodeType;
node = node.firstChild;
while (node) {
if (node.nodeName > "@" || (nodeType = node.nodeType) === 3 || nodeType === 4) {
return false;
}
node = node.nextSibling;
}
return true;
},
"focus": function(node) {
return node === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !! (node.type || node.href || ~node.tabIndex);
},
"target": function(node, param) {
var id = node.id || node.name
if (!id) return false
return ("#" + id) === location.hash
}
}
}
|
这里主要是整合之前的ExpandsFilter中的mathch, 单层数据 |
function matchDatum(node, datum, ignored) {
var subFilter
for (var i in datum) {
if (ignored !== i && (subFilter = filters[i]) && !subFilter(node, datum[i])) {
return false
}
}
return true
};
|
这个全局cache的引入是为了避免多次传入参数。 当然全局的缺点也很明显,维护会不方便, 不利于测试 |
var matchesCache = null; //保存那些matches函数
function matchData(node, data, ignored) { // 稍后再看存入step
var len = data.length,
datum = data[len - 1]
|
首先要满足自身 |
if (!matchDatum(node, datum, ignored)) return false
else {
if (len == 1) return true
var nextDatum = data[len - 2],
getNext = expandFilters.combos[nextDatum.combo],
match = matchesCache[len - 2],
next = getNext(node, match)
if (next) return true
else return false
}
};
|
动态产生供FilterOneNode使用的match |
function createMatch(data) {
return function(node) {
if (node == root || node == null || node == doc) return null //null 相当于休止符
return matchData(node, data)
}
};
function createMatches(data) {
var matches = []
for (var i = 0, len = data.length; i < len; i++) {
matches.push(createMatch(data.slice(0, i + 1)))
}
return matches
};
|
过滤主函数filter自底向上过滤非匹配节点 |
function filter(results, data, ignored) {
if (!data.length) return results;
|
这里是为了缓存match匹配函数 |
var preMatchesCache = matchesCache
matchesCache = createMatches(data)
for (var i = results.length; i--;) {
if (!matchData(results[i], data, ignored)) {
results.splice(i, 1)
}
}
|
Fixed: 因为一次filter可能会有字filter调用,比如matches、not、include |
matchesCache = preMatchesCache; // warning :以后写全局变量一定当心
return results;
}
|
获得第一次目标节点集 |
function getTargets(data, context) {
var results, ignored, lastPiece = data[data.length - 1]
if (lastPiece.id) {
results = finders.byId(lastPiece.id);
ignored = "id";
} else if (lastPiece.classList && lastPiece.classList.length && finders.byClassName) {
results = finders.byClassName(lastPiece.classList, context);
ignored = "classList";
} else {
results = finders.byTagName(lastPiece.tag || "*", context);
ignored = "tag";
}
if (!results.length) return results;
return filter(results, data, ignored);
}
|
API : find (private)根据parse后的数据进行节点查找
options:
1. parsed.data parse数据为一数组
2. node context节点
事实上没有data这个单词,我这里算是自定了这个单词
datas : [data,data] |
function find(datas, context) {
if (!datas[0][0]) return []
var results = []
for (var i = 0, len = datas.length; i < len; i++) {
var data = datas[i],
dlen = data.length,
last = data[dlen - 1]
results = results.concat(getTargets(data, context))
}
clearNthPositionCache()
if (!results.length) return results
if (len > 1){
distinct(results)
results.sort(sortor)
}
return results
}
|
API : 测试用get相当于all (private)为了测试时避免原生querySelector的影响 |
function get(sl, context) {
var data = parse(sl);
var result = find(data, context || doc);
return result;
}
|
API |
var supportQuerySelector = !! doc.querySelector
|
API1——one:对应标准的querySelector方法 |
function one(sl, context) {
var node
if (supportQuerySelector && !nes.debug) {
try {
node = (context || doc).querySelector(sl);
} catch (e) {
node = get(sl, context)[0];
}
} else {
node = get(sl, context)[0];
}
return node
}
|
API2——all对应标准的querySelectorAll方法 |
function all(sl, context) {
var nodeList
if (supportQuerySelector && !nes.debug) {
try {
nodeList = (context || doc).querySelectorAll(sl);
} catch (e) {
nodeList = get(sl, context);
}
} else {
nodeList = get(sl, context);
}
return nodeList
}
|
API 3:对应标准的matches方法 nes的matches功能稍强,它支持用分隔符链接符组合的复杂选择器 即与all、one函数的支持是一样的 注: 由于:not与:matches依赖于这个函数 ,所以同样支持复杂选择器 |
function matches(node, sl) {
var datas = parse(sl),
len = datas.length
if (!datas[len - 1][0]) return false
for (; len--;) {
if (matchOneData(node, datas[len])) return true
}
return false
}
|
matches 单步调用方法 |
function matchOneData(node, data) {
var len = data.length
if (!matchDatum(node, data[len - 1])) {
return false
} else {
return filter([node], data.slice(0)).length === 1
}
}
|
ASSEMBLE组装分为几个步骤 1. 生成pesudo 、 operator、combo 等expand方法具体扩展方法的使用请参见 nes的github |
|
统一由工厂函数createExpand |
;(function createExpand(host, beforeAssign) {
for (var i in host) {
nes[i] = (function(i) {
var container = host[i];
|
autoSet代表了这三个函数除了key、value参数 也支持字面量的参数输入 |
return autoSet(function(key, value) {
|
Warning: 直接覆盖,如果存在的话 |
container[key] = value.bind(container);
if (i in beforeAssign) {
beforeAssign[i](key, value);
}
})
})(i)
}
|
有些扩展如combos、operators由于为了避免冲突 关键字都写入了正则式中,这种情况下需要将新的正则式 填入相关正则,并进行parser的setup |
})(expandFilters, {
"operators": function(key, value) {
var befores = macros.operator.split("]");
befores.splice(1, 0, key.charAt(0) + "]");
macros.operator = befores.join("");
parser.setup();
},
"combos": function(key, value) {
var befores = macros.combo.split("]");
befores.splice(1, 0, key + "]");
macros.combo = befores.join("");
parser.setup();
}
})
|
2. 暴露API |
extend(nes, {
|
直接设置其为true 来强制不适用原生querySelector Api |
debug:false,
|
parser , 抽离的parser部分它可以: 1. parser.parse 解析内部规则定义的字符串匹配 2. parser.on 添加新规则 3. parser.clone 复制此parser,这个作用会在后面的zen-coding的demo中体现 4. parser.off 删除规则 5. parser.cache 缓存控制 |
parser:parser,
|
解析, 这个将被移除,使用parser.parse来代替 |
parse: parse,
|
查找parser解析后的data代表的节点 private |
_find: find,
|
测试时排除原生querySelector的影响 deprecated! 使用nes.debug来控制 |
_get: get,
|
主要API |
one: one,
all: all,
matches: matches
|
内建扩展 api 这三个已经内建:
|
})
|
5.Exports暴露API: amd || commonjs || global 支持commonjs |
if (typeof exports === 'object') {
module.exports = nes;
|
支持amd |
} else if (typeof define === 'function' && define.amd) {
define(function() {return nes });
} else {
|
直接暴露到全局 |
win.nes = nes;
}
}(window, document)
|