select选择器引擎初步封装
/**
* Created by Jepson on 2016/11/20.
*/
var select = (function () {
// 基本函数, support 对象, 验证 qsa 与 byclass
var support = {};
var rnative = /\{\s*\[native/; // 浏览器能力检测,对方法的定义进行检测
support.qsa = rnative.test( document.querySelectorAll + '' );
support.getElementsByClassName = rnative.test( document.getElementsByClassName + '' );
// 基本方法
function getByClassName( className, node, i ) {
node = node || document;
var list, res = [], i;
if ( support.getElementsByClassName ) {
return node.getElementsByClassName( className );
} else {
list = document.getElementsByTagName( '*' );
for( i = 0; i < list.length; i++ ) {
if( ( " " + list[ i ].className + " " ).indexOf( " " + className + " " ) > -1 ) {
res.push( list[ i ] );
}
}
return res;
}
}
// 捕获 1 是 id, 2 是 类, 3 是 通配符 4 是 标签 选择器
var rbaseselector = /^(?:\#([\w\-]+)|\.([\w\-]+)|(\*)|(\w+))$/;
function select ( selector, results ) {
results = results || [];
var m, temp;
if ( typeof selector != 'string' ) return results;
// 判断是否支持 qsa 方法,支持直接用
if ( support.qsa ) {
results.push.apply( results, document.querySelectorAll( selector ) )
return results;
}
m = rbaseselector.exec( selector );
if ( m ) {
// 捕获到数据了
if ( m[ 1 ] &&
( temp = document.getElementById( m[ 1 ] ) ) ) {
results.push( temp );
} else if ( m[ 2 ] ) {
results.push.apply( results, getByClassName( m[ 2 ] ) );
} else if ( m[ 3 ] ) {
results.push.apply( results, document.getElementsByTagName( m[ 3 ] ) ); // selector
} else if ( m[ 4 ] ) {
results.push.apply( results, document.getElementsByTagName( m[ 4 ] ) ); // selector
}
// 注意: 3 和 4 是可以合并
}
return results;
}
return select;
})();
trim 的检测与自己实现
var rtrim = /^\s+|\s+$/g;
var support = {};
support.trim = rnative.test(String.prototype.trim + ''); // 方法能力检测,定义
// 自定义实现 trim 方法
var myTrim = function (str) {
// 表示两端去空格, 然后返回去除空格的结果
if (support.trim) {
return str.trim();
} else {
// 自定义实现
return str.replace(rtrim, '');
}
}
带逗号的选择器功能实现
var select = ( function() {
// 正则表达式
var rnative = /\{\s*\[native/; // 利用方法定义进行能力检测
var rtrim = /^\s+|s+$/g; // trim 方法没有,我们就自己搞
var rbaseselector = /^(?:\#([\w\-]+)|\.([\w\-]+)|(\*)|(\w+))$/;
var support = {};
support.qsa = rnative.test( document.querySelectorAll + '' );
support.getElementsByClassName = rnative.test( document.getElementsByClassName + '' );
support.trim = rnative.test( String.prototype.trim + '' );
// 基本方法
function getByClassName( className, node ) {
node = node || document;
var allElem, res = [], i;
if ( support.getElementsByClassName ) {
node = node || document;
var allElem, res = [], i;
if ( support.getElementsByClassName ) {
return node.getElementsByClassName( className );
} else {
allElem = node.getElementsByClassName( '*' );
for( i = 0; i < allElem.length; i++ ) {
if ( (" " + allElem[ i ].className + " ").indexOf( " " + className + " ") > -1 ) {
allElem.push( list[ i ] );
}
}
return res;
}
}
}
// 自定义实现 trim 方法
var myTrim = function( str ) {
// 两端去掉空格,然后返回去除空格的结果
if ( support.trim ) {
return str.trim();
} else {
return str.replace( rtrim, '' );
}
}
function select( selector, results ) {
results = results || [];
var m, temp, selectors, i, subselector;
if ( typeof selector != 'string' ) return results;
// 处理分割的问题
selectors = selector.splits( ',' );
for( i = 0; i < selectors.length; i++ ) {
// selector[ i ] 选择器
subselector = myTrim( selectors[ i ] );
// 判断
if ( support.qsa ) {
results.push.apply( results, document.querySelectorAll( subselector ) );
} else if ( m = rbaseselector.exec( subselector ) ) {
// 捕获到数据了
if ( m[ 1 ] && ( temp = document.getElementById( m[ 1 ] ) ) ) {
results.push( temp );
} else if ( m[ 2 ] ) {
results.push.apply( results, getByClassName( m[ 2 ] ) );
} else if ( m[ 3 ] ) {
results.push.apply( results, document.getElementsByTagName( m[ 3 ] ) ); // selector
} else if ( m[ 4 ] ) {
results.push.apply( results, document.getElementsByTagName( m[ 4 ] ) ); // selector
}
// 注意: 3 和 4 是可以合并
}
}
}
return select; // 返回函数引用
})();
元素去重
可以得到 select 返回的就是一个数组.* ‘.p, div’很可能匹配到相同元素标签,都在数组中,那就有重复,消耗了性能*。所以可以抽象一下. 我们就是要在数组中去除重复元素
var arr = [ 1, 2, 3, 1, 3, 2, 4 ]; // => [ 1, 2, 3, 4 ]
var tempArr = [];
循环将 arr 中的 元素 一个一个 的加到 tempArr 中, 并且需要判断
如果 tempArr 中不包含该元素才会加进去
最简单的方法
var arr = [ 1, 2, 3, 2, 4, 3, 1, 5 ];
var tempArr = [];
for( var i = 0; i < arr.length; i++ ) {
if ( tempArr.indexOf( arr[ i ] ) === -1 ) {
tempArr.push( arr[ i ] );
}
}
console.log( tempArr );
但是 indexOf 早期浏览器又不支持,我们要自己封装, 能力检测 - 方法定义检测
var rnative = /\{\s*\[native/;
var support = {};
support.indexOf = rnative.test(Array.prototype.indexOf + "");
console.log( support.indexOf );
兼容性 indexOf 封装
var rnative = /\{\s*\[native/;
var support = {};
support.indexOf = rnative.test(Array.prototype.indexOf + "");
var myIndexOf = function (array, search, startIndex) {
startIndex = startIndex || 0;
if (support.indexOf) {
return array.indexOf(search, startIndex);
} else {
for (var i = startIndex; i < array.length; i++) {
if (array[i] === search) {
return i;
}
}
return -1;
}
}
var arr = [ 1, 2, 3, 2, 4, 3, 1, 5 ];
var tempArr = [];
for ( var i = 0; i < arr.length; i++ ) {
if ( myIndexOf( tempArr, arr[ i ] ) == -1 ) {
tempArr.push( arr[ i ] );
}
}
console.log( tempArr ); // 12345
所以元素去重方法就有了
var rnative = /\{\s*\[native/;
var support = {};
support.indexOf = rnative.test(Array.prototype.indexOf + "");
var myIndexOf = function (array, search, startIndex) {
startIndex = startIndex || 0;
if (support.indexOf) {
return array.indexOf(search, startIndex);
} else {
for (var i = startIndex; i < array.length; i++) {
if (array[i] === search) {
return i;
}
}
return -1;
}
}
// 兼容性元素去重方法
var unique = function ( array ) {
var resArray = [], i = 0;
for (; i < array.length; i++) {
if (myIndexOf(resArray, array[i]) == -1) {
resArray.push(array[i]);
}
}
return resArray;
}
添加后代元素元素选择器的封装
jquery的后代元素选择器
<body>
<div>div1
<div>div2
<div>div3
<div>div4</div>
</div>
</div>
</div>
</body>
<script>
$( 'div div' ).each( function () {
this.style.border = '1px solid red';
this.style.margin = '5px';
});
var length = $( 'div div' ).length;
</script>
利用空格 实现 后代元素 选择器
现在需要一个函数,函数的参数与结构类似于 select, 该函数专门处理带有 空格的 后代元素
// 假设 selector 选择器是字符串, 而且不是基本选择器
// 肯定是 : selector1 selector2 selector3 形式
// 还有可能: selector1 > selector2:first 形式
function select2 ( selector, results ) {
// 如果是空格形式的, 很简单, 将其 split 一下, 然后遍历数组,
// 利用数组中的选择器来获得元素.
// 比如 selecotr 是 'div div'
// split 后: [ 'div', 'div' ]
// 获得 div: document.getElementsByTagName( 'div' )
// 得到的是一个维数组 list
// 再次遍历 list, 在 list 下再次查找 div 元素
// list[ i ].getElementsByTagName( 'div' );
// 再比如: 'div .c'
// 1> 先找 div -> list
// 2> list[ i ].getElementsByClassName( 'c' )
// 再看一个例子: 'div p .c'
// 1> 找 'div' -> list
// 2> list[ i ] 中找 p -> 重新得到一个集合 list
// 3> 在 list[ i ] 再找 .c
// 首先将元素 split, 得到一个选择器数组
// 遍历数组, 获得元素, 得到元素数组
// 在遍历元素数组, 再来获得元素
var selectors = selector.split( ' ' );
// 假定 'div p .c'
var arr = document.getElementsByTagName( selectors[ i ].trim() );
var tempArr = [];
for ( var i = 0; i < arr.length; i++ ) {
tempArr.push.apply( tempArr, arr[ i ].getElementsByTagName( 'p' ));
}
// 在 tempArr 的基础上再次过滤 .c
var tempArr2 = [];
for ( var i = 0; i > tempArr.length; i++ ) {
tempArr2.push.apply( tempArr2, tempArr[ i ].getElementsByClassName( 'c' ) );
}
}
我们思考怎么提取,伪代码:
// 假设 selector 选择器是字符串, 而且不是基本选择器
// 肯定是 : selector1 selector2 selector3 形式
// 还有可能: selector1 > selector2:first 形式
function basicSelect ( selector, node ) {}
function select2 ( selector, results ) {
var selectors = selector.split( ' ' );
// 假定 'div p .c'
var arr = [], node = [ document ];
for ( var i = 0; i < node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ 0 ], node[ i ] ));
}
var tempArr = [];
for ( var i = 0; i < arr.length; i++ ) {
tempArr.push.apply( tempArr, basicSelect( selectors[ 1 ], arr[ i ] ));
}
// 在 tempArr 的基础上再次过滤 .c
var tempArr2 = [];
for ( var i = 0; i > tempArr.length; i++ ) {
tempArr2.push.apply( tempArr2, basicSelect( selectors[ 2 ], tempArr[ i ] ));
}
}
这里 arr,tempArr, tempArr2都是临时的,用完一次就没用了,所以可以考虑全用 arr (伪代码):
function basicSelect ( selector, node ) {}
function select2 ( selector, results ) {
var selectors = selector.split( ' ' );
// 假定 'div p .c'
var arr = [], node = [ document ];
for ( var i = 0; i < node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ 0 ], node[ i ] ));
}
node = arr;
arr = [];
for ( var i = 0; i < node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ 1 ], node[ i ] ));
}
node = arr;
arr = [];
for ( var i = 0; i > node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ 2 ], node[ i ] ));
}
node = arr;
arr = [];
// 最终结果存储到 node 里面去了
}
这样我们就可以考虑把整个过程用一个循环来做
function basicSelect ( selector, node ) {}
function select2 ( selector, results ) {
results = results || [];
var selectors = selector.split( ' ' );
var arr = [], node = [ document ];
for ( var j = 0; j < selectors.length; j++ ) {
for ( var i = 0; i < node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ j ], node[ i ] ));
}
node = arr;
arr = [];
}
results.push.apply( results, node );
return results;
}
好了我们最后完成一下basicSelect的代码
<div>
<p class='c'>p1
<span class="c">span</span>
</p>
<div>
<p class='c'>p3</p>
<div>
<p>p4</p>
</div>
</div>
</div>
<div>
<p>p2</p>
</div>
<script>
var rbaseselector = /^(?:\#([\w\-]+)|\.([\w\-]+)|(\*)|(\w+))$/;
function basicSelect ( selector, node ) {
node = node || document;
var m, res;
if ( m = rbaseselector.exec( selector ) ) {
if ( m[ 1 ] ) { // id
res = document.getElementById( m[ 1 ] );
if ( res ) {
return [ res ];
} else {
return [];
}
} else if ( m[ 2 ] ) { // class
return node.getElementsByClassName( m[ 2 ] );
} else if ( m[ 3 ] ) {
return node.getElementsByTagName( m[ 3 ] );
} else if ( m[ 4 ] ) {
return node.getElementsByTagName( m[ 4 ] );
}
}
return [];
}
function select2 ( selector, results ) {
results = results || [];
var selectors = selector.split( ' ' );
// 假定 'div p .c'
var arr = [], node = [ document ];
for ( var j = 0; j < selectors.length; j++ ) {
for ( var i = 0; i < node.length; i++ ) {
arr.push.apply( arr, basicSelect( selectors[ j ], node[ i ] ));
}
node = arr;
arr = [];
}
results.push.apply( results, node );
return results;
}
// 测试代码
var list = select2( 'div p .c' );
console.log( list );
</script>
加入后代选择器的封装
var select = (function() {
// 正则表达式
var rnative = /\{\s*\[native/;
var rtrim = /^\s+|\s+$/g;
var rbaseselector = /^(?:\#([\w\-]+)|\.([\w\-]+))|(\*)|([\w\-]+)$/;
var support = {};
// 分别判断方法是否支持
support.qsa = rnative.test( document.querySelectorAll + "" );
support.getElementsByClassName = rnative.test( document.getElementsByClassName + '' );
support.trim = rnative.test( String.prototype.trim + '' );
support.indexOf = rnative.test( Array.prototype.indexOf + '' );
// 基本方法
// 兼容性方法 getByClassName
function getByClassName( className, node ) {
node = node || document;
var allElem, res = [], i;
// 支持就用,不支持,我们就自己实现
if ( support.getElementsByClassName ) {
return node.getElementsByClassName( className );
} else {
allElem = node.getElementsByClassName( '*' ); // 获取所有的元素
for (i = 0; i < allElem.length; i++) { // 进行筛选
if ((' ' + allElem[i].className + '').indexOf(' ' + className + ' ') > -1) {
res.push(allElem[i]);
}
}
return res;
}
}
// 兼容性方法 myTrim
var myTrim = function( str ) {
// 两端去掉空格, 然后返回去掉空格的结果
if ( support.trim ) {
return str.trim();
} else {
// 这时候系统没有,自定义的实现
return str.replace( rtrim, '' );
}
};
// 兼容性方法 myIndexOf
var myIndexOf = function ( array, search, startIndex ) {
startIndex = startIndex || 0;
// 如果有么,就用系统的
if ( support.indexOf ) {
return array.indexOf( search, startIndex );
} else { // 如果没有么,那就自己实现好了挖
for ( var i = startIndex; i < array.length; i++ ) {
if ( array[ i ] === search ) {
return i; // 返回索引
}
}
return -1;
}
};
// 兼容性方法 数组去重
var unique = function( array ) {
var resArray = [], i = 0;
for (; i < array.length; i++ ) {
if ( myIndexOf( resArray, array[ i ] ) == -1 ) {
resArray.push( array[ i ] );
}
}
return resArray;
};
// 基本选择器封装,能够实现 id 类 通配符 标签选择器
function basicSelect( selector, node ) {
node = node || document;
var m, res;
if ( m = rbaseselector.exec( selector ) ) {
if ( m[ 1 ] ) { // id
res = document.getElementById( m[ 1 ] );
// id 选择器比较特殊,没找到会返回 null, 但是应该要返回[] 所以这里要做一个特殊处理
if ( res ){
return [ res ];
} else {
return [];
}
} else if ( m[ 2 ] ) { // class
return getByClassName( m[ 2 ], node );
} else if ( m[ 3 ] ) { // *
return node.getElementsByTagName( m[ 3 ] );
} else if ( m[ 4 ] ) { // tag
return node.getElementsByTagName( m[ 4 ] );
}
}
return [];
}
// 基于 basicSelect 的 层级选择器封装
function select2( selector, results ) {
results = results || [];
var selectors = selector.split( ' ' ); // 将selector 根据 空格进行分割
var arr = [], node = [ document ];
for ( var j = 0; j < selectors.length; j++ ) {
for ( var i = 0; i < node.length; i++ ) {
// 遍历 selectors 分割出来的 子元素, 进行选择
arr.push.apply( arr, basicSelect( selectors[ j ], node[ i ] ) );
}
node = arr;
arr = [];
}
// 最后的结果全都在 results 中
results.push.apply( results, node );
return results;
}
// 核心暴露函数
function select( selector, results ) {
results = results || [];
var m, temp, selectors, i, subselector;
// 你首先传给我个 东西, 那必须是字符串吧
if ( typeof selector != 'string' ) return results;
// 参数没有问题,那就开搞
// 先看看 牛逼的 qsa 能不能用,能用就直接用
if ( support.qsa ) {
results.push.apply( results, document.querySelectorAll( selector ) );
} else {
// 不存在我们自己来搞一搞, 字符串分割
selectors = selector.split( ',' );
// 循环
for( i = 0; i < selectors.length; i++ ) {
subselector = myTrim( selectors[ i ] ); // 去掉不该有的空格
// 接下来就是 处理 subselector
if ( rbaseselector.test( subselector ) ) {
// 基本选择器
results.push.apply( results, basicSelect( subselector ) );
} else {
// select2 函数
select2( subselector, results );
}
}
}
// return 之前过滤一波
return unique( results );
}
return select;
})();
解决 IE 8 中伪数组无法使用 push 的方法 - 完善封装
var list = document.getElementsByTagName( 'div' );
var arr = [];
arr.push.apply( arr, list );
console.log( arr );
在 IE 8 是无法实现这个 push 效果的,伪数组无法使用 push,我们需要考虑自己重新实现 push 的 apply 方法,并加入封装之中
var select = (function() {
var push = [].push;
// 如果出现了错误那么就需要重写 push
try {
var div = document.createElement('div');
div.innerHTML = '<p></p>';
var arr = [];
push.apply(arr, div.getElementsByTagName('p'));
} catch (e) {
push = {
apply: function (array1, array2) {
for (var i = 0; i < array2.length; i++) {
array1[array1.length++] = array2[i];
}
}
};
}
// 正则表达式
var rnative = /\{\s*\[native/;
var rtrim = /^\s+|\s+$/g;
var rbaseselector = /^(?:\#([\w\-]+)|\.([\w\-]+))|(\*)|([\w\-]+)$/;
var support = {};
// 分别判断方法是否支持
support.qsa = rnative.test( document.querySelectorAll + "" );
support.getElementsByClassName = rnative.test( document.getElementsByClassName + '' );
support.trim = rnative.test( String.prototype.trim + '' );
support.indexOf = rnative.test( Array.prototype.indexOf + '' );
// 基本方法
// 兼容性方法 getByClassName
function getByClassName( className, node ) {
node = node || document;
var allElem, res = [], i;
// 支持就用,不支持,我们就自己实现
if ( support.getElementsByClassName ) {
return node.getElementsByClassName( className );
} else {
allElem = node.getElementsByClassName( '*' ); // 获取所有的元素
for (i = 0; i < allElem.length; i++) { // 进行筛选
if ((' ' + allElem[i].className + '').indexOf(' ' + className + ' ') > -1) {
res.push(allElem[i]);
}
}
return res;
}
}
// 兼容性方法 myTrim
var myTrim = function( str ) {
// 两端去掉空格, 然后返回去掉空格的结果
if ( support.trim ) {
return str.trim();
} else {
// 这时候系统没有,自定义的实现
return str.replace( rtrim, '' );
}
};
// 兼容性方法 myIndexOf
var myIndexOf = function ( array, search, startIndex ) {
startIndex = startIndex || 0;
// 如果有么,就用系统的
if ( support.indexOf ) {
return array.indexOf( search, startIndex );
} else { // 如果没有么,那就自己实现好了挖
for ( var i = startIndex; i < array.length; i++ ) {
if ( array[ i ] === search ) {
return i; // 返回索引
}
}
return -1;
}
};
// 兼容性方法 数组去重
var unique = function( array ) {
var resArray = [], i = 0;
for (; i < array.length; i++ ) {
if ( myIndexOf( resArray, array[ i ] ) == -1 ) {
resArray.push( array[ i ] );
}
}
return resArray;
};
// 基本选择器封装,能够实现 id 类 通配符 标签选择器
function basicSelect( selector, node ) {
node = node || document;
var m, res;
if ( m = rbaseselector.exec( selector ) ) {
if ( m[ 1 ] ) { // id
res = document.getElementById( m[ 1 ] );
// id 选择器比较特殊,没找到会返回 null, 但是应该要返回[] 所以这里要做一个特殊处理
if ( res ){
return [ res ];
} else {
return [];
}
} else if ( m[ 2 ] ) { // class
return getByClassName( m[ 2 ], node );
} else if ( m[ 3 ] ) { // *
return node.getElementsByTagName( m[ 3 ] );
} else if ( m[ 4 ] ) { // tag
return node.getElementsByTagName( m[ 4 ] );
}
}
return [];
}
// 基于 basicSelect 的 层级选择器封装
function select2( selector, results ) {
results = results || [];
var selectors = selector.split( ' ' ); // 将selector 根据 空格进行分割
var arr = [], node = [ document ];
for ( var j = 0; j < selectors.length; j++ ) {
for ( var i = 0; i < node.length; i++ ) {
// 遍历 selectors 分割出来的 子元素, 进行选择
push.apply( arr, basicSelect( selectors[ j ], node[ i ] ) );
}
node = arr;
arr = [];
}
// 最后的结果全都在 results 中
push.apply( results, node );
return results;
}
// 核心暴露函数
function select( selector, results ) {
results = results || [];
var m, temp, selectors, i, subselector;
// 你首先传给我个 东西, 那必须是字符串吧
if ( typeof selector != 'string' ) return results;
// 参数没有问题,那就开搞
// 先看看 牛逼的 qsa 能不能用,能用就直接用
if ( support.qsa ) {
push.apply( results, document.querySelectorAll( selector ) );
} else {
// 不存在我们自己来搞一搞, 字符串分割
selectors = selector.split( ',' );
// 循环
for( i = 0; i < selectors.length; i++ ) {
subselector = myTrim( selectors[ i ] ); // 去掉不该有的空格
// 接下来就是 处理 subselector
if ( rbaseselector.test( subselector ) ) {
// 基本选择器
push.apply( results, basicSelect( subselector ) );
} else {
// select2 函数
select2( subselector, results );
}
}
}
// return 之前过滤一波
return unique( results );
}
return select;
})();
each 函数函数封装
function each ( arr, func ) {
var i;
// 在 js 中(面向对象的编程语言中) 有一个规则:鸭式变型
// 在 ES5 中还引入了 Array.isArray 的方法专门来判断数组
if ( arr instanceof Array || arr.length >= 0) {
for ( i = 0; i < arr.length; i++ ) {
func.call( arr[ i ], i, arr[ i ] );
}
} else {
for ( i in arr ) {
func.call( arr[ i ], i, arr[ i ] );
}
}
return arr;
}
map 函数的封装
function map(arr, func) {
var i, res = [], tmp;
// 鸭式变型
if (arr instanceof Array || arr.length >= 0) {
for (i = 0; i < arr.length; i++) {
tmp = func(arr[i], i);
if (tmp != null) {
res.push(tmp);
}
}
} else {
for (i in arr) {
tmp = func(arr[i], i);
if (tmp != null) {
res.push(tmp);
}
}
}
return res;
}
var list = map([1, 2, 3, 4, 5], function (v, i) {
return 'a';
});