一边学习前端,一边通过博客的形式自己总结一些东西,当然也希望帮助一些和我一样开始学前端的小伙伴。
如果出现错误,请在评论中指出,我也好自己纠正自己的错误
author: thomaszhou
- 1. 判断是否是数组类型
- 2. 复制数组(slice和concat)
- 3. 类数组(NodeList)转数组(Array),函数参数arguments转数组
- 4. 重复n个字符
- 5. 创建 N x N 二维矩阵,并初始化数据
- 6. 数组去重、合并数组去重
数组的内置方法应用
判断是否是数组类型
很多时候我们需要对JavaScript中数据类型( Function、String、Number、Undefined、Boolean和Object
)做判断。在JavaScript中提供了typeof
操作符可以对这些常用的数据类型做判断。但要使用typeof
来判断数据是不是一个数组,就不起作用了
console.log(typeof function () {return;}); // function
console.log(typeof "a"); // string
console.log(typeof 123); // number
console.log(typeof a); //undefined
console.log(typeof true); // boolean
console.log(typeof NaN); // number
console.log(typeof !NaN); //boolean
console.log(typeof {name:"leo",age: "37"}); // object
console.log(typeof ["leo","37"]); // object
console.log(typeof null); // object
复制代码
es5的isArray()函数
var arr = [1,2,3,45];
function isArray(obj){
return Array.isArray(obj);
}
alert(isArray(arr));
复制代码
ECMAScript 5 还有着非常大的兼容性,所以我们并不能完美的使用原生判断,当使用ie6/7/8的时候就会出现问题。
construct和instanceof
constructor
属性返回对创建此对象的函数的引用,使用此属性可以检测数组类型- 除了使用constructor自身属性之外,还可以使用
instanceof
。 instanceof用来判断某个构造函数的prototype
是否存在于被检测对象的原型链里。也就是判断前面的对象是否是后者对象的类或者实例。
var arr = [1,2,3,45];
console.log(arr.constructor === Array);
console.log(arr instanceof Array);
复制代码
- 缺点:需要注意的是,当在不同的window或iframe里构造的数组时会失败。这是因为每一个iframe都有它自己的执行环境,彼此之间并不共享原型链,所以此时的判断一个对象是否为数组就会失败。此时我们有一个更好的方式去判断一个对象是否为数组。 当你在多个frame中回来跳的时候,这两种方法就惨了。由于每一个frame都有自己的一套执行环境,跨frame实例化的对象彼此并不共享原型链,通过instanceof操作符和constructor属性检测的方法自然会失败。
Object.prototype.toString
var is_array = function(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
};
alert(is_array(arr));
复制代码
使用Object.prototype.toString
方法来检测对象类型。toString将返回[object type]
type
为对象类型。下面是对象检测显示的结果。
var toString = Object.prototype.toString;
console.log(toString.call(dd));//[object Function]
console.log(toString.call(new Object));//[object Object]
console.log(toString.call(new Array));//[object Array]
console.log(toString.call(new Date));//[object Date]
console.log(toString.call(new String));//[object String]
console.log(toString.call(Math));//[object Math]
console.log(toString.call(undefined));//[object Undefined]
console.log(toString.call(null));//[object Null]
复制代码
因此我们可以利用对象的Object.prototype.toString方法检测对象是否为数组。在frame下也通过。
-
最佳检测 -Array和Object.prototype.toString结合
最佳检测方法就是,不管原生isArray是否可用,都回归到object.prototype.toString的检测上。
Object.prototype.toString
的行为:首先,取得对象的一个内部属性[[Class]]
,然后依据这个属性,返回一个类似于"[object Array]"的字符串作为结果(看过ECMA标准的应该都知道,[[]]
用来表示语言内部用到的、外部不可直接访问的属性,称为“内部属性”)。利用这 个方法,再配合call,我们可以取得任何对象的内部属性[[Class]],然后把类型检测转化为字符串比较,以达到我们的目的。call
改变toString的this引用为待检测的对象,返回此对象的字符串表示,然后对比此字符串是否是[object Array],以判断其是否是Array的实例。为什么不直接o.toString()?嗯,虽然Array继承自Object,也会有toString方法,但是这个方法有可能会被改写而达不到我们的要求,而Object.prototype则是老虎的屁股,很少有人敢去碰它的,所以能一定程度保证其“纯洁性”:)
JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个:Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String。
var isArray = function () {
if (Array.isArray) {
return Array.isArray;
}
//下面语法不明白
var objectToStringFn = Object.prototype.toString, arrayToStringResult = objectToStringFn.call([]);
return function (subject) {
return objectToStringFn.call(subject) === arrayToStringResult;
};
}();
alert(isArray(arr));// true
复制代码
复制数组
我们都知道数组是引用类型数据。这里使用slice,map复制一个数组,原数组不受影响。
let list1 = [1, 2, 3, 4];
let newList = list1.slice();//推荐slice
list1.push(5); // [1,2,3,4,5]
console.log(newList); //[1,2,3,4]
console.log(list1); //[1, 2, 3, 4, 5]
let list2 = [5,6,7,8];
let newList2 = list2.concat();
newList2.push(9); //
console.log(newList2); //[5, 6, 7, 8, 9]
console.log(list2); //[1, 2, 3, 4, 5]
复制代码
转换成数组
类数组(NodeList)转数组(Array),函数参数转数组
-
通过三种方式实现:如slice,这里加多数组的from方法,ES6语法糖
getElementsByClassName获取的dom元素的数据类型为HTMLCollection,虽然类似于数组可以遍历,但是该数据类型不存在数组中用于操作数组的方法,通过y以下方法转换后,就可以使用数组的内置方法
//返回的不是真正的Array(你无法使用filter、map、reduce等方法)
const nodeList = document.querySelectorAll('div');
// 方法1: 使用Array.from
const arrayList1 = Array.from(nodeList);
// 方法2: 使用slice
const arrayList2 = Array.prototype.slice.call(nodeList);
// 方法3: 使用ES6语法糖
const arrayList3 = [...nodeList];
复制代码
-
函数参数 转成 数组
函数参数转数组 将函数参数转数组,利用arguments伪数组形式,再用slice拷贝为新数组。
function argsParam() {
//arguments伪数组形式,再用slice拷贝为新数组
return Array.prototype.slice.call(arguments);
}
console.log(argsParam(1,2,3,4)); //[1, 2, 3, 4]
复制代码
- 那Array.Prototype.slice的内部实现是什么?
Array.prototype.slice = function(start,end){
var result = new Array();
start = start || 0;
end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
for(var i = start; i < end; i++){
result.push(this[i]);
}
return result;
}
复制代码
- 其他:Array.prototype.slice.call能将具有length属性的对象转成数组,除了IE下的节点集合(因为ie下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
var a = {length:2, 0:'zero', 1: 'first'};
console.log(Array.prototype.slice.call(a)); // ["zero", "first"]
var b = {length:3, 0:'zero', 1: 'first', 2 : 'zero'};
console.log(Array.prototype.slice.call(b)); // ["zero", "first", "zero"]
var c = {length:3, 2 : 'zero'};
console.log(Array.prototype.slice.call(c)); // [empty, empty , "zero"]
复制代码
重复n个字符
- 利用Array构造函数传参,再使用join函数分隔指定的字符串
/**
@params
num: 重复次数
str: 重复字符串
**/
function repeatStr(num, str) {
return new Array(num+1).join(str);
}
console.log(repeatStr(5, 's'));//sssss
复制代码
创建n乘n二位矩阵
创建n乘n二维矩阵,并初始化数据 使用Array对象传入数组length参数,调用fill再用map循环fill替换对应的值返回一个新数组
/**
@params
num: 矩阵次数
str: 矩阵数组中的值,由于fill函数替换所以值都是一致的
**/
function arrayMatrix(num, matrixStr) {
return Array(num).fill(null).map(() => Array(num).fill(matrixStr));
}
// ["a", "a", "a", "a"] ["a", "a", "a", "a"] ["a", "a", "a", "a"] ["a", "a", "a", "a"]
console.log(arrayMatrix(4, 'a'));
复制代码
数组去重
!!! 方法12345对于数字1和字符串1这类的问题,是无法去重的,但是方法6可以解决这个问题,但是也会导致需要设置最后是留下字符串的1还是数字1
- 法一:双层for循环(不用新的内存空间-但是时间复杂度很高)
- 第一层遍历数组,第二层用第一层得到的值来和后面所有的值依次比较,相同则用splice()方法从数组中提除该重复元素。
- 法二:遍历数组法(不用新的内存空间-但是时间复杂度很高)
- 实现思路:新建一新数组,遍历传入数组,遍历的值如果和新数组内的元素不重复,就加入该新数组中;注意:判断值是否在数组的方法“indexOf”是ECMAScript5 方法,以下不支持,需多写一些兼容低版本浏览器代码,
- indexOf就是找到新数组的第一个某值,方便查找是否重复
function unique1(array){
var n = []; //一个新的临时数组
//遍历当前数组
for(var i = 0; i < array.length; i++){
//如果当前数组的第i已经保存进了临时数组,那么跳过,
//否则把当前项push到临时数组里面
if (n.indexOf(array[i]) == -1)
n.push(array[i]);
}
return n;
}
复制代码
通过unique1.apply(arr)或unique1.call(arr)调用,为什么?
- 法三:数组下标判断法
- 还是得调用“indexOf”性能跟遍历数组法差不多,实现思路:如果当前数组的第i项在当前数组中第一次出现的位置不是i,那么表示第i项是重复的,忽略掉。否则存入结果数组
unction unique3(arr){
var n = [];
n.push(arr[0]);
len = arr.length;
//从第二项开始遍历
for(var i = 1; i < len; i++) {
//如果当前数组的第i项在当前数组中第一次出现的位置不是i,
//那么表示第i项是重复的,忽略掉。否则存入结果数组
if (arr.indexOf(arr[i]) == i) {
n.push(arr[i]);
}
}
return n;
}
console.log(unique3(arr));
复制代码
- 方法四:排序后相邻去除法
- 虽然原生数组的”sort”方法排序结果不怎么靠谱,但在不注重顺序的去重里该缺点毫无影响。实现思路:给传入数组排序,排序后相同值相邻,然后遍历时新数组只加入不与前一值重复的值。
function unique4(array){
array.sort();
var re=[array[0]];
for(var i = 1; i < array.length; i++){
if( array[i] !== re[re.length-1]){
re.push(array[i]);
}
}
return re;
}
复制代码
- 法五:对象键值对法(最优):思路如下
- 创建一个JavaScript对象以及新数组
- 使用for循环遍历原数组,每次取出一个元素与JavaScript对象的键做对比
- 如果不包含,将存入对象的元素的值推入到结果数组中,并且将存入object对象中该属性名的值设置为1
// use 'strict';
var arr = ['1',1,2,3,4,4,45,65,'b','a','b'];
function unique4 (arr) {
var newArr = [],// 构建一个新数组存放结果
object = {};
len = arr.length;
// for循环时,每次取出一个元素与对象进行对比
// 如果这个元素不重复,则将它存放到结果数中
// 同时把这个元素的内容作为对象的一个属性,并赋值为1,
// 存入到第2步建立的对象中
for (var i = 0; i < len; i++) {
// 检测在object对象中是否包含遍历到的元素的值
if (!object[typeof arr[i] + arr[i]]) {
// 如果不包含,将存入对象的元素的值推入到结果数组中
object[typeof arr[i] + arr[i]] = 1
//存入object对象中该属性名的值设置为1
newArr.push(arr[i]);
}
}
// console.log(object);
return newArr;
}
console.log(unique4(arr));
复制代码
test:
var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique3(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]
复制代码
不同的键可能会被误认为一样;例如: a[1]、a["1"] 。这种方法所费时间:621ms。这种方法所费时间是最短,但就是占用内存大一些
- 法六:不区分字符串和数字的去重(对象键值法)
- 对象的key,在存入的时候,就自动转化成string类型,所以像前四种方法针对1和'1'是无法区分,但是如果将值放进对象的key中,1和'1'的比较就会编程'1'和'1'的比较,也就是i 字符串间的比较
function unique4 (arr) {
var newArr = [],
object = {};
len = arr.length;
for (var i = 0; i < len; i++) {
if (!object[arr[i]]) {
object[arr[i]] = 1;
newArr.push(arr[i]);
}
}
console.log(object);
return newArr;
}
console.log(unique4(arr));
复制代码
-
ES6去重
注意set返回的也是一个不重复的类数组形式要使用Array.from或者别的方法方法转成数组形式
function unique (arr) {
return Array.from(new Set(arr))
}
或者:
const unique = arr => [...new Set(arr)];
复制代码
Array union (合并数组去重)
用 a 和 b 的所有值创建一个 Set 并转换成一个数组。
const union = (a, b) => Array.from(new Set([...a, ...b]));
// union([1,2,3], [4,3,2]) -> [1,2,3,4]
复制代码
过滤数组中的非唯一值
- 将Array.filter()用于仅包含唯一值的数组。
- 思路:indexOf和lastIndexOf找的位置相同,那就是唯一的值
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
// console.log(filterNonUnique([1,2,2,3,4,4,5])); // [1,3,5]
复制代码