It has been 1043 days since the last update, the content of the article may be outdated.
创建 数组字面量:var arr = [];
,内部还是new Array()
的方式。
1 2 3 var arr = [1 ,2 ,3 ];console .log(arr);
构造函数:var arr = new Array();
1 2 3 var arr = new Array (1 , 2 , 3 );console .log(arr);
共同点:构造函数没有参数时表示定义一个空数组,等同于[]
。
1 2 var arr = new Array ();console .log(arr);
区别:
使用构造函数定义数组,当它只有一个数字类型的参数时,意为数组长度,而不是在确定数组成员。 使用字面量定义数组,数组是什么就是什么,非常明确。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var arr = new Array (2 );console .log(arr); var arr1 = [3.5 ];console .log(arr1); var arr2 = new Array (3.5 );console .log(arr2);
一般情况下,推荐使用字面量创建数组 。
数组是一种特殊的对象,所以当一个数组没有数据,但又访问它的话,会返回undefined
。
1 2 3 4 var arr = ['tuan' , 'dou' , 'piao' ];console .log(arr[0 ]); console .log(arr[3 ]);
操作数组的方法发展:以 ES3.0 为基础,ES5.0 在 ES3 基础上扩展,ES6.0 在 ES5 基础上扩展。
以下这些操作数组的方法是 ES3.0 中的。
改变原数组 push():末尾加数据 push()
:往数组的末尾追加数据,返回数组长度。
1 2 3 4 var arr = [1 ];console .log(arr.push(10 , 3 )); console .log(arr);
pop():末尾删数据 pop()
:把数组的最后一位剪切出来,返回被剪切的数据。执行时不用传参。
1 2 3 4 var arr = [1 , 2 , 3 , 4 , 6 ];console .log(arr.pop()); console .log(arr);
unshift():开头加数据 unshift()
:往数组的开头追加数据,返回数组长度。
1 2 3 4 var arr = [1 ];console .log(arr.unshift(10 , 3 )); console .log(arr);
shift():开头删数据 shift()
:把数组的第一位剪切出来,返回被剪切的数据。执行时不用传参。
1 2 3 4 var arr = [1 , 2 , 3 , 4 ];console .log(arr.shift()); console .log(arr);
reverse():逆转顺序 reverse()
:逆转数组顺序。
1 2 3 var arr = [1 , 2 , 3 ];console .log(arr.reverse());
splice():截取并添加 splice()
:从第几位开始,截取多少位,在切口处添加数据。
格式:xx.splice(从第几位开始,截取多少的长度,在切口处添加新的数据)
返回被截取的数据。
1 2 3 4 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];console .log(arr.splice(2 , 1 , 0 , 9 , 8 )); console .log(arr);
如果不截取,仅添加:
1 2 3 4 5 6 var arr1 = [1 , 2 , 3 , 5 ];console .log(arr1.splice(3 , 0 , 4 )); console .log(arr1);
如果从负数位开始,就是逆序处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];console .log(arr.splice(-1 , 3 )); console .log(arr); var arr1 = [1 , 2 , 3 , 4 , 5 , 6 ];console .log(arr1.splice(-4 , 3 )); console .log(arr1);
负数的内部原理:数据实际索引 = 负数 + 数组的 lenght
1 2 3 4 5 6 7 8 9 var arr = [1 , 3 , 5 , 7 ]splice = function (pos ) { pos += pos > 0 ? 0 : this .length }
sort():排序 sort()
:给数组排序,默认按字符的 asc 码排列。返回排序完成后的数组。
1 2 3 4 var arr = [3 , 4 , 10 , -1 , 5 , 6 ];console .log(arr.sort()); console .log(arr);
如果想要改变sort()
方法的排序规则,在括号内创建函数,系统会自动调用,实现你想要的规则。
要求:这个函数必须写两个形式参数。
原理:系统会自动调用无数次这个函数(调用次数可求),第一次调用时,会把数组的第一位和第二位传入函数。第二次传入第一位和第三位,第三次传入第一位和第四位,当第一位和所有都比完后,再比较第二位和第三位,第二位和第四位……(冒泡排序)。通过函数内的规则比较,比较完成后,当返回值:
为负数时,前面的数在前。 为正数时,后面的数在前。 为 0 时,保持不变 现在我们想要按照自然数升序排序,那么定义规则如下:
第一次:3 和 4 比较,3 < 4
-> 输出 -1,为负数,3 在前 第二次:3, 10 -> 3 < 10
-> -1 -> 3 在前 第三次:3, -1 ->3 > -1
-> 1 -> 3 在后 第六次:4, 10 ->4 < 10
-> -1 -> 4 在前 1 2 3 4 5 6 7 8 9 10 11 var arr = [3 , 4 , 10 , -1 , 5 , 6 ];arr.sort(function (a, b ) { if (a > b) { return 1 ; }else { return -1 ; } }); console .log(arr);
简化自然数升序排序规则:
1 2 3 4 5 6 7 8 9 var arr = [3 , 4 , 10 , -1 , 5 , 6 ];arr.sort(function (a, b ) { return a - b; }); console .log(arr);
自然数降序规则:
1 2 3 4 5 6 7 8 9 var arr = [3 , 4 , 10 , -1 , 5 , 6 ];arr.sort(function (a, b ) { return b - a; }); console .log(arr);
给一个有序的数组乱序,确保每次执行是不一样的顺序。
1 2 3 4 5 6 7 8 9 var arr = [1 , 2 , 3 , 4 , 5 , 6 , 7 ];arr.sort(function (a, b ) { return Math .random() - 0.5 ; }); console .log(arr); console .log(arr);
将下面的数组内成员按字符串长度排序:
1 2 3 4 5 6 7 8 9 var arr = ['ab' , 'bcdefg' , 'cccc' , 'dfsgsgsgesgv' ,'z' , 'xxyyzz' ];arr.sort(function (a, b ) { return a.length - b.length; }); console .log(arr);
规律总结:不管是什么比较,都是按照冒泡排序将数组成员传入函数进行比较,所以大胆写就好了。
不改变原数组 concat():数组拼接 concat()
:连接两个数组。
1 2 3 4 5 6 7 8 var arr = [1 , 2 , 3 ];var arr1 = [5 , 7 ];console .log(arr.concat(arr1)); console .log(arr); console .log(arr1);
注意,连接后有了一个新的数组,原数组是没有改变的。
join():数组元素连接 join()
:把数组各元素用参数连接起来,并返回字符串形式。不传参数的话,默认按照逗号连接。
1 2 3 4 5 6 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];var newArr = arr.join('-' );console .log(typeof newArr); console .log(newArr);
字符串方法split()
与数组方法join()
是互逆的,join()
按什么连,split()
就可以按什么拆。
split()
:将字符串按照参数拆分成数组。
1 2 3 4 5 6 7 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];var newArr = arr.join('-' );console .log(newArr); console .log(newArr.split('-' ));
把下列所有字符串连接起来:
1 2 3 4 5 6 7 8 9 10 var str = 'Alibaba' , str1 = 'Baidu' , str2 = 'Tencent' , str3 = 'Bytedance' , str4 = 'Wangyi' , str5 = 'Weibo' ; var arr = [str, str1, str2, str3, str4, str5];console .log(arr.join('' ));
字符串是原始值,存储在栈内存里面,栈内存是先进后出的规则。如果使用+
号来连接,需要来回在栈内取元素,这样很浪费时间,效率很低。 数组作为引用值,是一种散列结构存储在堆内存里面,散列算法会更有效率。
toString() toString()
:将数组变成字符串。
1 2 3 4 5 6 var arr = [1 , 2 , 3 ];console .log(typeof (arr.toString())); console .log(arr.toString()); console .log(arr);
slice():截取 slice()
:对数组进行截取,不改变原数组,所以要注意接收返回值 。
格式:slice(从该位开始截取,截取到该位之前)
。
1 2 3 4 5 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];var newArr = arr.slice(1 , 3 );console .log(newArr);
如果省略第二个参数,那么就代表:从该位开始截取,截取到数组结束之前。
1 2 3 4 5 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];var newArr = arr.slice(3 );console .log(newArr);
如果是从负数位开始截取,就是逆序截取。
如果两个参数都省略,那么就代表数组全部截取。
1 2 3 4 5 var arr = [1 , 2 , 3 , 4 , 5 , 6 ];var newArr = arr.slice();console .log(newArr);
类数组 创建 属性要为索引(数字)属性。
必须有 length 属性。
最好加上push()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var obj = { '0' : 'a' , '1' : 'b' , '2' : 'c' , 'length' : 3 , push : Array .prototype.push } obj.push('d' ); console .log(obj);
如果再加上splice()
方法,会完全展现出数组的样式。
1 2 3 4 5 6 7 8 9 10 11 12 13 var obj = { '0' : 'a' , '1' : 'b' , '2' : 'c' , 'length' : 3 , 'push' : Array .prototype.push, 'splice' : Array .prototype.splice } console .log(obj);
它依然是对象,但可以当数组用。
类数组的优点:把数组和对象的好处拼到一起。不过并不是所有的数组方法都能用,除非你自己添加。
arguments
就是一个类数组。
内部原理 push()
方法能作用于类数组,关键点在于:length 属性。
对象中 length 的属性值决定了push()
从哪里加入新数据。
Array.prototype.push()
可以理解的简单版本:
1 2 3 4 5 6 7 Array .prototype.push = function ( ) { for (var i = 0 ; i < arguments .length; i++) { this [this .length] = arguments [i]; this .length ++; } return this .length; }
Array.prototype.push()
源码:
1 2 3 4 5 6 7 8 9 function ArrayPush ( ) { var n = TO_UNIT32(this .length); var m = %_ArgumentsLength(); for (var i = 0 ; i < m; i++) { this [i + n ] = %_Arguments(i); } this .length = n + m; return this .length; }
求下列输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var obj = { '2' : 'a' , '3' : 'b' , 'length' : 2 , 'push' : Array .prototype.push, } obj.push('c' ); obj.push('d' ); console .log(obj);
类数组转换为数组 最原生的方法:使用一个新的数组。
1 2 3 4 5 6 var divArr = document .getElementsByTagName('div' );var result = [], len = divArr.length; for (var i = 0 ; i < len; i++) { result.push(divArr[i]); }
Array.prototype.slice.call(arrayLike)
:该方法借用了数组原型中的 slice 方法,会返回一个数组。
1 2 3 4 5 6 7 8 9 10 11 Array .prototype.slice = function (start, end ) { var result = new Array (); start = start || 0 ; end = end || this .length; for (var i = start; i < end; i++) { result.push(this [i]); } return result; }
1 2 3 4 5 6 7 8 9 10 var arrLike = { '0' : 'tuan' , '1' : 'dou' , '2' : 'piao' , 'length' : 3 } console .log(Array .prototype.slice.call(arrLike));
Array.from()
:ES6 中新增方法,可以将两类对象转为真正的数组:类数组对象、可遍历(iterable)对象(包括 ES6 新增的数据结构 Set 和 Map)。只要有 length 属性的对象,都可以应用此方法转换成数组。
1 2 3 4 5 6 7 var arrayLike = { '0' : 'a' , '1' : 'b' , '2' : 'c' , length : 3 }; var arr = Array .from(arrayLike);
补充:数组迭代方法 数组迭代方法对每个数组项进行操作,不会改变原数组。
forEach()-调用函数 forEach(callback)
方法为每个数组元素调用一次函数(回调函数),没有返回值。
callback 回调函数中有两个变量:elem 和 index,elem 用于接收数组元素,index 就是对应的元素索引。
1 2 3 Array .forEach(function (elem, index ) { });
例:
1 2 3 4 5 var familyArr = ['tuan' , 'dou' , 'piao' ];familyArr.forEach(function (elem, index ) { console .log(index + ': ' + elem); });
内部实现原理:
1 2 3 4 5 Array .prototype.myForEach = function (fn ) { for (var i = 0 ; i < this .length; i++) { fn(this [i], i); } }
不能使用 break 语句,从源码来看,这个 break 的位置是在函数 fn 里面,而不是在 for 循环里,所以无法通过它来中断循环语句。
1 2 3 4 5 6 7 8 9 10 11 12 var familyArr = [ {name : 'tuan' , age : 3 , card : 'Visa' }, {name : 'dou' , age : 2 , card : 'Master' }, {name : 'piao' , age : 1 , card : 'Apple Card' } ]; familyArr.forEach(function (elem, index ) { if (elem.name == 'dou' ) { break ; } console .log(index); });
return 语句也只是终止循环中那一次 fn 函数,无法终止整个 for 循环。
1 2 3 4 5 6 7 familyArr.forEach(function (elem, index ) { if (elem.name == 'dou' ) { return ; } console .log(index); });
filter()-筛选 filter(callback)
方法可以通过函数筛选出一个新的数组。返回值是符合筛选条件的新数组。
1 2 3 Array .filter(function (elem, index ) { });
回调函数中会有一个 return 语句,后面跟有筛选条件。当有数组成员满足该条件时,就存放到新数组中。遍历完成后,将该数组返回。
1 2 3 4 5 6 7 8 9 10 11 12 var familyArr = [ {name : 'tuan' , age : 3 }, {name : 'dou' , age : 2 }, {name : 'piao' , age : 1 } ]; var newArr = familyArr.filter(function (elem, index ) { if (index % 2 == 1 ) { return true ; } }); console .log(newArr);
内部实现原理:
1 2 3 4 5 6 7 8 9 Array .prototype.myFilter = function (fn ) { var arr = []; for (var i = 0 ; i < this .length; i++) { if (fn(this [i], i)) { arr.push(this [i]); } } return arr; }
和 map() 的区别:filter() 方法 return 后跟的是筛选条件,符合条件的存放到新数组中,最后返回新数组。map() 方法 return 后跟的就是我需要的值,这些值直接存放到新数组中,最后返回新数组。
map()-操作 map(callback)
方法对每个数组元素执行函数,然后使用 return 出来的值创建新数组。
1 2 3 Array .map(function (elem, index ) { });
回调函数中有 return 语句,后面跟的值就是新数组的成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var newArr = familyArr.map(function (elem, index ) { delete elem.card; delete elem.age; if (index % 2 == 0 ) { elem.character = '狗鸡' ; return elem; }else { elem.character = '小可爱' ; return elem; } }); console .log(newArr);
内部实现原理:
1 2 3 4 5 6 7 Array .prototype.myMap = function (fn ) { var arr = []; for (var i = 0 ; i < this .length; i++) { arr.push(fn(this [i], i)); } return arr; };
从内部原理可以看出,map() 方法的实现是基于浅克隆,即直接拷贝引用地址。所以需注意,当数组成员是值类型,map不会改变原数组;当数组成员是引用类型,则可以改变原数组 。
map() 方法不会对没有值的数组元素执行函数。
reduce()-累加 reduce(callback)
方法从左向右在每个数组元素上运行函数,以生成或减少它单个值。
1 2 3 Array .reduce(function (perValue, elem, index ) { ... },initialValue);
callback 回调函数中有三个变量:perValue,elem 和 index。perValue 为上一次返回的值,如果没有上一次 return 值,会取第 0 位数组成员作为初始值,然后从数组第 1 位开始运行。elem 和 index 依然代表数组成员和索引。
1 2 3 4 5 6 7 8 9 10 var arr = [5 , 3 , 7 , 1 , 9 ];arr.reduce(function (preValue, elem, index ) { console .log(preValue); return index; });
也可以传一个初始值 initialValue。
1 2 3 4 5 6 7 8 9 10 11 var arr = [5 , 3 , 7 , 1 , 9 ];arr.reduce(function (preValue, elem, index ) { console .log(preValue); return index; }, 10 );
常用于累加器。
1 2 3 4 5 6 7 8 9 10 11 var arr = [5 , 3 , 7 , 1 , 9 ];var result = arr.reduce(function (preValue, elem, index ) { return preValue + elem; }); console .log(result);
内部实现原理:
先判断 init 是否有值,没有则取第 0 位作初始值,函数从第 1 位开始执行 init 有值,函数从第 0 位开始执行 fn 每次执行完成后 return 出来的值,会作为下一次 previous 的值,所以直接用 previous 接收即可 最后返回全部数组成员执行完成后的 previous 值 1 2 3 4 5 6 7 8 9 10 11 12 Array .prototype.myReduce = function (fn, init ) { var previous = init, i = 0 ; if (init === undefined ) { previous = this [0 ]; i = 1 ; } for (i; i < this .length; i++) { previous = fn(previous, this [i], i); } return previous; };
和 map() 的区别:map() 会存储每一次执行的值,最后组成一个新数组返回。reduce() 是在所有数组成员执行完成后,返回最后的那个 return 值。
reduceRight()-累加 reduceRight(callback)
方法是从右向左,其他地方和 reduce() 没区别。
initialValue 没有传值时:
1 2 3 4 5 6 7 8 9 10 11 var arr = [5 , 3 , 7 , 1 , 9 ];arr.reduceRight(function (preValue, elem, index ) { console .log(preValue); return index; });
initialValue 有值时:
1 2 3 4 5 var arr = [5 , 3 , 7 , 1 , 9 ];arr.reduceRight(function (preValue, elem, index ) { console .log(preValue); return index; }, 10 );
内部实现原理:
1 2 3 4 5 6 7 8 9 10 11 12 Array .prototype.myReduceRight = function (fn, init ) { var previous = init, i = this .length - 1 ; if (init === undefined ) { previous = this [i]; i = this .length - 2 ; } for (i; i >= 0 ; i--) { previous = fn(previous, this [i], i); } return previous; };
剩余更多方法
应用 封装 typeof() 方法 要求:调用后可以输出准确的值类型,比如原始值数字输出number
,数字对象输出number-object
,数组输出array
等。
思路:
分两类:原始值、引用值 区分引用值:数组、对象、包装类 包装类判断:Object.prototype.toString()
函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function myTypeof (target ) { var typeStr = typeof (target), template = { '[object Array]' : 'array' , '[object Object]' : 'object' , '[object Number]' : 'number - object' , '[object String]' : 'string - object' , '[object Boolean]' : 'boolean - object' }; if (target === null ) { return 'null' ; } else if (typeStr == 'object' ) { var str = Object .prototype.toString.call(target); return template[str]; } else { return typeStr; } } myTypeof();
这一类工具方法可以放入一个专门的 .js 文件里,以便后续直接使用。
数组去重 要求:在原型链上进行编程,不改变原数组。
思路:
一个对象不可能有两个或两个以上同名的属性,可以同值,不能同名。 当某个属性已经存在,后续调用都默认是在访问这个属性。如果某个属性不存在,访问会显示undefined
。 将数组内的成员(属性值)当作对象的属性名传入,属性值任意(不是转化为 false 的那六个值即可)。 依次传入,如果某个属性名已经存在(返回属性值),传入下一个判断。 如果不存在(返回undefined
),新增这个属性,再接着判断下一个。 最终会剩下不同的属性名,取出剩下的属性名,完成。 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 Array .prototype.unique = function ( ) { var temp = {}, arr = [], len = this .length; for (var i = 0 ; i < len; i++) { if (!temp[this [i]]) { temp[this [i]] = 'a' ; arr.push(this [i]); } } return arr; }
建立空对象用来装属性名 建立一个空数组用来装去重后的数组成员,最后 return 出去 遍历数组成员:for(var i = 0; i < len; i++) {}
将数组成员this[i]
传入对象temp
中:temp[this[i]]
如果访问的属性名存在,可以访问到属性值,那就忽略,继续下一个 如果不存在:temp[this[i]] = undefined
让undefiend
取反,走 if 语句新增进去:!temp[this[i]]
进行调用:
1 2 3 4 5 var oneArr = [1 , 1 , 3 , 4 , 5 , 5 , 5 , 3 , 'dou' , 'dou' ];console .log(oneArr.unique()); console .log(oneArr);
注意:这个对象里的属性值任意,但不能是转化为 false 的那六个值。因为如果属性值转化为 false 了,然后再取反,会变成 true,走 if 语句,这样就不能去重了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Array .prototype.unique = function ( ) { var temp = {}, arr = [], len = this .length; for (var i = 0 ; i < len; i++) { if (!temp[this [i]]) { temp[this [i]] = this [i]; arr.push(this [i]); } } return arr; } var oneArr = [1 , 1 , 0 , 0 , 0 , 5 , 5 , 3 , 'dou' , 'dou' ];console .log(oneArr.unique());
当temp[this[i]] = this[i]
,而此时数组内有成员为 0,考察 0 的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 this [2 ] = 0 -> temp[this [2 ]] -> temp[0 ] = undefined ->if (!undefined ) { temp[0 ] = this [2 ] -> temp[0 ] = 0 arr.push(0 ) -> arr = [0 ] } this [3 ] = 0 -> temp[this [3 ]] 已有:temp[0 ] = 0 -> if (!0 ) { temp[0 ] = this [3 ] -> temp[0 ] = 0 arr.push(0 ) -> arr = [0 , 0 ] }
demo: 数组搜索 HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!doctype html > <html lang ="en" > <head > <meta charset ="utf-8" > <title > 数组搜索</title > <link rel ="stylesheet" href ="arraySearch.css" > </head > <body > <div class ="wrapper" > <div class ="search" > <input class ="search-box" type ="text" placeholder ="请输入用户名" > <p > <span species ="a" class ="active" > All</span > <span species ="human" > Human</span > <span species ="cat" > Cat</span > </p > </div > <div class ="user-list" > <ul > </ul > </div > </div > <script src ="../tools.js" > </script > <script src ="arraySearch.js" > </script > </body > </html >
CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 * { padding : 0 ; margin : 0 ; } .wrapper { width : 400px ; border : 1px solid #ccc ; margin : 100px auto; border-radius : 4px ; } .wrapper .search { width : 100% ; height : 50px ; line-height : 50px ; } .wrapper .search .search-box { padding : 10px 15px ; border-radius : 4px ; margin-left : 20px ; border : 1px solid #ccc ; outline : none; } .wrapper .search p { display : inline-block; margin-left : 10px ; } .wrapper .search p span { color : #38f ; padding : 3px 5px ; cursor : pointer; } .wrapper .search p span .active { color : #fff ; background-color : #38f ; border-radius : 4px ; } .wrapper .user-list { width : 100% ; } .wrapper .user-list ul { margin : 0 20px ; } .wrapper .user-list ul li { position : relative; list-style : none; border-bottom : 1px solid #ccc ; padding : 10px 0 ; } .wrapper .user-list ul li img { position : absolute; width : 40px ; height : 40px ; } .wrapper .user-list ul li p { margin-left : 50px ; } .wrapper .user-list ul li p .des { font-size : 0.7em ; }
原数组:
1 2 3 4 5 6 7 var personArr = [ {name : '刘团子' , src : 'images/weibotuan.jpg' , des : '我要减肥......' , species : 'human' }, {name : '陈豆子' , src : 'images/weibodou.jpg' , des : '你看不见我🙈' , species : 'human' }, {name : '票票' , src : 'images/littlePiao.jpeg' , des : '我闻到小鱼干的味道了!' , species : 'cat' }, {name : '刘圆圆' , src : 'images/weibonowt.jpg' , des : '一只坏喵。' , species : 'cat' }, {name : '陈方方' , src : 'images/weibonowd.jpg' , des : '一条咸鱼。' , species : 'human' }, ];
选择 DOM 元素。
1 2 3 var oUl = document .getElementsByTagName('ul' )[0 ];var oSearch = document .getElementsByClassName('search-box' )[0 ];var oP = document .getElementsByTagName('p' )[0 ];
渲染函数:将数据插入到 HTML 页面。
4-8 行如果不换行写是这样:字符串 + 变量 + 字符串 + 变量 + 字符串 + 变量 + 字符串 '<li><img src=' + elem.src + '><p class="username">' + elem.name + '</p><p class="des">' + elem.des + '</p></li>'
先用字符串拼接起来,然后通过 innerHTML 添到 ul 标签内 1 2 3 4 5 6 7 8 9 10 11 12 function renderList (arr ) { var str = '' ; arr.forEach(function (elem, index ) { str += '<li>\ <img src=' + elem.src + '>\ <p class="username">' + elem.name + '</p>\ <p class="des">' + elem.des + '</p>\ </li>' ; }); oUl.innerHTML = str; } renderList(personArr);
设定全局变量:
对于这类标记了一些信息且需要写在全局的变量,最好使用对象收纳起来 intoText:监听文本框输入的值state.intoText = this.value;
intoSpec:监听点击的 span 对应的 species 值state.intoSpec = event.target.getAttribute('species');
1 2 3 4 var state = { intoText : '' , intoSpec : 'a' }
input 事件:监听输入框文本输入。
1 2 3 4 oSearch.oninput = function ( ) { state.intoText = this .value; renderList(lastFilterfn(personArr)); }
根据 name 筛选数组。函数执行完成后会返回一个筛选后的新数组。
筛选条件:elem.name.indexOf(text) !== -1 ?
如果未找到项目,Array.indexOf()
会返回 -1。所以我们返回能找到的数组成员 1 2 3 4 5 function filterText (text, arr ) { return arr.filter(function (elem, index ) { return elem.name.indexOf(text) !== -1 ? true : false ; }); }
click 事件:监听鼠标点击。
点击事件绑定在父元素 p 标签上,冒泡到子元素 span 标签上 如果点击到间隙就会点到 p 标签上,所以需要先判断是否点击的是 span 标签 event.target:事件源对象 1 2 3 4 5 6 7 8 9 10 addEvent(oP, 'click' , opClick); function opClick (e ) { var event = e || window .event; if (event.target.nodeName == 'SPAN' ) { document .getElementsByClassName('active' )[0 ].className = '' ; event.target.className = 'active' ; state.intoSpec = event.target.getAttribute('species' ); renderList(lastFilterfn(personArr)); } }
根据物种 Species 来筛选。函数执行完成后会返回一个筛选后的新数组。
点击 All,监听到的 intoSpec 值为 a,数组全部返回即可 否则根据传入值判断,符合条件(监听到的 intoSpec 值等于数组成员的 species 值)的数组成员留下 1 2 3 4 5 6 7 8 9 function filterSpecies (spec, arr ) { if (spec == 'a' ){ return arr; }else { return arr.filter(function (elem, index ) { return elem.species == spec; }); } }
合并筛选。
比如筛选 human 中的 ‘豆’:点击‘Human’,先触发 click 事件,执行 filterSpecies 函数,找到 species = human 的数组成员,然后拿着已经筛选过的数组 ,输入关键字‘豆’,触发 input 事件,执行 filterText 函数,找到 name 中有‘豆’的成员,最后执行函数 renderList 渲染到页面。 比如筛选姓‘刘’的 human:输入关键字‘刘’,触发 input 事件,执行 filterText 函数,找到所有 name 中有‘刘’的成员。然后在这个基础之上,点击‘Human’,触发 click 事件,也就是拿着已经筛选过的数组执行 filterSpecies 函数,找到 species = human 的数组成员,最后执行函数 renderList 渲染到页面。 也就是说,当某个事件触发后,我想在这个基础之上去触发另一个事件,最好的办法就是在已经筛选的数组之上,再进行一次筛选就可以了。 即在 click 事件中,执行一遍 filterText 函数,再拿着它返回的数组 lastArr 执行一遍 filterSpecies 函数,最后执行 renderList 函数。在 input 事件中也添加同样的操作,触发顺序不影响。 1 2 3 var lastArr = filterText(intoText, personArr);lastArr = filterSpecies(intoSpec, lastArr); renderList(lastArr);
两个事件中增加一样的代码,这种情况下我们可以将它打包成一个函数,直接调用。 合并筛选函数:
对象 json 中的属性名需和对象 state 的属性名一样,因为在json[prop](state[prop], lastArr)
这条语法中,它们共用了一个 prop 名。
函数 unionFilterFn 执行完成后会 return 一个函数出来,让 lastFilterfn 等于这个函数。
lastFilterfn 传入要处理的数组 personArr,执行完成后会返回新一个数组 lastArr。
最后让函数 renderList 将这个新数组渲染到页面即可:renderList(lastFilterfn(personArr));
1 2 3 4 5 6 7 8 9 10 function unionFilterFn (json ) { return function (arr ) { var lastArr = arr; for (prop in json) { lastArr = json[prop](state[prop], lastArr); } return lastArr; } } var lastFilterfn = unionFilterFn({intoText : filterText, intoSpec : filterSpecies});
注意,无论是 input 事件还是 click 事件,都只是为了监听输入的文字或点击的标签,然后给筛选函数传值的。比如我输入名字(oninput)传了 intoText 值进来,然后又点击标签(onclick)传了 intoSpec 值进来,执行顺序为:oninput -> intoText -> onclick -> intoSpec。所以在一个事件中筛选完名字后接着筛选物种,只是改变了数组,监听的值早就传了。