创建

数组字面量:var arr = [];,内部还是new Array()的方式。

1
2
3
var arr = [1,2,3];

console.log(arr); // [1, 2, 3]

构造函数:var arr = new Array();

1
2
3
var arr = new Array(1, 2, 3);

console.log(arr); // [1, 2, 3]

共同点:构造函数没有参数时表示定义一个空数组,等同于[]

1
2
var arr = new Array();
console.log(arr); // []

区别:

  • 使用构造函数定义数组,当它只有一个数字类型的参数时,意为数组长度,而不是在确定数组成员。
  • 使用字面量定义数组,数组是什么就是什么,非常明确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 表示数组长度为 2 的稀松数组:

var arr = new Array(2);
console.log(arr); // [empty × 2]

// 表示数组:

var arr1 = [3.5];
console.log(arr1); // [3.5]

// 报错:

var arr2 = new Array(3.5);
console.log(arr2); // 报错,无效的数组长度 Uncaught RangeError: Invalid array length

一般情况下,推荐使用字面量创建数组

数组是一种特殊的对象,所以当一个数组没有数据,但又访问它的话,会返回undefined

1
2
3
4
var arr = ['tuan', 'dou', 'piao'];

console.log(arr[0]); // tuan
console.log(arr[3]); // undefined

操作数组的方法发展:以 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)); // 2
console.log(arr); // [1, 10, 3]

pop():末尾删数据

pop():把数组的最后一位剪切出来,返回被剪切的数据。执行时不用传参。

1
2
3
4
var arr = [1, 2, 3, 4, 6];

console.log(arr.pop()); // 6
console.log(arr); // [1, 2, 3, 4]

unshift():开头加数据

unshift():往数组的开头追加数据,返回数组长度。

1
2
3
4
var arr = [1];

console.log(arr.unshift(10, 3)); // 2
console.log(arr); // [10, 3, 1]

shift():开头删数据

shift():把数组的第一位剪切出来,返回被剪切的数据。执行时不用传参。

1
2
3
4
var arr = [1, 2, 3, 4];

console.log(arr.shift()); // 1
console.log(arr); // [2, 3, 4]

reverse():逆转顺序

reverse():逆转数组顺序。

1
2
3
var arr = [1, 2, 3];

console.log(arr.reverse()); // [3, 2, 1]

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)); // [3]
console.log(arr); // [ 1, 2, 0, 9, 8, 4, 5, 6 ]

如果不截取,仅添加:

1
2
3
4
5
6
// 从第 3 位开始,截取 0,在第 3 位之前插入新的数据

var arr1 = [1, 2, 3, 5];

console.log(arr1.splice(3, 0, 4)); // []
console.log(arr1); // [ 1, 2, 3, 4, 5]

如果从负数位开始,就是逆序处理:

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)); // [6]
console.log(arr); // [1, 2, 3, 4, 5]

// 注意:如果是从 -1 开始,截取 3 位,最后结果也只有 1 位
// 如果想截取 3 位,负数需要是 -3 甚至 更小

var arr1 = [1, 2, 3, 4, 5, 6];

console.log(arr1.splice(-4, 3)); // [3, 4, 5]

console.log(arr1); // [1, 2, 6]

负数的内部原理:数据实际索引 = 负数 + 数组的 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
}
// 比如 pos 为 -1,小于 0
// 那么 pos += 4
// pos = -1 + 4 -> pos = 3
// -1 位就是第 3 位

sort():排序

sort():给数组排序,默认按字符的 asc 码排列。返回排序完成后的数组。

1
2
3
4
var arr = [3, 4, 10, -1, 5, 6];

console.log(arr.sort()); // [ -1, 10, 3, 4, 5, 6 ]
console.log(arr); // [ -1, 10, 3, 4, 5, 6 ]

如果想要改变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, 3, 4, 5, 6, 10 ]

简化自然数升序排序规则:

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, 3, 4, 5, 6, 10 ]

自然数降序规则:

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); // [ 10, 6, 5, 4, 3, -1 ]

给一个有序的数组乱序,确保每次执行是不一样的顺序。

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); // [ 4, 1, 5, 2, 3, 7, 6 ]
console.log(arr); // [ 2, 1, 4, 7, 3, 5, 6 ]

将下面的数组内成员按字符串长度排序:

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); // [ 'z', 'ab', 'cccc', 'bcdefg', 'xxyyzz', 'dfsgsgsgesgv' ]

规律总结:不管是什么比较,都是按照冒泡排序将数组成员传入函数进行比较,所以大胆写就好了。

不改变原数组

concat():数组拼接

concat():连接两个数组。

1
2
3
4
5
6
7
8
var arr = [1, 2, 3];

var arr1 = [5, 7];

console.log(arr.concat(arr1)); // [ 1, 2, 3, 5, 7 ]

console.log(arr); // [ 1, 2, 3 ]
console.log(arr1); // [ 5, 7 ]

注意,连接后有了一个新的数组,原数组是没有改变的。

join():数组元素连接

join():把数组各元素用参数连接起来,并返回字符串形式。不传参数的话,默认按照逗号连接。

1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6];

var newArr = arr.join('-');

console.log(typeof newArr); // string
console.log(newArr); // 1-2-3-4-5-6

字符串方法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); // 1-2-3-4-5-6

console.log(newArr.split('-')); // [ '1', '2', '3', '4', '5', '6' ]

把下列所有字符串连接起来:

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('')); // AlibabaBaiduTencentBytedanceWangyiWeibo

字符串是原始值,存储在栈内存里面,栈内存是先进后出的规则。如果使用+号来连接,需要来回在栈内取元素,这样很浪费时间,效率很低。
数组作为引用值,是一种散列结构存储在堆内存里面,散列算法会更有效率。

toString()

toString():将数组变成字符串。

1
2
3
4
5
6
var arr = [1, 2, 3];

console.log(typeof(arr.toString())); // string
console.log(arr.toString()); // 1, 2, 3

console.log(arr); // [1, 2, 3]

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); // [2, 3]

如果省略第二个参数,那么就代表:从该位开始截取,截取到数组结束之前。

1
2
3
4
5
var arr = [1, 2, 3, 4, 5, 6];

var newArr = arr.slice(3);

console.log(newArr); // [4, 5, 6]

如果是从负数位开始截取,就是逆序截取。

如果两个参数都省略,那么就代表数组全部截取。

1
2
3
4
5
var arr = [1, 2, 3, 4, 5, 6];

var newArr = arr.slice();

console.log(newArr); // [1, 2, 3, 4, 5, 6]

类数组

创建

属性要为索引(数字)属性。

必须有 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);

// Output:
// { '0': 'a',
// '1': 'b',
// '2': 'c',
// '3': 'd',
// length: 4,
// push: [Function: push] }

如果再加上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);

// Output:
// Object(3) ["a", "b", "c", push: ƒ, splice: ƒ]

它依然是对象,但可以当数组用。

类数组的优点:把数组和对象的好处拼到一起。不过并不是所有的数组方法都能用,除非你自己添加。

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; // 修改数组的length
return this.length;
}

求下列输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 内部解析:obj[obj.length] = 'c' ->  obj[2] = 'c'

var obj = {
'2': 'a',
'3': 'b',
'length': 2,
'push': Array.prototype.push,
}
obj.push('c');
obj.push('d');

console.log(obj);

// Output:
// { '2': 'c', '3': 'd', length: 4, push: [Function: push] }

类数组转换为数组

最原生的方法:使用一个新的数组。

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 方法,会返回一个数组。

  • slice 方法的内部实现:
1
2
3
4
5
6
7
8
9
10
11
// slice(从该位开始截取,截取到该位之前)

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)); // ["tuan", "dou", "piao"]
// 改变 this 指向到这个类数组
// slice 两个参数都省略,代表全部截取
// 截取某部分 -> xx.call(arrLike, start, end);

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); // ['a','b','c']

补充:数组迭代方法

数组迭代方法对每个数组项进行操作,不会改变原数组。

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); // 0: tuan 1: dou 2: piao
});

内部实现原理:

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);
});
// 报错,非法中断声明 Uncaught SyntaxError: Illegal break statement

return 语句也只是终止循环中那一次 fn 函数,无法终止整个 for 循环。

1
2
3
4
5
6
7
// 循环到 name = 'dou' 的成员时,它的 fn 函数被终止
familyArr.forEach(function(elem, index){
if(elem.name == 'dou') {
return;
}
console.log(index); // 0 2
});

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); // [{name: "dou", age: 2}]

内部实现原理:

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);

// newArr = [
// {name: "tuan", character: "狗鸡"},
// {name: "dou", character: "小可爱"},
// {name: "piao", character: "狗鸡"}
// ]

内部实现原理:

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); // 5 1 2 3
return index;
});
// preValue index
// 5 1
// 1 2
// 2 3
// 3 4

也可以传一个初始值 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); // 10 0 1 2 3
return index;
}, 10);
// preValue index
// 10 0
// 0 1
// 1 2
// 2 3
// 3 4

常用于累加器。

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); // 25

// preValue elem return
// 5 3 8
// 8 7 15
// 15 1 16
// 16 9 25

内部实现原理:

  • 先判断 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); // 9 3 2 1
return index;
});

// preValue index elem
// 9 3 1
// 3 2 7
// 2 1 3
// 1 0 5

initialValue 有值时:

1
2
3
4
5
var arr = [5, 3, 7, 1, 9];
arr.reduceRight(function (preValue, elem, index) {
console.log(preValue); // 10 4 3 2 1
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) { // 单独输出 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()); // [ 1, 3, 4, 5, 'dou' ]

console.log(oneArr); // [ 1, 1, 3, 4, 5, 5, 5, 3, 'dou', 'dou' ]

注意:这个对象里的属性值任意,但不能是转化为 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()); // [ 1, 0, 0, 0, 5, 3, 'dou' ]

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
// 因为 temp[this[i]] = this[i],而数组内又有 0
// 也就是在遍历过程中,this[i] 会等于 0
// 然后它作为属性值赋给了对象:temp[0] = 0
// 再考察下一个 0 时,if 条件中的 temp[0] = 0,再取反就为 true
// 会再走一次 if 语句,无法将它排除出去

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。所以在一个事件中筛选完名字后接着筛选物种,只是改变了数组,监听的值早就传了。