07 | 数组原理(上):帮你梳理眼花缭乱的数组 API
数组概念的探究
截至 ES7 规范,数组共包含 33 个标准的 API 方法和一个非标准的 API 方法
Array 的构造器
Array 构造器用于创建一个新的数组
// 1. 对象字面量let arr1 = [];// 使用对象字面量var b = [];b.length = 6; // [empty × 6]// 2. 使用 Array 构造器,可以自定义长度var a = Array(6); // [empty × 6]
Array 构造器根据参数长度的不同,有如下两种不同的处理方式:
new Array(arg1, arg2,…)- 参数长度为 0 或长度大于等于 2 时,传入的参数将按照顺序依次成为新数组的第 0 至第 N 项(参数长度为0 时,返回空数组)
new Array(len)
Array.of(8.0, 5); // [8, 5] Array(8.0, 5); // [8, 5]
Array.of(‘8’); // [“8”] Array(‘8’); // [“8”]
<a name="FndNu"></a>### Array.fromArray.from 的设计初衷是快速便捷地基于其他对象创建新数组,准确来说就是从一个类似数组的可迭代对象中创建一个新的数组实例。其实就是,只要一个对象有迭代器,Array.from 就能把它变成一个数组(注意:是返回新的数组,不改变原对象)Array.from 拥有 3 个参数:1. 类似数组的对象,必选;<br />2. 加工函数,新生成的数组会经过该函数的加工再返回;<br />3. this 作用域,表示加工函数执行时 this 的值```javascriptvar obj = {0: 'a', 1: 'b', 2:'c', length: 3};Array.from(obj, function(value, index){console.log(value, index, this, arguments.length);return value.repeat(3); //必须指定返回值,否则返回 undefined}, obj);// ["aaa","bbb","ccc"]
除了objectm, 有迭代器的对象还包括 String、Set、Map 等
// StringArray.from('abc'); // ["a", "b", "c"]// SetArray.from(new Set(['abc', 'def'])); // ["abc", "def"]// MapArray.from(new Map([[1, 'ab'], [2, 'de']]));// [[1, 'ab'], [2, 'de']]
Array 的判断
var a = [];// 1.基于instanceofa instanceof Array;// 2.基于constructora.constructor === Array;// 3.基于Object.prototype.isPrototypeOfArray.prototype.isPrototypeOf(a);// 4.基于getPrototypeOfObject.getPrototypeOf(a) === Array.prototype;// 5.基于Object.prototype.toStringObject.prototype.toString.apply(a) === '[object Array]';// 6.ES6 新增的方法 Array.isArray 如果兼容性有问题,其实也是用的方法5Array.isArray(a)
数组API - 改变自身的方法
基于 ES6,会改变自身值的方法一共有 9 个,分别为 pop、push、reverse、shift、sort、splice、unshift,以及两个 ES6 新增的方法 copyWithin 和 fill。
// pop方法var array = ["cat", "dog", "cow", "chicken", "mouse"];var item = array.pop();console.log(array); // ["cat", "dog", "cow", "chicken"]console.log(item); // mouse// push方法var array = ["football", "basketball", "badminton"];var i = array.push("golfball");console.log(array);// ["football", "basketball", "badminton", "golfball"]console.log(i); // 4// reverse方法var array = [1,2,3,4,5];var array2 = array.reverse();console.log(array); // [5,4,3,2,1]console.log(array2===array); // true// shift方法var array = [1,2,3,4,5];var item = array.shift();console.log(array); // [2,3,4,5]console.log(item); // 1// unshift方法var array = ["red", "green", "blue"];var length = array.unshift("yellow");console.log(array); // ["yellow", "red", "green", "blue"]console.log(length); // 4// sort方法var array = ["apple","Boy","Cat","dog"];var array2 = array.sort();console.log(array); // ["Boy", "Cat", "apple", "dog"]console.log(array2 == array); // true// splice方法var array = ["apple","boy"];var splices = array.splice(1,1);console.log(array); // ["apple"]console.log(splices); // ["boy"]// copyWithin方法// array.copyWithin(target, start, end)// target 必需。复制到指定目标索引位置。// start 可选。元素复制的起始位置。// end 可选。停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数。var array = [1,2,3,4,5];var array2 = array.copyWithin(0,3);console.log(array===array2,array2); // true [4, 5, 3, 4, 5]// fill方法var array = [1,2,3,4,5];var array2 = array.fill(10,0,3);console.log(array===array2,array2); // true [10, 10, 10, 4, 5], 可见数组区间[0,3]的元素全部替换为10
数组API - 不改变自身的方法
基于 ES7,不会改变自身的方法也有 9 个,分别为 concat、join、slice、toString、toLocaleString、indexOf、lastIndexOf、未形成标准的 toSource,以及 ES7 新增的方法 includes。
// concat方法var array = [1, 2, 3];var array2 = array.concat(4,[5,6],[7,8,9]);console.log(array2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]console.log(array); // [1, 2, 3], 可见原数组并未被修改// join方法var array = ['We', 'are', 'Chinese'];console.log(array.join()); // "We,are,Chinese"console.log(array.join('+')); // "We+are+Chinese"// slice方法var array = ["one", "two", "three","four", "five"];console.log(array.slice()); // ["one", "two", "three","four", "five"]console.log(array.slice(2,3)); // ["three"]// toString方法var array = ['Jan', 'Feb', 'Mar', 'Apr'];var str = array.toString();console.log(str); // Jan,Feb,Mar,Apr// tolocalString方法var array= [{name:'zz'}, 123, "abc", new Date()];var str = array.toLocaleString();console.log(str); // [object Object],123,abc,2016/1/5 下午1:06:23// indexOf方法var array = ['abc', 'def', 'ghi','123'];console.log(array.indexOf('def')); // 1// includes方法var array = [-0, 1, 2];console.log(array.includes(+0)); // trueconsole.log(array.includes(1)); // truevar array = [NaN];console.log(array.includes(NaN)); // true
数组API - 数组遍历
基于 ES6,不会改变自身的遍历方法一共有 12 个,分别为 forEach、every、some、filter、map、reduce、reduceRight,以及 ES6 新增的方法 entries、find、findIndex、keys、values。
// forEach方法var array = [1, 3, 5];var obj = {name:'cc'};var sReturn = array.forEach(function(value, index, array){array[index] = value;console.log(this.name); // cc被打印了三次, this指向obj},obj);console.log(array); // [1, 3, 5]console.log(sReturn); // undefined, 可见返回值为undefined// every方法var o = {0:10, 1:8, 2:25, length:3};var bool = Array.prototype.every.call(o,function(value, index, obj){return value >= 8;},o);console.log(bool); // true// some方法var array = [18, 9, 10, 35, 80];var isExist = array.some(function(value, index, array){return value > 20;});console.log(isExist); // true// map 方法var array = [18, 9, 10, 35, 80];array.map(item => item + 1);console.log(array); // [19, 10, 11, 36, 81]// filter 方法var array = [18, 9, 10, 35, 80];var array2 = array.filter(function(value, index, array){return value > 20;});console.log(array2); // [35, 80]// reduce方法var array = [1, 2, 3, 4];var s = array.reduce(function(previousValue, value, index, array){return previousValue * value;},1);console.log(s); // 24// ES6写法更加简洁array.reduce((p, v) => p * v); // 24// reduceRight方法 (和reduce的区别就是从后往前累计)var array = [1, 2, 3, 4];array.reduceRight((p, v) => p * v); // 24// entries方法var array = ["a", "b", "c"];var iterator = array.entries();console.log(iterator.next().value); // [0, "a"]console.log(iterator.next().value); // [1, "b"]console.log(iterator.next().value); // [2, "c"]console.log(iterator.next().value); // undefined, 迭代器处于数组末尾时, 再迭代就会返回undefined// find & findIndex方法var array = [1, 3, 5, 7, 8, 9, 10];function f(value, index, array){return value%2==0; // 返回偶数}function f2(value, index, array){return value > 20; // 返回大于20的数}console.log(array.find(f)); // 8console.log(array.find(f2)); // undefinedconsole.log(array.findIndex(f)); // 4console.log(array.findIndex(f2)); // -1// keys方法[...Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][...new Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]// values方法var array = ["abc", "xyz"];var iterator = array.values();console.log(iterator.next().value);//abcconsole.log(iterator.next().value);//xyz
reducearray.reduce(function(prevVal, curVal, curIndex, array), initialValue)
- callback(一个在数组的每一项中调用的函数,接受四个参数):
- previousValue(上一次调用回调函数时的返回值,或者初始值)
- currentValue(当前正在处理的数组元素)
- currentIndex(当前正在处理的数组元素下标)
- array(调用 reduce() 方法的数组)
- previousValue(上一次调用回调函数时的返回值,或者初始值)
- initialValue(可选的初始值,作为第一次调用回调函数时传给 previousValue 的值)
题目: var arr = [ {name: ‘brick1’}, {name: ‘brick2’}, {name: ‘brick3’} ]
希望最后返回到 arr 里面每个对象的 name 拼接数据为 ‘brick1, brick2 & brick3’ ,如果用 reduce 如何实现呢?
function getName(arr){return arr.reduce(function(prevVal, curVal, curIndex, arr){// curIndex === 0 return curVal// curIndex === arr.length-1 return prevVal + & curVal// return prevVal + , + curValif(curIndex === 0){return curVal.name;}else if(curIndex === arr.length-1){return prevVal + '&' + curVal.name}else{return prevVal + ',' + curVal.name}}, '')}let arr = [ {name: 'brick1'}, {name: 'brick2'}, {name: 'brick3'} ]getName(arr) // 'brick1, brick2 & brick3'

以上,数组的各方法基本讲解完毕,这些方法之间存在很多共性,如下:
- 所有插入元素的方法,比如 push、unshift 一律返回数组新的长度;
- 所有删除元素的方法,比如 pop、shift、splice 一律返回删除的元素,或者返回删除的多个元素组成的数组;
- 部分遍历方法,比如 forEach、every、some、filter、map、find、findIndex,它们都包含 function(value,index,array){} 和 thisArg 这样两个形参
08 | 数组原理(中):如何理解 JS 的类数组?
JavaScript 中有哪些情况下的对象是类数组
- 函数里面的参数对象 arguments;
- 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection;
-
类数组基本介绍
arguments
函数参数列表
function foo(name, age, sex) {console.log(arguments);console.log(typeof arguments); // objectconsole.log(Object.prototype.toString.call(arguments)); // [object Arguments]}foo('jack', '18', 'male');
HTMLCollection
HTMLCollection 简单来说是 HTML DOM 对象的一个接口,这个接口包含了获取到的 DOM 元素集合,返回的类型是类数组对象,如果用 typeof 来判断的话,它返回的是 ‘object’。它是及时更新的,当文档中的 DOM 变化时,它也会随之变化。
var elem1, elem2;// document.forms 是一个 HTMLCollectionelem1 = document.forms[0];elem2 = document.forms.item(0);console.log(elem1);console.log(elem2);console.log(typeof elem1);console.log(Object.prototype.toString.call(elem1));
NodeList
NodeList 对象是节点的集合,通常是由 querySelector 返回的。NodeList 不是一个数组,也是一种类数组。虽然 NodeList 不是一个数组,但是可以使用 for…of 来迭代。在一些情况下,NodeList 是一个实时集合,也就是说,如果文档中的节点树发生变化,NodeList 也会随之变化。
var list = document.querySelectorAll('input[type=checkbox]');for (var checkbox of list) {checkbox.checked = true;}console.log(list);console.log(typeof list);console.log(Object.prototype.toString.call(list));
类数组应用场景
遍历参数操作
function add() {var sum =0,len = arguments.length;for(var i = 0; i < len; i++){sum += arguments[i];}return sum;}add() // 0add(1) // 1add(1,2) // 3add(1,2,3,4); // 10
定义链接字符串函数
function myConcat(separa) {var args = Array.prototype.slice.call(arguments, 1); // 类数组转化为数组,并从1截取到数组末尾 ["red", "orange", "blue"]return args.join(separa);}myConcat(", ", "red", "orange", "blue");// "red, orange, blue"myConcat("; ", "elephant", "lion", "snake");// "elephant; lion; snake"myConcat(". ", "one", "two", "three", "four", "five");// "one. two. three. four. five"
传递参数使用
// 使用 apply 将 foo 的参数传递给 barfunction foo() {bar.apply(this, arguments); // apply 接受参数数组 call 接受参数列表}function bar(a, b, c) {console.log(a, b, c);}foo(1, 2, 3) //1 2 3
如何将类数组转换成数组
类数组借用数组方法转数组
Array.prototype.push.call(obj, a, b)Array.prototype.concat.apply([], 参数数组)[].slice.call(arguments)var arrayLike = {0: 'java',1: 'script',length: 2}Array.prototype.push.call(arrayLike, 'jack', 'lily');console.log(typeof arrayLike); // 'object'console.log(arrayLike);// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
function sum(a, b) {let args = Array.prototype.slice.call(arguments);// let args = [].slice.call(arguments); // 这样写也是一样效果console.log(args.reduce((sum, cur) => sum + cur));}sum(1, 2); // 3function sum(a, b) {let args = Array.prototype.concat.apply([], arguments);console.log(args.reduce((sum, cur) => sum + cur));}sum(1, 2); // 3
ES6 的方法转数组
Array.from({类数组对象})[...arguments]...argsfunction sum(a, b) {let args = Array.from(arguments);console.log(args.reduce((sum, cur) => sum + cur));}sum(1, 2); // 3function sum(a, b) {let args = [...arguments];console.log(args.reduce((sum, cur) => sum + cur));}sum(1, 2); // 3function sum(...args) {console.log(args.reduce((sum, cur) => sum + cur));}sum(1, 2); // 3
总结
09 | 数组原理(下):实现数组扁平化的 6 种方式
数组扁平化
将多层嵌套数组拍扁var arr = [1, [2, [3, 4,5]]];console.log(flatten(arr)); // [1, 2, 3, 4,5]
扁平化的实现
方法一:普通的递归实
var arr = [1, [2, [3, 4, 5]]];function flatten(arr){let res = [];for(let i=0;i<arr.length;i++){if(Array.isArray(arr[i])){res = res.concat(flatten(arr[i]))}else{res.push(arr[i]);}}return res;}console.log(flatten(arr))
方法二:利用 reduce 函数迭代
function flatten2(arr){return arr.reduce(function(prev, cur){return prev.concat(Array.isArray(cur) ? flatten2(cur) : cur)}, [])}console.log(flatten2(arr))
方法三:扩展运算符实现
// 方法3var arr = [1, [2, [3, 4]]];function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);}return arr;}console.log(flatten(arr)); // [1, 2, 3, 4,5]
方法四:split 和 toString 共同处理
转化为字符串后,用特定的分隔符转化为数组
var arr = [1, [2, [3, 4]]];function flatten(arr) {return arr.toString().split(',');}console.log(flatten(arr)); // [1, 2, 3, 4,5]
方法五:调用 ES6 中的 flat
arr.flat([depth])
// 方法5var arr = [1, [2, [3, 4]]];function flatten(arr) {return arr.flat(Infinity);}console.log(flatten(arr)); // [1, 2, 3, 4,5]
方法六:正则和 JSON 方法共同处理
// 方法 6let arr = [1, [2, [3, [4, 5]]], 6];function flatten(arr) {let str = JSON.stringify(arr);str = str.replace(/(\[|\])/g, '');str = '[' + str + ']';return JSON.parse(str);}console.log(flatten(arr)); // [1, 2, 3, 4,5]
总结
10 | 数组排序(上):如何用 JS 实现各种数组排序?
各种排序的 JS 实现
归并排序
时间复杂度
O(nlogn)
思想
- 分解子问题:将需要被排序的数组从中间分割为两半,然后再将分割出来的每个子数组各分割为两半,重复以上操作,直到单个子数组只有一个元素为止。
- 求解每个子问题:从粒度最小的子数组开始,两两合并、确保每次合并出来的数组都是有序的。(这里的“子问题”指的就是对每个子数组进行排序)。
- 合并子问题的解,得出大问题的解:当数组被合并至原有的规模时,就得到了一个完全排序的数组
/*** 分解:将数组一分为二, 直到拆分为单个元素组成的数组为止* 合并:将最小的数组排序并合并(两个单元素合并为一个双元素数组 两个双合并为一个四元素)*/function mergeSort(arr) {let len = arr.length;// 递归边界if (len <= 1) {return arr;}// 以数组中心为基准点进行拆分let mid = Math.floor(len / 2);// 递归查分左侧let left = mergeSort(arr.slice(0, mid));// 递归拆分右侧let right = mergeSort(arr.slice(mid, len));// 合并并排序两个数组arr = sortArrFn(left, right);// console.log("sortArr", arr);return arr;}/*** 合并排序两个数组* @param {*} arr1* @param {*} arr2*/function sortArrFn(arr1, arr2) {// arr1.sort(); // 此处不需要对数组排序,因为进入的最小元素的数组本身有序// arr2.sort();let i = 0,j = 0;let len1 = arr1.length,len2 = arr2.length;let res = [];while (i < len1 && j < len2) {if (arr1[i] <= arr2[j]) {res.push(arr1[i]);i++;} else {res.push(arr2[j]);j++;}}if (i < len1) {return arr1.slice(i, len1);} else {return res.concat(arr2.slice(j, len2));}}// console.log(sortArrFn([3, 1, 2], [5, 6, 4]));console.log(mergeSort([1, 3, 4, 5, 6, 2, 9, 2]));
快速排序
时间复杂度
O(nlogn)
思想
将原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组
