在(2)中描述了选择器的基本思路和求值步骤,最关键的步骤在于对子表达式的求值,也就是实现XX.selector函数,下面说说思路:
子表达式就是不带','的CSS合法字符串,形式为"expr1 binOp expr2 binOp expr3",其中binOp是二元运算符,expr是由一元表达式组成的字符串,如:"div > ul li.class",XX.selector的目标就是求出符合子表达式的元素集合。
我们可以将子表达式看成由非二元表达式组通过二元运算符连接起来的表达式,如 "div:last-child > ul li.class"可以分成3个非二元表达式组:"div:last-child","ul","li.class",
先求出第一组非二元表达式的值,接着根据二元运算符和后面的非二元组进行过滤,最后的结果就是符合选择器的元素集合。主要步骤如下:
1、 输入子表达式字符串,如"div:last-child","ul","li.class",
2、 对字符串预处理,除去两端空格,除去二元运算符两边多余的空格,
3、 获得第一项非二元组表达式,上述为:"div:last-child"
4、 将非二元组分解为表达式,上述为"div",":last-child"
5、 求出符合非二元组的元素集合ret
6、将剩余的字符串构造为二元组集合binExpr,即为二元运算符加非二元表达式组,也就是符合语法规则 binExpr = expr1 + op + expr2,因为expr1前面我们已经求过值了,所以现在就是根据op和expr2来过滤掉不符合条件的元素,而expr2可以重复利用第4步骤进行分解存储。
6、遍历二元组集合binExpr,根据运算符和expr2筛选符合条件的元素
7、返回ret
对于第2步骤,定义了一组trim函数来实现,
对第3步骤,我定义了一个XX.getExprGroup函数来实现,
对于第4步骤则定义XX.getUnaryExprObj函数来实现该功能。
根据上述步骤,我们可以构件一个基类Expr,带有最基本的filter和toString方法,toString主要用来调试用,filter用来过滤元素,接着继承该基类的有,一元表达式对象UnaryExpr,属性表达式对象AttExpr,伪类表达式对象PseExpr,二元表达式对象BinExpr, 其中除去BinExpr,其他的Expr都带有一个calculate方法,该方法用于计算符合表达式的元素集合,具体如下图所示:
最基本的Expr包含一个op属性,用来保存相关的运算符,其中unaryExpr还有一个term属性,用来保存标识符,AttExpr和PseExpr本身的标识符就是运算符,所以不需要额外属性了,BinExpr则用expr属性保存其非二元表达式组中对应的表达式对象。
在实际的代码中,我并没有写出Expr的代码,也没体现出各个运算符的继承关系,只是在概念上是这么理解的,主要考虑到,这些类所拥有的属性和方法较少,另一方面也为了效率。
具体代码如下:
/*一元表达式构造函数*/ XX.unaryExpr = function(op, term){ this.op = op; this.term = term; } XX.unaryExpr.prototype.toString = function(){ return this.op + this.term; }; XX.unaryExpr.prototype.calculate = function(context){ //待实现 }; XX.unaryExpr.prototype.filter = function(ret){ //待实现 }; /*伪类表达式构造函数*/ XX.PseExpr = function(op){ this.op = op; }; XX.PseExpr.prototype.toString = function(){ return ':' + this.op; }; XX.PseExpr.prototype.filter = function(){ };
/*属性表达式构造函数*/ XX.AttExpr = function(op){ this.op = op; }; XX.AttExpr.prototype.toString = function(){ return '[' + this.op + ']'; }; XX.AttExpr.prototype.filter = function(){ }; /*二元表达式构造函数*/ XX.binExpr = function(op, exprstr){ this.op = op; this.expr = []; /*存储一元表达式对象*/ var start = exprstr.length - 1, obj = null; while(obj = XX.getExprObj(exprstr, start)){ this.expr.push(obj.expr); start = --obj.index; } }; XX.binExpr.prototype.toString = function(){ var op = this.op == ' '? '->' : this.op; return op + this.expr.toString(); }; XX.binExpr.prototype.filter = function(ret){ };
相关trim函数的实现/* *去除字符串左边空白 * */ XX.trimLeft = function(str) { return str.replace(/^\s+/, ''); }; /*去除字符串右边空白*/ XX.trimRight = function(str) { for(var i = str.length - 1; i > -1 && /\s/.test(str.charAt(i)); --i) ; //不做任何操作 if(i > -1) { return str.substr(0, i + 1); } return ''; }; /*去除字符串两端空白*/ XX.trim = function(str) { return XX.trimRight(XX.trimLeft(str)); };
XX.getExprGroup与XX.getUnaryExprObj函数的定义
/*顺序获得一组一元表达式字符串 *参数selector:输入的字符串表达式 * start: 开始位置 * 返回值:表达式组字符串 * */ XX.getExprGroup = function(selector, start){ };
/* * 获取一个表达式对象 * 参数selector:选择符字符串 * 参数curIndex:开始的搜索的索引位置 * 返回值:无表达式则为null,否则返回一个对象,{expr:表达式对象, index:运算符的位置} * */ XX.getExprObj = function(selector, curIndex) { //待实现 };
XX.getUnaryExprObj函数之所以返回一个包含运算符位置的index,主要是对字符串方便进行遍历扫描。
挤得差不多了,后面再慢慢实现各个表达式类的方法,以及二元表达式的构造函数存储一元表达式的部分。