前端——正儿八经的数组处理实战向(内含apply、call、bind实操)(八)

写在开头:无论前端、后端、还是运维,作为一名较为合格的程序员,我们的最最最基本的目的就是要将我们所写的代码,转换为需求场景下的不同的功能,这样才能够实现代码基本价值的体现,当然了代码的可读性、可维护性等等同样相当重要的。本篇将是JS的简单业务逻辑实战篇,内容即处理数据向。

apply、call、bind

虽然在前端第六篇讲到this的时候,是有较为基础的提过一下this的三基友(apply、call、bind),但是当时只是浅尝辄止主要讲的是作用域方面的事情,而在实际应用场景当中,我们还需要更加深度的去剖析这三者,毕竟他们在复杂数据类型的处理上面地位还是很高的。

三者的基本概念,以及他们之间的区别

在深度解析apply、call、bind之前,我们再回顾一下三者之间的区别,其实在第六篇时已经有过总结了,再简单的重复一遍即可。

  • apply:仅为函数(个人不太建议在显式构造函数中使用)当中的特有属性方法,即Function.prototype.apply()。apply()方法在MDN的介绍当中是包含有两个参数的且两个参数皆为可选,func.apply(thisArg, [argsArray])。第一个参数为一个对象(有时也会是this对象),第二个参数为一个数组或类数组(含有length属性即类数组),且该数组为func函数的传递参数。但是个人建议在使用apply()方法时,都至少要把第一个对象参数带上,且在typescript当中的语法解析,第一个参数也是必选项。
  • call:同样的仅为函数当中的特有属性,他们的区别基本区别可能就在于call()方法,func.call(thisArg,...[argsArray]),第二个参数即为一个参数列表,这里我们扩展运算符来表示第二个参数。
  • bind:与之前两个的区别更加的明显,当然由于后边才出现的缘故,功能较apply与call而言也更加的灵活。其最主要的区别有两点。1.作为函数上面的方法,apply、call都是立即执行的,而bind则会返回一个函数,我们需要手动执行。这样的好处是在某些需要回调或者其他需求下,我们可以控制执行顺序。2.func.bind(thisArg,o),我们暂时就这样就用o来表示这个有些奇特的参数,之所以说该参数奇特,也是因为其结合了apply与call方法中第二个参数的特点,o既可以是一个数组,也可以是一个参数列表。

下面我们用几个简单的?来再次加深印象:

var demo = {
  max: function(...str) {
    if (Array.isArray(this)) {
      // 我们首先判断该对象是否为一个数组,Array.isArray几乎不存在什么兼容性问题
      // 唯一注意的就是IE9+吧。
      str.push(...this);
      // 通过扩展运算符,将数组展开成参数列表,分别“压进”str参数数组当中
    }
    if (str.length === 0) {
      return "请输入一个非空数组";
    } else {
      let max = str.reduce((prev, next) => {
        return Math.max(prev, next);
      });
      // 通过Array.prototype.reduce方法,在内部结合Math.max方法,我们即可模拟出数组取最大值的需求
      return max;
      // 最后完成了一个极为简单基础的MDN上面关于取数组中最大值方法的模拟改进版
    }
  }
};
const arr = [5, 6, 2, 3, 7];
const max = Math.max.apply([8], arr);
console.log(max);
// MDN上的?中的第一个参数只是起到了占位符的作用,没有其他的影响,打印结果为7
const max1 = demo.max.apply(null, arr);
const max2 = demo.max.call([8, 9, 2], ...arr);
const max3 = demo.max.bind(12, ...arr)();
console.log(`${max1}===${max2}===${max3}`);
// 而在本?当中,第一个参数如果为非数组参数,则不影响取最大值的结果,而如果为一个数组,则可能影响到其最大值
// 打印结果为:7===9===7,当然本?中运用了大量关于ES6的内容,后面的代码为了其贴合实际应用场景都会如此
复制代码

好了,讲完了this的三个基友,我们正式开始进行一些简单且常用的数据处理操作。

关于数组的数据操作

数组中的取值问题

1.1 数组的赋值问题

const arr1 = [1, 2, 3, 4];
let arr2 = arr1;
console.log(arr1===arr2);
// 因为是赋值操作,两者在JS引擎中默认是全等的,打印值为true
arr2.reverse();
console.log(arr1);
// 如果是单纯的赋值操作,由于赋值操作可以理解成栈内存中指针地址赋值的情况,
// 所以在改变其赋值对象内容时,就相当于改变原数组的内容,这里说明一点,赋值连一层浅拷贝都算不上。
复制代码

1.2 数组的一层浅拷贝

// 一层浅拷贝数组
const arr3=[1,2,3,4];
let arr4=Object.assign([],arr3);
console.log(arr3===arr4);
// 此时的引用地址已经不相同了,打印值为false
arr4.reverse();
console.log(arr3);
// 并不会影响到原数组当中的内容。
复制代码

1.3 一层浅拷贝存在的问题

// 其实一般情况下,浅拷贝数组已经够用了。但是若出现数组中嵌套对象(或其他引用数据类型)等情况,就需要考虑
// 多层浅拷贝,或者一劳永逸地深拷贝情况了。否则可能就会如下边的?,仍会影响原数组内容
const arr1 = [1, 2, 3, [1, 2]];
let arr2 = Object.assign([], arr1);
arr2[3].reverse();
console.log(arr1.join()===arr2.join());
// 我们将数组转换成简单数据类型:字符串,结果发现里面的内容时相同的
// 所以说,在改变数组内的数组时,原数组对象也是会受到影响的,不相信的童鞋可以,自行打印arr1与arr2
复制代码

1.4 终极方案,数组的深拷贝(非万能)

// 如果确定需要操作的数组中包含其他需要操作的复杂数据类型
// 这个时候我们就要使用一劳永逸的深拷贝方案了,即将一个数组,完完全全从里到外的copy一遍。
// 犹如“真正”的深拷贝代码太过冗余,且考虑方面太多,如果仅实战向的话,我们可以考虑序列化与反序列化来模拟深拷贝。
// 这种方案的优势,第一:就是简单;第二:涉及范围广,只要不涉及类似循环引用与函数基本都是可以的。
const arr1=[1,2,3,[1,2]];
let arr2=JSON.parse(JSON.stringify(arr1));
arr2[3].reverse();
console.log(arr1);
// 此时打印结果,你会发现原数组并没有受到任何影响
复制代码
  1. 数组取极值(最大值或者最小值)
const arr = [11, 3, 0, 4, 13, 6];
const max = Math.max.apply(null, arr);
const min = Math.min.apply(null, arr);
console.log(max);
console.log(min);
复制代码
  1. 数组中的排序问题
const arr = [11, 3, 0, 4, 13, 6];
class SortArr {
  arr = [];
  constructor(arr) {
    this.arr = JSON.parse(JSON.stringify(arr));
    // 考虑到Array.prototype.sort()方法是会改变原数组的。
    // 所以首先先使用深拷贝,保证不会影响到原数组内的情况
  }
  // ES6中的类,定义了两个函数(方法),这两个方法并不需要进行function关键字
  orderArr() {
    this.arr.sort((a, b) => {
      return a - b;
    });
    return this.arr;
  }
  // 第一个orderArr函数,其功能为数组(数值)从小到大排序
  orderReverseArr() {
    this.arr.sort((a, b) => {
      return a - b;
    });
    return this.arr.reverse();
  }
  // 第一个orderReverseArr函数,其功能为数组(数值)从大到小排序
}
const OrderArr = new SortArr(arr);
console.log(OrderArr.orderArr());
// 数组arr将会从小到大依次打印:[0, 3, 4, 6, 11, 13]
console.log(OrderArr.orderReverseArr());
// 数组arr将会从大到小依次打印:[13, 11, 6, 4, 3, 0]
console.log(arr);
// 将会打印原数组:[11, 3, 0, 4, 13, 6]
复制代码
  1. 数组与字符串之间的转化

4.1 数组转换为字符串

// 在某些功能需求场景下,我们需要完成数组与字符串(简单数据类型)之间的转换工作。
// 数组转换成字符串
const arr=[1,2,3];
let str=arr.join();
// Array.prototype.join([separator])方法,参数为可选项,默认为‘,’,可接受空字符
console.log(str);
// 打印值为:1,2,3
复制代码

4.2 字符串转化为数组

// 字符串转换成数组
const str = "1 2 3 4";
let arr1 = str.split(" ");
console.log(arr1);
// 打印结果为:["1", "2", "3", "4"],注意:并不是[1,2,3,4]。
// 这也绝对是可以理解的,若有非number类型,想要转换成数组,必须保证里面的内容为字符串格式才能防止出错。
// 至于["1", "2", "3", "4"]到[1,2,3,4]的转换也非常简单,只需要进行条件判断,再进行字符串转数字再即可。
复制代码

4.3 延展4.2?情况

// 字符串转换成数组
const str = "1 x y 4";
let arr1 = str.split(" ");
let arr2 = [];
for (let item of arr1) {
  if (+item + "" !== "NaN") {
    // 这里利用了弱类型转换特性,以及扩展运算符的深拷贝方式
    arr2 = [...arr2, parseFloat(item)];
  } else {
    arr2 = [...arr2, item];
  }
}
// 多种数组循环模式的性能对比,for-of在一般情况下全面且高性能。
console.log(arr1);
// 打印结果:["1", "x", "y", "4"]
console.log(arr2);
// 打印结果:[1, "x", "y", 4]
复制代码
  1. 数组中的多种遍历及需求场景较优解

5.1 数组中的过滤

const arr = [1, 2, 3, 4];
let arr1 = arr.filter(item => {
  return item > 2;
});
console.log(arr1);
// 打印值:[3, 4] 未改变原数组
复制代码

5.2 数组中操作每个元素

const arr = [1, 2, 3, 4];
let arr1 = arr.map(item => {
  return item * 2;
});
console.log(arr1);
// 打印值:[2, 4, 6, 8] 未改变原数组
复制代码

5.3 数组中的累加计算

const arr = [1, 2, 3, 4];
let sum = arr.reduce((prev, next) => {
  console.log(prev);
  console.log(next);
  
  return prev + next;
});
console.log(sum);
// 打印值:10,由于其参数特性,其实Array.prototype.reduce()方法很值得挖掘
复制代码

5.4 万金油循环方式for-of

const arr1 = [1, 2, 3, 4];
let arr2 = [];
for (let item of arr1) {
  item = item + 1;
  arr2 = [...arr2, item];
}
console.log(arr2);
// 其实不仅是数组,任何含有迭代器(iterator)的对象,都可以用for-of方法进行循环遍历
// 打印结果:[2, 3, 4, 5],且算是深拷贝了,只是若有多层嵌套需要考虑多方面因素
复制代码

5.5 地位尴尬的forEach

const arr1 = [1, 2, 3, 4];
let arr2 = [];
arr1.forEach(item => {
  arr2 = [...arr2, item + 1];
});
console.log(arr2);
// 打印值:[2, 3, 4, 5],对于Array.prototype.forEach方法来说,他好像什么都能做,也挺全能的。
// 但是全能有时候也并非好事,论“专业”他不如那些map、fitter、reduce、论性能他又不如for-of,所以地位尴尬。
// 况且Array.prototype.forEach方法是不能提前跳出循环的,他在MDN的介绍都是:对数组的每个元素执行一次提供的函数。
复制代码

5.6 for-in(额,开玩笑的。for-in不建议用在数组当中,不是他不能用,而是实在没有意义。你要是花式炫技当我没说)

  1. 数组的截断问题

6.1 使用slice方法进行数组截取

const arr1 = [1, 2, 3, 4];
let arr2 = arr1.slice(-3, -1);
// Array.prototype.slice方法,开始索引值和结束索引值的参数都是可选的。且包含开始不包含结束
// 既然是可选参数,那么必然存在默认值,begin:默认值为0,end:默认值为数组最后一位索引值。
// 参数也可以取负值(当数组长度过大的情况使用),负值代表了倒数第几的含义。
console.log(arr2);
// 根据上面所述。打印值应为截取的倒数第三至倒数第一位(不包含end)的数组:[2, 3]

复制代码

6.2 使用splice进行数组截取

const arr1 = [1, 2, 3, 4];
let arr2 = JSON.parse(JSON.stringify(arr1));
// 由于Array.prototype.splice方法会改变原数组,所以我们先进行一下深拷贝。
let arr3 = arr2.splice(1, 1);
// Array.prototype.splice方法有三个可选参数,参数1:要操作的数组的索引值,参数2:删除(截取)的数组个数,参数3:添加的参数列表
// 其实由这三个参数应该就可以看出来,splice方法不仅仅局限于对数组的截取作用。而且有时改变原数组也是有需求场景的。
console.log(arr2);
// 因为会改变原数组,打印值为:[1, 3, 4]
console.log(arr3);
// 数组arr3代表了所截取的数组,打印值为:[2]
复制代码
  1. 数组使用进行增加与删除操作

7.1 数组的增加操作(不啰嗦了,一个?概括吧,这些很基础了)

const arr1 = [1, 2, 3, 4];
let arr2=JSON.parse(JSON.stringify(arr1));
// 首先说明下,对于数组的增加与删除的所有方法都是会改变原数组本身的。
arr2.push(5);
console.log(arr2); // 打印值:[1, 2, 3, 4, 5](在数组最后增加)
arr2.unshift(0);
console.log(arr2); // 打印值:[0, 1, 2, 3, 4, 5](在数组开头增加)
arr2.splice(arr2.length - 1, 0, 6);
console.log(arr2); // 打印值:[0, 1, 2, 3, 4, 5, 6](可自定义增加)
复制代码

7.2 数组的删除操作(同样一个?概括)

const arr1 = [1, 2, 3, 4];
let arr2=JSON.parse(JSON.stringify(arr1));
// 首先说明下,对于数组的增加与删除的所有方法都是会改变原数组本身的。
arr2.pop();
console.log(arr2); // 打印值:[1, 2, 3](在数组末尾删除)
arr2.shift();
console.log(arr2); // 打印值:[2, 3](在数组开头删除)
arr2.splice(0, 1);
console.log(arr2); // 打印值:[3] (自定义删除情况)
复制代码
  1. 将两个数组进行连接操作
// 同样有两个方法,至于具体使用,可以具体匹配需求场景
const arr1 = [1, 2];
const arr2 = [3, 4];
let arr3 = arr1.concat(arr2);
let arr4 = [...arr1, ...arr2];
console.log(arr3);
console.log(arr4);
// 打印值都是[1, 2, 3, 4]
复制代码
  1. 差点遗忘,但相当好用的数组的倒转(主要就一句代码)
const arr1=[1,2,3,4];
let arr2=JSON.parse(JSON.stringify(arr1));
arr2.reverse();
console.log(arr2);
// Array.prototype.reverse方法同样会改变原数组,打印值:[4, 3, 2, 1]
复制代码

对于数组操作的简单讲解就先到这里了,我们再进行一下总结,顺便说一点需要注意的问题。

  • 会改变原数组的方法:能够进行数组增删的所有方法(shift、pop、unshift、push、splice)、能够进行数组排序的方法(sort、reverse)。
  • 不会改变原数组的方法:剩下的都是不会改变原数组的方法,包括没有讲到的some、every(其用处不是很大),还有一个valueOf(我个人是不打算将他归类为数组方法的,因为他其实是对象上的方法)

说完这些,我们再来谈一谈操作数组注意事项(暂时只想到了一点)

  1. 可以多使用一些扩展运算符进行数组操作,这样可以有效的保证数组的准确性。

最后说两句:我们操作数组的时候,其实考虑较优解并不是第一要素,更重要的还是要保证其可读性与可维护性,尤其是可维护性(因为可读性大家都很容易注意到,取名规范,格式规范等等)。而保证可维护性的第一要素,我个人认为即尽量少的操作原数组,这也就是我为什么强调和看好简易版深拷贝的原因。

转载于:https://juejin.im/post/5c905ade5188252d9320754d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值