数组扩展方法(一)

news2024/11/14 22:06:55

Array.prototype.forEach

MDN解释forEach()方法是对数组的每个元素执行一个给定的函数,换句话来说就是在调用forEach()方法的时候,需要传入一个回调函数callback,循环每个数组内部元素时都会执行一次传入的回调函数callback
forEach()方法的具体语法:

arr.forEach(callback(currentValue, index, array), thisArg);

callback:
  为数组中每个元素执行的函数,该函数内部接收currentValue, index, array三个参数。

  currentValue:
    数组中正在处理的当前元素。
  
  index:
    数组中正在处理的当前元素索引值。
 
  array:
    forEach()方法正在操作的数组,例如:arr。
 
thisArg:
  可选参数。当前执行回调函数callback时,内部的this指向。

返回值:
  undefined

看过forEach()方法基本语法的后,我们再来看几个关于forEach()方法中thisArg参数的问题:
我们知道thisArg参数设置的是当执行回调函数callback时,内部this的值。如果thisArg参数有值,则每次callback被调用的时候,内部的this都会指向thisArg参数,这是毋庸置疑的。如果忽略了thisArg参数,或者用null、undefined,此时callback在执行的时候,内部this的值是全局对象window。
为什么在忽略thisArg参数,或者thisArg参数是null、undefined的时候,callback在执行的时候,内部的this的值是全局对象window呢🤔?实际上,我们需要明白callback回调函数在forEach()方法中的执行机制,forEach()方法循环每一个数组元素的时候都会调用callback回调函数,而此时callback函数都是独立调用的方式执行调用,相当于是window.callback()的方式进行调用。所以此时回调函数内部this值是全局对象😱。

// 指定thisArg参数
var arr = [1, 2, 3, 4, 5],
    newArr = [];
arr.forEach(function(elem, index, array){
   this.push(elem);
}, newArr)
console.log(arr === newArr); // false
console.log(newArr); // [1, 2, 3, 4, 5]


// 忽略thisArg参数
var arr = [1, 2, 3, 4, 5];
arr.forEach(function(elem, index, array){
   console.log(this); // window
})

// thisArg参数是null、undefined
arr.forEach(function(elem, index, array){
   console.log(this); // window
}, null)

arr.forEach(function(elem, index, array){
   console.log(this); // window
}, undefined)

// 当回调函数是箭头函数时,this 为 window
arr.forEach((elem,index,array)=>{
  console.log(this); // window
},thisArg)

forEach()方法不会对未初始化的值进行操作,也就是说forEach()方法并不会对像稀松数组中empty空元素进行遍历,并且如果是空数组的话,forEach()方法也不会对数组进行操作。但是注意一点哦,for循环是会对empty空元素进行操作的。

// 定义稀松数组
var arr = [1, 2, , , 5];

// forEach遍历
arr.forEach(function(elem, index, array) {
  console.log(elem);
});

// for循环遍历
for(var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

image.png
forEach()方法遍历数组的范围在第一次调用回调函数callback前就会被确定。调用forEach()后添加到数组中的项不会再被回调函数callback访问到。如果已经存在的值被改变,则传递给callback回调函数的值是forEach()遍历到它们那一刻的值。已删除的项不会被遍历到,也就是说如果已经访问的元素在迭代时被删除了,之后的元素将被跳过
如果已经存在的值被改变,则传递给callback回调函数的值是forEach()遍历到它们那一刻的值。这句话该如何理解呢?
也就是说,如果没有遍历到某个元素的时候,此时这个元素的值发生了改变,那么传递给回调函数callback的值就是改变之后的值。如果遍历到了某个元素的时候,此时这个元素的值发生了改变,那么传递给回调函数callback的值就是更改之前的值。所以在下面例子中,虽然更改arr[0]的值,但是由于已经轮到arr[0]元素进行遍历,所以传递给回调函数callback的值是1。而arr[1]元素未进行遍历,所以传递给回调函数callback的值改变之后的值7。
已删除的项不会被遍历到,也就是说如果已经访问的元素在迭代时被删除了,之后的元素将被跳过。这句话又该如何理解呢?
也就是说,如果已经访问的元素在迭代时删除了,此时数组的长度发生改变,数组整体索引值发生改变,此时正在被迭代元素的后一个元素将会被跳过,不会进行迭代。所以例子中,当到达包含值two的项时,整个数组的第一个项被移除,导致数组的长度发生变化,所有的元素索引值发生变化。元素three的索引值从3变成2,所以元素three会被跳过。

// 后添加的项不再被callback访问
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (elem, index, array) {
	arr.push(index + 1);
	console.log(elem); // 1 2 3 4 5
})
// 元素发生改变
var arr = [1, 2, 3, 4, 5];
arr.forEach(function(elem, index, array){
	arr[0] = 6;
	arr[1] = 7;
	console.log(elem); // 1 7 3 4 5
})
// 跳过元素
var words = ['one', 'two', 'three', 'four'];
words.forEach(function (word) {
	console.log(word); // one two four
	if (word === 'two') {
		words.shift();
	}
});

forEach()方法在也是经常用的到数组扩展方法,我们知道通常前端有时候会将数据保存在DOM结构中,我们可以通过DOM结构获取数据,从而直接进行对数据的操作😆。因为从DOM中获取的数据是JSON字符串的形式,所以需要将JSON字符串转换为JSON对象的格式。

<div class="J_data" style="display:none">[{"id":"1","name":"李知恩","sex":"女"},{"id":"2","name":"迪丽热巴","sex":"女"},{
		"id":"3","name":"彭于晏","sex":"男"},{"id":"4","name":"威斯布鲁克","sex":"男"}]
</div>

// 获取JSON字符串数据,并且转换数据
var data = JSON.parse(document.getElementsByClassName('J_data')[0].innerHTML);

// 利用forEach遍历数据
data.forEach(function(elem, index, array){
	console.log(elem, index, array);
	console.log(this); // {"id":"5","name":"胡歌","sex":"男"}
}, {"id":"5","name":"胡歌","sex":"男"});

forEach()方法是ES5扩展方法,所以可以一些低版本的浏览并不支持forEach()方法,所以我们需要重写forEach()方法进行兼容低版本的浏览器。

// 自定义数组的扩展方法
Array.prototype.myForEach = function(callback, thisArg) {
	// 保存当前的this
	var arr = this,
	// 获取数组的长度
			len = arr.length,
	// 设置thisArg参数
			arg;
	// 当然也可以这样设置thisArg参数
	//  arg = arguments[1] ? thisArg || window;
	
	// 判断callback参数的合法性
	if (typeof callback !== 'function') {
		throw new TypeError('callback is not function.');
	}
	
	// 判断是否传入thisArg参数
	if (thisArg) {
		arg = thisArg;
	}
	
	// 循环遍历数组
	for (var i = 0; i < len; i++) {
		// 每次循环,都需要执行callback函数
		callback.apply(arg, [arr[i], i, arr]);
	}
	
	// 可写,可不写,因为函数默认返回undefined;
	// return undefined;
}

forEach()方法被设计成为通用方法,也就是说可以类数组对象也可以使用。例如:arguments、NodeList等。当然类数组对象本身是没有forEach()方法的,所以需要从Array.prototype上继承来使用即可。

var obj = {
	0: {
		"id": "1",
		"name": "李知恩",
		"sex": "女"
	},
	1: {
		"id": "2",
		"name": "迪丽热巴",
		"sex": "女"
	},
	2: {
		"id": "3",
		"name": "科比",
		"sex": "男"
	},
	'length': 3,
	'hobby': ['读书', '敲代码', '修飞机'],
	'splice': Array.prototype.splice,
	'forEach': Array.prototype.forEach
  }
obj.forEach(function(elem, index, obj){
     console.log(elem, index, obj);
})

Array.prototype.filter

Array.prototype.filter()方法是ES5新增的数组扩展方法,filter英文的意思是:“过滤”。那么Array.prototype.filter()方法就可以理解为:数组的过滤方法。
在MDN上解释:filter()方法创建一个新数组,其包括通过所提供函数实现的测试的所有元素。也就是说,filter()方法返回一个新数组,并且数组内部的元素都是能够通过回调函数测试过的。
filter()方法的执行机制可以通过上述的描述大概的理解,filter()方法对数组中的每一个元素都需要通过提供的回调函数测试,如果回调函数通过测试的话,就返回该元素;如果没有通过测试,就不返回该元素,所以整体就到达一个所谓过滤的效果。我们一起来看一下filter()方法具体的语法:

var newArr = arr.filter(callback(elem, index, array), thisArgs);

callback:
	用来测试数组的每个元素的函数。返回true表示该元素通过测试,保留该元素;false表示未通过测试,不保留该元素。
	
	element:
		数组中当前正在处理的元素。
	
	index:
		正在处理的元素在数组中的索引。
	
	array:
		调用了filter的数组本身。

thisArg:
	callback函数执行时,内部的this值。
	
返回值: 
	一个新的、有通过测试的元素组成的数组,如果任何元素都没有通过测试,则返回空数组。
熟悉`Array.protoype.filter()`方法之后,我们再通过MDN上filter()方法的描述进一步了解filter()方法。

首先filter()方法为数组中的每一个元素都调用一次callback函数,并且利用所有使得callback返回true或等价于true的值的元素创建一个新数组。 换句话来说,就是filter()方法会对数组中的每个元素都执行一次callback函数,如果该元素让callback函数返回true或等价于true的值,那么就添加到新数组中。
等价于true的值是什么东西呢?等价于true的值指代的就是Truthy(真值):指代的是在Boolean上下文中,转换后为true的值。除了fasly(虚值)集合中的值,其他的值都是Truthy(真值);fasly(虚值)集合:undefined null 0 NaN fasle ''
下面的例子,filter()方法中的callback函数返回的都是falsy虚值,所以数组中所有的元素都没有通过测试,返回的结果都是空数组。

var arr = [1, 2, 3, 4, 5];
var newArr = arr.filter(function(elem, index, array) {
	return 0;
	return NaN;
	return fasle;
	return null;
});
console.log(newArr); // []

filter()方法的callback函数只会在已经赋值的索引上被调用,对于那些已经被删除或从未被赋值的索引不会调用。那些没有通过callback测试的元素会被跳过,不会包含在新数组中。
例如下面的例子中,arr是稀松数组,此时虽然callback函数返回的都是true,但是由于filter()方法只会在已经赋值的索引上被调用,相对于稀松数组中的empty元素并不会调用callbak函数。
elem === 1时,删除arr数组的最后一项元素6,此时还没有遍历到元素6。而当遍历到元素值6时,元素已经被删除,所以callback函数并不会被调用。
而下面例子中“小问题”,当代码执行到elem === 6的时候,删除arr数组中的最后一项元素,但是呢?在filter()方法返回的数组中依然存在元素6,这是为什么呢?其实也很简单,因为删除数组中元素6的操作在filter()方法遍历数组元素6操作之后,callback函数已经获取到当前elem的值,所以再去对原数组操作已经没有意义。

// 稀松数组
var arr = [1, 2, 3, , , 6];
var newArr = arr.filter(function(elem, index, array) {
	return true;
})
console.log(newArr); // [1, 2, 3, 6];

// 已经被删除元素
var arr = [1, 2, 3, , , 6];
var newArr = arr.filter(function(elem, index, array) {
	if (elem === 1) {
		arr.pop();
	}
	return true;
})
console.log(newArr); // [1, 2, 3]

// 小问题
var arr = [1, 2, 3, , , 6];
var newArr = arr.filter(function(elem, index, array) {
	if (elem === 6) {
		arr.pop();
	}
	return true;
})
console.log(newArr); // [1, 2, 3, 6]

filter()方法并不会改变原数组,它返回过滤后的新数组。filter()遍历的元素范围在第一次调用callback之前就已经确定了。在调用filter()方法之后被添加到数组中的元素不会被filter()方法遍历到。如果已经存在的元素改变了,则它们传入callback的值是filter()遍历到它们那一刻的值😉。
我们结合上述👆的规则,看一下具体的现象😲。首先是在调用filter()方法之后被添加到数组中的元素不会被filter()方法遍历到。例子1中,elem === 1的时候在原数组arr中新增元素push(4,5),但是在filter()方法执行完成后,并没有返回新增的元素。
第二,如果已经存在的元素改变了,则它们传入callback的值是filter()遍历到它们那一刻的值⚠️。例子2中,elem === 1的时候,此时arr[1] = 3改变的是原数组arr中的第二个元素,但是由于filter()方法并没有遍历到第二个元素,所以当filter()方法遍历到第二个元素的时候,元素已经改变。那么传入到callbak函数中的elem值就是改变后的值😀。
例子3中,elem === 1的时候,此时arr[0] = 3改变的是原数组arr中的第一个元素,也就是现在遍历的元素值。所以callback函数现在接收到的elem值就是未改变的值,并不是改变之后的值。正是因为这些现象,所以才有这条规则:如果已经存在的元素改变了,则它们传入callback的值是filter()遍历到它们那一刻的值🤔。

// callbak添加元素
var arr = [1, 2, 3];
var newArr = arr.filter(function(elem, index, array) {
	if (elem === 1) {
		arr.push(4, 5);
	}
	return true;
})
console.log(newArr); // [1, 2, 3]

// callback改变元素
var arr = [1, 2, 3];
var newArr = arr.filter(function(elem, index, array) {
	if (elem === 1) {
		arr[1] = 3;
	}
	return true;
})
console.log(newArr); // [1, 3, 3]

// callback改变元素
var arr = [1, 2, 3];
var newArr = arr.filter(function(elem, index, array) {
	if (elem === 1) {
		arr[0] = 3;
	}
	return true;
})
console.log(newArr); // [1, 2, 3]
重写filter方法,兼容ES3
如果改变新数组元素的值是引用值,会影响到原数组引用值,所以需要深度递归再存入新数组中。
Array.prototype.myFilter = function(callback) {
	var arr = this,
			len = arr.length,
	// 参数2改变this指向,可传可不传。
			arg2 = arguments[1] || window,
	// 返回的新数组
			newArr = [],
			item;
	// for循环
	for(var i = 0; i < len; i++) {
			// 深度克隆后,不然改变元素会影响原始数组中的引用值。
			item = deepClone(arr[i]);
			// 如果回调函数返回true,深度拷贝后存入新数组;返回false,返回空字符串。
			callback.apply(arg2, [item, i, arr]) ? newArr.push(item) : '';
	}
	return newArr;
}

var newArr = data.myFilter(fn, document);
console.log(newArr);

function fn(elem, index, array) {
	console.log(this); // document
	return elem.sex === '男';
}

filter方法主要是callback回调函数返回值是Truthy的时候,将元素放入新的数组中。但是现在存在一个问题,就是稀松数组。稀松数组会存在empty元素,数组原生的filter方法会将empty元素自动过滤掉。

var arr3 = [1, , ,]; // 稀松数组

// 原生数组filter
var result = arr3.filter(function(elem) {
	return true;
});
console.log(result); // [1]

// 重写数组filter
var result2 = arr3.myFilter(function(elem){
	return true;
});
console.log(result); // [1, undefined, undefined]

那么为什么之前写的filter方法遇见稀松数组会有这种问题呢?其实问题就出现在for循环的自身,因为之前重写的时候,我们在for循环中设置item = arr[i],由于稀松数组中并不存在当前的i值,所以itemundefined。我们需要做的,就是如何将这种情况给排除出去。

Array.prototype.myFilter = function(callback, thisArg) {
	// 保存this指向
	var _this = this,
	// 设置thisArg参数默认值
			thisArg = thisArg === undefined ? window : thisArg,
	// 创建返回的新数组
			_newArr = [],
	// 创建索引值
			_index = -1;
	// 利用for...in循环处理我们上述描述的问题
	for (var key in _this) {
		// 判断当前key是否是属于自身并且存在
		// 此时的key --> '0', '1', '2'
		if (_this.hasOwnProperty(key) && key) {
			// 执行回调函数,改变this指向,传入参数
			callback.apply(thisArg, [_this[key], ++_index, _this])
			?	_newArr.push(_this[key]) : '';
		}
	}
	// 返回新的数组
	return _newArr;
}

// 测试
var arr3 = [1, , ,]; // 稀松数组
var result2 = arr3.myFilter(function(elem){
	return true;
});
console.log(result); // [1]
filter 方法被设计成为通用的方法,类数组对象也能使用
var obj = {
	0: {
		"id": "1",
		"name": "李知恩",
		"sex": "女"
	},
	1: {
		"id": "2",
		"name": "迪丽热巴",
		"sex": "女"
	},
	2: {
		"id": "3",
		"name": "科比",
		"sex": "男"
	},
	'length': 3,
	'hobby': ['读书', '敲代码', '修飞机'],
	'splice': Array.prototype.splice,
	'filter': Array.prototype.filter
}
var newArr = obj.filter(function (elem, index, obj) {
	return elem.sex === '女';
});
console.log(newArr);

Array.prototype.map

map方法的定义
map是ES5新增的数组扩展方法,map方法存放在Array.prototype。

语法:
  var newArr = [].map(function(elem, index, array){},document);
  参数1:回调函数
  参数2:更改参数1函数中的this指向
  返回值:新的数组
 
this指向:和forEach、filter方法相同

效果:
  1.map每一次返回当前元素就会将操作过后的元素返回到新数组中,但是如果操作的引用值,就需要深度递归,不然会影响到原始值。
  2.map我们希望根据原数组能够返回一个与原数组产生映射关系的数组。
map 方法注意事项
  1. map方法会给原数组中的每个元素都按照顺序调用一次callback回调函数。callback每次执行后的返回值(包括undefined)组合起来形成一个新的数组。
  2. callback函数只会在有值的索引上被调用,那些从来没有被赋过值或者使用delete删除的索引则不会被调用。
  3. map方法处理数组元素的返回是在callback回调函数第一次调用之前就已经确定了。调用map方法之后追加的数组元素不会被callback回调函数访问。
  4. 如果存在的数组元素改变了,那么传给callback的值是map访问该元素时的值。在map函数调用后但在访问该元素前,该元素被删除的话,则无法被访问到。
map 使用案例

现在有一个['1', '2', '3']数组,我们要将这个数组元素从字符串类型转为数字类型,通常我们会使用map方法映射出一个新的数组,例如下面的例子:

['1', '2', '3'].map(parseInt); // [1, NaN, NaN]

我们期望的结果是[1, 2, 3],但是实际上是[1, NaN, NaN]。这是因为parseInt(string, radix)存在第二个参数radix基底,功能就是告诉程序我要用多少进制的数转为十进制的数。那么上面的例子,就等价于下面的例子:

['1', '2', '3'].map(function(elem, index) {
	return parseInt(elem, index);
	// parseInt('1', 0) --> 1
	// parseInt('2', 1) --> NaN, 1进制中不存在数字2
	// parseInt('3', 2) --> NaN, 2进制中不存在数字3
});

那么如果我们就想用parseInt解决上述的问题,那该怎么办呢?

const returnInt = element => parseInt(element, 10);
['1', '2', '3'].map(returnInt); // [1, 2, 3]


['1', '2', '3'].map(Number); // [1, 2, 3]
重写map ,兼容ES3
Array.prototype.myMap = function(callback){
	var arr = this,
			len = arr.length,
	// 参数2更改this指向可传可不传
			arg2 = arguments[1] || window,
			newArr = [],
			item;
	// for循环
	for(var i = 0; i < len; i++) {
		// 深度递归
		item = deepClone(arr[i]);
		// 根据回调函数的返回值,传入新的数组
		newArr.push(callback.apply(arg2,[item, i, arr]));
	}
	return newArr;
}

// 验证:
<div class="J_data" style="display:none">
	[{"id":"1","name":"李知恩","sex":"女"},{"id":"2","name":"迪丽热巴","sex":"女"},{
		"id":"3","name":"彭于晏","sex":"男"},{"id":"4","name":"威斯布鲁克","sex":"男"}]
</div>

var data = JSON.parse(document.getElementsByClassName('J_data')[0].innerHTML);

var newArr = data.myMap(fn, document);

function fn(elem, index, arr){
	elem.id *= 2;
	console.log(this); // document
	return elem;
}
console.log(newArr);
map方法被设计成通用的方法,类数组对象也可以操作
var obj = {
	0: {
		"id": "1",
		"name": "李知恩",
		"sex": "女"
	},
	1: {
		"id": "2",
		"name": "迪丽热巴",
		"sex": "女"
	},
	2: {
		"id": "3",
		"name": "科比",
		"sex": "男"
	},
	'length': 3,
	'hobby': ['读书', '敲代码', '修飞机'],
	'splice': Array.prototype.splice,
	'map': Array.prototype.map
}
var newArr = obj.map(function (elem, index, obj) {
	elem.id *= 2;
	return elem;
});
console.log(newArr);

Array.prototype.every

every 方法的定义

every()方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。

every是ES5新增的数组扩展方法, every方法存放在Array.prototye。

语法:
	var bool = [].every(function(elem, index, array){},document);
	参数1:回调函数
	参数2:更改参数1函数内部的this指向
	返回值:如果回调函数的每一次都返回Truthy,则是true,否则false

this指向:和forEach、filter、map方法相同

效果:
	1.every回调函数中只要有一个不满足条件返回false就停止遍历,条件就是return后的表达式。
every 方法的描述
  1. every方法为数组中的每个元素执行一次callback函数,直到它找到一个使callback返回falsy的元素。如果发现了一个这样的元素,every方法将会立即返回false。否则,callback为每一个元素返回trueevery就会返回truecallback只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。
  2. every不会改变原数组,因为它只是返回Boolean值。
  3. every遍历的元素范围在第一次调用callback之前就已经确定了。在调用every之后添加的数组元素不会被callback访问到。
  4. 如果数组中存在的元素被更改,则他们传入callback的值是every访问到它们的那一刻的值。那些被删除的元素或从来未被赋值的元素将不会被访问到。
every 方法中回调函数的返回值
var bool = data.every(function(elem, index, array){
	return []; // true
	return {}; // true
	return undefined; // false
});

// 结论:
返回值受Boolean隐式转换的影响,除了返回虚值(falsly),其它都是true;
重写every方法,兼容ES3
Array.prototype.myEvery = function(callback){
  let arr = this,
      len = arr.length,
      argThis = arguments[1] || window
      returnValue = true,
      item = null;
  for(let i = 0;i<len;i++){
    item = arr[i];
    if(item && returnValue && !callback.apply(argThis,[item,i,arr])){
      return returnValue = false
    }
  }
  return returnValue;
}

// 验证:
<div class="J_data" style="display:none">
	[{"id":"1","name":"李知恩","sex":"女"},{"id":"2","name":"迪丽热巴","sex":"女"},{ "id":"3","name":"彭于晏","sex":"男"},{"id":"4","name":"威斯布鲁克","sex":"男"}]
</div>

var data = JSON.parse(document.getElementsByClassName('J_data')[0].innerHTML);

var bool = data.myEvery(function(elem, index, array){
	// 需求:数组元素中是否存在女性,因为every每一次循环要是不满足elem.sex === '男',就返回false,并且停止遍历,说明元素中存在女性。
	return elem.sex === '男';
});
console.log(bool); // false
every方法被设计成通用的方法,类数组对象也可以使用
var obj = {
	0: {
		"id": "1",
		"name": "李知恩",
		"sex": "女"
	},
	1: {
		"id": "2",
		"name": "迪丽热巴",
		"sex": "女"
	},
	2: {
		"id": "3",
		"name": "科比",
		"sex": "男"
	},
	'length': 3,
	'hobby': ['读书', '敲代码', '修飞机'],
	'splice': Array.prototype.splice,
	'every': Array.prototype.every
}
var bool = obj.every(function (elem, index, obj) {
	return elem.sex === '女';
});
console.log(bool); // false;

Array.prototype.some

some方法的定义
some是ES5新增的数组扩展方法,some方法存放在Array.prototype。

语法:
	var bool = [].some(function(elem, index, array){},document);
	参数1:回调函数
	参数2:更改参数1函数内部的this指向
	返回值:数组中有至少一个元素通过回调函数的测试就会返回true,所有元素都没有通过回调函数的测试返回
	值才会是falsethis指向:和forEach、filter、map、every相同

效果:
	1.some回调函数中只要有一个满足条件返回true就停止遍历,条件就是return后的表达式。
some方法描述
  1. some()为数组中的每一个元素执行一次callback函数,直到找到一个使得callback返回一个“真值Truthy”。如果找到了这样一个值,some()会立即返回true,否则some()返回false
  2. callback只会在那些“有值”的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。
  3. some()遍历的元素的范围在第一次调用callback前就已经确定了。在调用some()后被添加到数组中的值就不会被callback访问到。
  4. 如果数组中存在且还未被访问到的元素被callback改变了,则其传递给callback的值是some()访问到它那一刻的值。已经被删除的元素不会被访问到。
some方法返回值的注意事项
var bool = data.some(function(elem, index, array){
	return []; // true
	return {}; // true
	return undefined; // false
});

// 结论:
返回值受Boolean隐式转换的影响,除了返回虚值,其它都是true;
使用示例
var arr = [1234567];
arr.some((item)=>{
  return item > 5;
})
重写some方法,兼容ES3
Array.prtotype.some = function(callback) {
	var arr = this,
			len = arr.length,
			arg2 = arguments[1] || window,
			bool = false;
	for(var i =  0; i < len; i++) {
		if(callback.apply(arg2, [arr[i], i, arr])){
			// 回调函数中返回true,说明有一项满足条件,返回true,并且停止执行。
			return bool = true;
		}
	}
	return bool;
}
some方法被设计成通用的方法,类数组对象也可以使用
var obj = {
	0: {
		"id": "1",
		"name": "李知恩",
		"sex": "女"
	},
	1: {
		"id": "2",
		"name": "迪丽热巴",
		"sex": "女"
	},
	2: {
		"id": "3",
		"name": "科比",
		"sex": "男"
	},
	'length': 3,
	'hobby': ['读书', '敲代码', '修飞机'],
	'splice': Array.prototype.splice,
	'some': Array.prototype.some
}
var bool = obj.some(function (elem, index, obj) {
	return elem.sex === '女';
});
console.log(bool); // true;

Array.prototype.reduce

reduce方法的定义

reduce()方法对数组中的每个元素按序执行一个由您提供的reducer函数,每一次运行reducer会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

reduce是ES5新增的数组扩展方法,reduce方法存放在Array.prototype中,reduce函数又叫做归纳函数。

归纳函数与过滤的区别:
	1.归纳函数:在我的条件范围之内,往容器内存储东西,而这个容器成为新的东西,而不是原来的东西。
	2.过滤:通过回调函数返回布尔值,true就放入容器中,不是我就丢弃。

语法:
	var newArr = [].reduce(function(prev, elem, index, array){}, initialValue);
	参数1:回调函数 reducer函数。
	参数2:初始化值。
	返回值:使用reducer回调函数遍历整个数组后的结果。
	
this指向:回调函数中默认指向window,没有改变this指向的参数。

效果:
	1.利用initialValue作为初始化的值,每次遍历后,通过回调函数返回prev的值,让程序遍历循环下去。
	
reduce方法中回调函数的参数理解、initialValue参数理解
var newArr = [].reduce(function(prev, elem, index, array){
  console.log(elem, index, array);
}, initialValue);

1. 首先elem,index,array和之前的方法相同,分别代表当前元素,当前元素的下标,原始数组。

2. initialValue初始化值,起到的作用首次遍历数组元素时,给prev进行赋值,所以在第一次遍历的时候prev === initialValue; 

3. prev每次遍历结束后的值,每次遍历操作数组元素之后,存储到prev之后,返回prev值,作为此次循环结束的值。
reduce方法的描述
  1. reducer回调函数第一次执行时,previousValuecurrentValue的取值有两种情况:
  • 如果调用reduce()时提供了initialValuepreviousValue取值为initialValuecurrentValue则取数组中的第一个值。

  • 如果没有提供initialValue,那么previousValue取数组中的第一个值,currentValue取第二个值。

  1. 如果数组仅有一个元素(无论位置如何)并没有提供初始值initialValue,或者有提供initialValue但是数组为空,那么此唯一值将被返回且reducer回调函数不会执行。
  2. 如果数组为空且未指定初始值initialValue,则会抛出TyError
  3. reduce不会改变原数组
  4. reduce遍历的元素范围是在第一次调用callback之前确定的,所以即使有元素在调用开始后被追加到数组中,这些元素也不会被callback访问。
  5. 如果数组现有的元素发生了改变,传递给callback的值将会是元素被reduce访问时的值,在调用reduce开始后,尚未被访问的元素若被删除,则其将不会被reduce访问。
reduce 返回值的问题

reducer回调函数,必须设置返回值,reducer回调函数的返回值作为下次callback回调函数执行的previousValue值。如果你忽略reducer回调函数的返回值,那么默认返回undefined,导致最后的结果返回的是undefined

var initialValue = [];

var newArr = [1, 2, 3, 4].reduce(function(prev, elem, index, array){
   console.log(prev); // 除了第一次循环是[],其余都是undefined;
   prev.push(elem);
   return prev; // 只有返回prev结束的值,才能延续执行代码。
}, initialValue);

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

console.log(newArr === initialValue); // true
reduce方法示例
let sum = [1, 2, 4, 5, 6, 7, 3].reduce(function(prev, current, index, array){
  return prev + current; 
}); 
// sum is 28, initialValue non-existent

let sum = [1, 2, 4, 5, 6, 7, 3].reduce(function(prev, current, index, array){
  return prev + current; 
}, 0); 
// sum is 28, initialValue exist
// 我们必须要传递initialValue值,因为如果省略initialValue的话,那么第一次执行reducer回调的时候prev->{x:1},但是第二次执行将会是3 + {x:3}的结果 --> '3[object Object]'。

let sum = [{x: 1}, {x: 2}, {x: 3}].reduce((prev, current) => {
  return prev + current.x;
}, 0);
// sum is 6
let flattened = [[0, 1], [2, 3], [4, 5]].reduce((prev, current) => {
  return prev.concat(current);
},[]);
// flattened: [0, 1, 2, 3, 4, 5]
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'].reduce((prev, current) => {
  current in prev ?  prev[current] ++ : prev[current] = 1;
  return prev;
}, {});
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
let myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd'];

let res = myArray.reduce((prev,current)=>{
  !prev.includes(current) && prev.push(current)
  return prev;
},[])
// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5)
  })
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2)
  })
}

// function 3
function f3(a) {
 return a * 3
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4)
  })
}


var promiseArr = [p1, p2, f3, p4];
function promiseRuntimeSquence(arr, input) {
	return arr.reduce(
		// reducer
		(promiseChain, currentPromise) => promiseChain.then(currentPromise),
		// initialValue
		Promise.resolve(input)
	)
}
promiseRuntimeSquence(promiseArr, 10).then(console.log); // 1200
重写reduce方法,兼容ES3
Array.prototype.myReduce = function(callback, initialValue) {
	var arr = this,
			len = arr.length,
	// 原生reduce没有改变this功能,我们可以自己添加
			arg2 = arguments[2] || window,
			item;
	// for循环遍历
	for(var i = 0; i < len; i++) {
		// 深度克隆
		item = deepClone(arr[i]);
		initialValue = callback.apply(arg2, [initialValue, item, i, arr]);
	}
	return initialValue;
}
/**
* 为什么所有的重写方法中都牵扯到in运算符呢?
* 因为有稀松数组的原因,所以要排除empty元素:
* 例如: arr = [,,,1,2,3]  arr = [,1,,,,3,,,4]这种情况
*
* 下面中的while循环的作用,也正是为了确定数组中第一个可枚举的元素。
*
*/

Array.prototype.myReduce = function(callback) {
	var _this = this,
		_len = _this.length,
		_index = 0,
		// prevValue
		value;
	// 判断条件是否传入initialValue的值
	if (arguments.length >= 2) {
		// 说明传入initialValue参数,prevValue == initialValue
		value = arguments[1];
	} else {
		// 说明没有传入initialValue参数,prevValue == 数组中可枚举的第一个值,currentValue就等于数组中可枚举的第二个值
		while (_index < _len && !(_index in _this)) {
			_index++;
		}
		// [_index++]很巧妙,_index++,先赋值,后加减,为下面for循环埋下伏笔
		// 例如: this[_index++] -> this[0] --> _index 0 --> 1
		value = this[_index++]; 
	}

	// 循环
	// _index由于上面的this[_index++],所以排除掉了prevValue对应的值
	for (; _index < _len; _index++) {
		if (_index in _this) {
			value = callback(value, this[_index], _index, _this);
		}
	}
	return value;
}

Array.prototype.reduceRight

reduceRight方法定义
reduceRight和reduce非常相似,主要区别是在于排序;

reduceRight是reduce的倒序形式。
重写reduceRight,兼容ES3
Array.prototype.myReduceRight = function (callback, initialValue) {
	var arr = this,
			len = arr.length,
			arg2 = arguments[2] || window,
			item;
	// 实现倒序
	for (var i = len - 1; i >= 0; i--) {
		// 深度克隆
		item = tools.deepClone(arr[i]);
		initialValue = callback.apply(arg2, [initialValue, arr[i], i, arr]);
	}
	return initialValue;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1236843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单链表OJ题——10.环形链表2

10.环形链表2 142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; /* 解题思路&#xff1a; 如果链表存在环&#xff0c;则fast和slow会在环内相遇&#xff0c;定义相遇点到入口点的距离为X,定义环的长度为C,定义头到入口的距离为L,fast在slow进入环之后一圈内追上slow…

ArmSoM-RK3588编解码之mpp编码demo解析:mpi_enc_test

一. 简介 [RK3588从入门到精通] 专栏总目录mpi_enc_test 是rockchip官方编码 demo本篇文章进行mpi_enc_test 的代码解析&#xff0c;编码流程解析 二. 环境介绍 硬件环境&#xff1a; ArmSoM-W3 RK3588开发板 软件版本&#xff1a; OS&#xff1a;ArmSoM-W3 Debian11 三. …

并行与分布式 第7章 体系结构 下

文章目录 并行与分布式 第7章 体系结构 下7.3 互连结构7.3.1 网络拓扑的基本概念7.3.2 互连网络分类7.3.3 典型静态网络7.3.4典型动态互连网络 7.4 性能评测7.4.1 工作负载7.4.2 峰值速度7.4.3 并行执行时间7.4.4 性能价格比7.4.5多处理器性能定律 并行与分布式 第7章 体系结构…

Linux免密登录——A登录B密钥设置(SSH SCP)

密钥登录 密钥登录比帐号密码方式更安全、更方便&#xff0c;并提供了更多的自动化和批处理选项。 安全性&#xff1a;使用非对称加密算法&#xff0c;公钥存在服务器&#xff0c;私钥存在本地计算机&#xff0c;私钥不在网络传输&#xff0c;降低被黑客截获风险。强密码&#…

2023年亚太杯数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 最短时…

Pyside6/PyQt6的QTreeWidget如何添加多级子项,如何实现选中父项,子项也全部选中功能,源码示例

文章目录 📖 介绍 📖🏡 环境 🏡📒 使用方法 📒📝 数据📝 源码📖 介绍 📖 在UI开发中经常会需要展示/让用户多层级选择,这篇文章记录了一个QTreeWidget如何添加多级子项,如何实现选中父项,子项也全部选中/取消选中功能的源码示例,大家可以举一反三实现自…

私有化敏感词检测API服务wordscheck

之前有网友在找敏感词检测的应用&#xff0c;这个应该能满足他的需求&#xff1b; 什么是 wordscheck &#xff1f; wordscheck 是敏感词检测 API&#xff0c;提供文本识别、智能鉴黄、涉政检测、谩骂等等敏感词检测过滤服务。 简介 敏感词库从大量样本库整理出来&#xff0c;…

数据结构(超详细讲解!!)第二十四节 二叉树(上)

1.定义 二叉树&#xff08;Binary Tree&#xff09;是另一种树型结构。 二叉树的特点&#xff1a; 1&#xff09;每个结点至多只有两棵子树&#xff08;即二叉树中不存在度大于2的结点&#xff09;&#xff1b; 2&#xff09;二叉树的子树有左右之分&#xff0c;其次序…

关于AssetBundle禁用TypeTree之后的一些可序列化的问题

1&#xff09;关于AssetBundle禁用TypeTree之后的一些可序列化的问题 2&#xff09;启动Unity导入变动的资源时&#xff0c;Singleton ScriptableObject 加载不到 3&#xff09;Xcode15构建Unity 2022.3的Xcode工程&#xff0c;报错没有兼容的iPhone SDK 这是第361篇UWA技术知识…

EPSon打印机更换色带

1、打印机色带拆装视频 打印机色带更换 2、色带盒四周有多个卡扣&#xff0c;需从右到左依次轻微用力掰开&#xff0c;使盖板与盒体脱离&#xff0c;注意不要掰断卡扣。 3、如何将色带放入打印机色带盒&#xff1f; A、色带放入盒体时不可打乱打结&#xff0c;以免卡带&#x…

图解Spark Graphx基于connectedComponents函数实现连通图底层原理

原创/朱季谦 第一次写这么长的graphx源码解读&#xff0c;还是比较晦涩&#xff0c;有较多不足之处&#xff0c;争取改进。 一、连通图说明 连通图是指图中的任意两个顶点之间都存在路径相连而组成的一个子图。 用一个图来说明&#xff0c;例如&#xff0c;下面这个叫graph…

合理运用ChatGPT使用Python编写一个桌面便签应用

ChatGPT的编程能力也不差&#xff0c;本次我就一步一步提要求&#xff0c;让ChatGPT根据我的要求&#xff0c;编写出一个可用的&#xff0c;可打包运行的桌面便签。 代码 import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QAction, QSystemTrayIco…

通信原理板块——时分复用

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、基本概念 复用的目的是为了扩大…

宏集新闻 | 虹科传感器事业部正式更名为宏集科技

致一直支持“虹科传感器”的朋友们&#xff1a; 为进一步整合资源&#xff0c;给您带来更全面、更优质的服务&#xff0c;我们非常荣幸地宣布&#xff0c;虹科传感器事业部已正式更名为宏集科技。这一重要的改变代表了虹科持续发展进程中的新里程碑&#xff0c;也体现了我们在传…

GitHub 报告发布:TypeScript 取代 Java 成为第三受欢迎语言

GitHub发布的2023年度Octoverse开源状态报告发布&#xff0c;研究围绕AI、云和Git的开源活动如何改变开发人员体验&#xff0c;以及在开发者和企业中产生的影响。报告发现了三大趋势&#xff1a; 1、生成式AI的广泛应用&#xff1a; 开发人员大量使用生成式AI进行构建。越来越…

智能座舱架构与芯片- (14) 测试篇 上

一、 验证平台概要 1.1 测试软件方法论 “软件定义汽车” 的时代&#xff0c;软件在整车制造中的重要性日渐凸显。但不同于其他行业的软件开发&#xff0c;汽车行业有自己独特的软件开发要求。首先是需求严谨、需求层次复杂、需要通过专业的工具进行管理&#xff1b;其次开发…

张弛声音变现,如此配音是一场史诗

在给战争剧进行配音的过程中&#xff0c;配音艺术家须突出剧中的紧迫气氛、战斗场面的惊心动魄以及人物的英雄气概。战争剧经常涉及密集的战斗描写、复杂的策略以及角色间深刻的情感纠葛。以下是为战争剧配音时的几点指导思路&#xff1a; 强烈且充满张力的语气 在配音时使用充…

【C++】string类的介绍与使用

&#x1f9d1;‍&#x1f393;个人主页&#xff1a;简 料 &#x1f3c6;所属专栏&#xff1a;C &#x1f3c6;个人社区&#xff1a;越努力越幸运社区 &#x1f3c6;简 介&#xff1a;简料简料&#xff0c;简单有料~在校大学生一枚&#xff0c;专注C/C/GO的干货分…

已完结7个,再启动1个新项目,嘎嘎强!

作者&#xff1a;小傅哥 博客&#xff1a;https://bugstack.cn 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; 大家好&#xff0c;我是技术UP主小傅哥。 &#x1f490;又到了启动新项目的时候&#xff0c;死鬼开心嘛。小傅哥的星球&#xf…

六大排序详讲(直接插入排序+希尔排序+选择排序+堆排序+冒泡排序+快速排序)

文章目录 排序一、 排序的概念1.排序&#xff1a;2.稳定性&#xff1a;3.内部排序&#xff1a;4.外部排序&#xff1a; 二、插入排序1.直接插入排序2.希尔排序 三、选择排序1.直接选择排序方法一方法二直接插入排序和直接排序的区别 2.堆排序 四、交换排序1.冒泡排序2.快速排序…