js数组API原生实现最全整理

183 阅读4分钟

Array.from()

Array.myFrom = function(target,fn,thisargs){
	if(typeof target[Symbol.iterator]!=="function"){
		return [];
	}
	if(typeof fn!=="function"){
		fn=false;
	}
	const it = target[Symbol.iterator](),res=[];
	let end = false;
	if(!end){
		let {value,done} = it.next();
		if(!done){
			if(fn){
				res.push(fn.call(thisargs,value))
			}
			else{
				res.push(value);
			}
		}
		end=done;
	}
	return res;
}

Array.from()方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

接受三个参数,target:目标对象,fn:如果有,返回回调函数处理后的每个元素,thisargs,指定函数运行的this环境

Array.of()

Array.myOf = function(){
	let result = [];
	for(let i of arguments){
		result.push(i)
	}
	return result;
}

copyWithin()

if (!Array.prototype.copyWithin) {
  Array.prototype.copyWithin = function(target, start/*, end*/) {
    // Steps 1-2.
    if (this == null) {
      throw new TypeError('this is null or not defined');
    }

    var O = Object(this);

    // Steps 3-5.
    var len = O.length >>> 0;

    // Steps 6-8.
    var relativeTarget = target >> 0;

    var to = relativeTarget < 0 ?
      Math.max(len + relativeTarget, 0) :
      Math.min(relativeTarget, len);

    // Steps 9-11.
    var relativeStart = start >> 0;

    var from = relativeStart < 0 ?
      Math.max(len + relativeStart, 0) :
      Math.min(relativeStart, len);

    // Steps 12-14.
    var end = arguments[2];
    var relativeEnd = end === undefined ? len : end >> 0;

    var final = relativeEnd < 0 ?
      Math.max(len + relativeEnd, 0) :
      Math.min(relativeEnd, len);

    // Step 15.
    var count = Math.min(final - from, len - to);

    // Steps 16-17.
    var direction = 1;

    if (from < to && to < (from + count)) {
      direction = -1;
      from += count - 1;
      to += count - 1;
    }

    // Step 18.
    while (count > 0) {
      if (from in O) {
        O[to] = O[from];
      } else {
        delete O[to];
      }

      from += direction;
      to += direction;
      count--;
    }

    // Step 19.
    return O;
  };
  }

fill()

Array.prototype.myFill = function(value,start,end){
	start=start?start<0?start+this.length:start:0; //start不存在默认为0,为负值则加length
	end=end?end<0?end+this.length:end:this.length;  //end不存在默认为length;
	console.log(start,end)
	if(end<start){        //end>start不填充
		end=start=0;
	}
	if(end>this.length){	//处理边界超出
		end=this.length;
	}	
	if(start>this.length){		//start边界超出,则不管end是什么都应该忽略,不填充
		start=end=0;
	}
	for(let i=start;i<end;i++){
		this[i]=value;
	}
	return this;
}

fill:从start开始填充,到end结束,不包括end。如果start和end超出边界,则忽略。如果start>end,则不填充

sort()

sort()方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的.

如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。例如 "Banana" 会被排列到 "cherry" 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

在chrome的V8源码中,对于数组元素10以内的,使用插入排序,对于大于10的,使用快速排序算法。

为什么是这样,来测试一下

function quickSort(arr) {
    var len = arr.length; //结束递归的条件 
    if (len <= 1) return arr;
    var left = [];
    var right = []; //中间基数 
    var midindex = Math.floor(len / 2); 
    var mid = arr[midindex];
    for (var i = 0; i < len; i++) {
        if (arr[i] == mid) continue;
        else if (arr[i] < mid) left.push(arr[i]);
        else right.push(arr[i]);
    }
    return quickSort(left).concat([mid], quickSort(right));
}
let arr = []
for (let i = 0; i < 100000; i++) {
    arr.push(parseInt(Math.random() * 10000))
}
let startTime = performance.now() 
quickSort(arr) 
let endTime = performance.now() 
console.log(endTime - startTime)
function insertSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        var k = i; //前提: 1 前面必须有内容 //前提: 2 当前这个元素,比左边小,交换1次 
        while (k - 1 >= 0 && arr[k] < arr[k - 1]) {
            var temp = arr[k];
            arr[k] = arr[k - 1];
            arr[k - 1] = temp;
            k--;
        }
    }
    return arr;
}
let arr = []
for (let i = 0; i < 100000; i++) {
    arr.push(parseInt(Math.random() * 10000))
}
let startTime = performance.now() 
insertSort(arr) 
let endTime = performance.now() 
console.log(endTime - startTime)

DFeEQI.png

DFeGyq.png

slice()

Array.prototype.mySlice = function(begin,end){
	let len = this.length;
	const result = [];
	let index = 0;
	begin = begin?begin<0?len+begin:begin:0;	//begin或end为负值,则加len为结果
	end = end||end===0?end<0?end+len:end:len;  //重点:考虑到end为0或者负值的情况
	if(end<begin){
		return result;					//end<begin则返回空数组
	}
	for(;begin<end;begin++){
		result[index++] = this[begin];	//截取的片段不包括end索引处元素
	}
	return result;
}

splice()

Array.prototype.mySplice = function(){
	let params = Array.prototype.slice.call(arguments)
		let start = params.shift();    
		let deleteCount = params.shift();
		start = Math.floor(start-0)||0;    //获取start,floor防止小数
		if(start<0){
			start = this.length+start		/start<0,则加length作为结果
		}
		if(start>this.length-1){			//start>length,说明不删除元素
			return [];
		}
		deleteCount = Math.floor(deleteCount-0)||this.length-start; //防止小数
		deleteCount<0?0:deleteCount;		//seleteCount<0说明不删除
		deleteCount>this.length-start?this.length-start:deleteCount; //如果超出数组上限,就删除start后面所有元素
		let i=0,startArr=[],res=[],len=this.length;
		for(i=0;i<start+deleteCount;i++){
			value=this.shift();         //从头取出数组元素
			if(i<start){
				startArr.push(value)	//start之前的放入一个数组startArr
			}	
			if(i>=start){	
				res.push(value);		//应该删除的元素放入一个数组作为结果res
			}
		}
		while(params.length){
			this.unshift(params.pop());	//先从头添加增加的元素
		}
		while(startArr.length){
			this.unshift(startArr.pop());	//然后添加start之前的数组元素startArr
		}
		return res;	//返回删除的元素
}

indexOf()

Array.prototype.myIndexOf = function(target,startIndex){
	let len= this.length;
	startIndex = startIndex?startIndex<0?startIndex+len<0?0:startIndex+len:startIndex:0;
	for(let i = startIndex;i<len;i++){
		if(target===this[i])){
			return i;;
		}
	}
	return -1;
}

其实和includes几乎相等,只是这里NaN!=NaN,所以不能使用Obejct.is()比较

lastIndexOf()

includes()

Array.prototype.myIncludes = function(target,startIndex){
	let len= this.length;
	startIndex = startIndex?startIndex<0?startIndex+len<0?0:startIndex+len:startIndex:0;
	for(let i = startIndex;i<len;i++){
		if(Object.is(target,this[i])){
			return true;
		}
	}
	return false;
}

includes的startIndex注意:如果没有传,默认为0,非数字,默认为0。传入负值,则为startIndex+len,如果结果仍然为负值,则为0

find()

Array.prototype.myFind(callback,thisargs){
	if(!this instanceof Array){
		throw new Error("this is not a array";)
	}
	if(typeof callback !=="function"){
		throw new TypeError("callback is not a function");
	}
	let context = thisargs||this;
	let target = this;
	for(let i=0;i<target.length;i++){
		if(callback.call(context,target[i],i,target)){
			return target[i]
		}
	}
	return undefined;
}

find的第一个参数为回调函数,第二个参数为指定的上下文this,可选。结果返回满足回调函数条件的第一个元素,如果没有,则返回undefined。回调函数接受三个参数:当前元素,索引,数组本身。

array.find(function(currentValue, index, arr),thisValue)

flat()

Array.prototype.myFlat = function(depth=1){
	const result = []; // 缓存递归结果
  // 开始递归
  (function flat(depth) {
    // forEach 会自动去除数组空位
    this.forEach((item) => {
      // 控制递归深度
      if (Array.isArray(item) && depth > 0) {
        // 递归数组
        flat(item, depth - 1)
      } else {
        // 缓存元素
        result.push(item)
      }
    })
  })(depth)
  // 返回递归结果
  return result;
}
arr.reduce((acc, val) => acc.concat(val), []);
function flatten(input) {
  const stack = [...input];
  const res = [];
  while (stack.length) {
    // 使用 pop 从 stack 中取出并移除值
    const next = stack.pop();
    if (Array.isArray(next)) {
      // 使用 push 送回内层数组中的元素,不会改动原始输入
      stack.push(...next);
    } else {
      res.push(next);
    }
  }
  // 反转恢复原数组的顺序
  return res.reverse();
}
function* flatten(array) {
    for (const item of array) {
        if (Array.isArray(item)) {
            yield* flatten(item);
        } else {
            yield item;
        }
    }
}

reverse()

Array.prototype.myReverse = function(){
	if(this.length===0){
		return this;
	}
	let left = Math.floor((this.length/2)-1); //Math.floor解决奇数造成的0.5问题
	let y = this.length%2;  //判断是奇数还是偶数个元素
	let right = left+1+y;	//取得右指针
	while(left>=0&&right<this.length){
		[this[left],this[right]] = [this[right],this[left]];  //双指针法
		right++;
		left--;
	}
	return this;
}

DFeEQI.png

concat()

Array.prototype.myConcat = function(){
	let target = (this instanceof Array)?this:[];
	let args = Array.prototype.slice.call(arguments);
	let result = [];
	while(target.length){
		result.push(target.shift());
	}
	while(args.length>0){
		let value = args.shift();
		if(value instanceof Array){    //如果value是数组,则扁平化一层。
			for(let item of value){
				result.push(item)
			}
		}
		else{
			result.push(value)      //value不是数组,直接添加
		}
	}
	return result;
}

join

Array.prototype.myJoin = function(char){
	let result = this[0]||'';
	let len = this.length;
	char = char||",";    //join不传参数,默认","为分隔符
	for(let i = 0;i<length;i++){
		result += char + this[i];
	}
	return result;
}

参数:每个数组方法接受俩个参数:一个为回调函数,一个为参数运行时的上下文this,会影响回调函数中的this指向。回调函数接受三个参数,数组元素,索引,数组本身。

every()

Array.prototype.myEvery = function(callback,newthis){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	const res = [];
	const o = Object(this);
	let len = o.length;
	for(let i = 0;i<len;i++){
		if(i in o){
			if(!callback.call(newthis,o[i],i,o)){
				return false;
			}
		}
	}
	return true;
}

some()

Array.prototype.mySome = function(callback,newthis){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	const res = [];
	const o = Object(this);
	let len = o.length;
	for(let i = 0;i<len;i++){
		if(i in o){
			if(callback.call(newthis,o[i],i,o)){
				return true;
			}
		}
	}
	return false;
}

map()

Array.prototype.myMap = function(callback,newthis){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	const res = [];
	const o = Object(this);
	let len = o.length;
	for(let i = 0;i<len;i++){
		if(i in o){
			res[i]=callback.call(newthis,o[i],i,o);
		}
	}
	return res;
}

forEach()

Array.prototype.myForEach = function(callback,newthis){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	const o = Object(this);
	let len = o.length;
	for(let i = 0;i<len;i++){
		if(i in o){
			callback.call(newthis,o[i],i,o)
		}
	}
}

filter()

Array.prototype.myFilter = function(callback,newthis){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	const res = [];
	const o = Object(this);
	let len = o.length;
	for(let i = 0;i<len;i++){
		if(i in o){
			if(callback.call(newthis,o[i],i,o)){
				res.push(o[i])
			}
		}
	}
	return res;
}

reduce()

Array.prototype.reduce(callback,initValue){
	if(this === undefined){
		throw new TypeError('this is null or not defined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'must be a function');
	}
	let O = Object(this);
	let len = O.length;
	let accumlator = initValue;
	if(accumlator === undefined){
		while(k<len&&!(k in O)){
			k++;
		}
		if(k>=len){
			throw new TypeError('Reduce of empty array no inital value')
		}
		accumlator = O[k];
	}
	while(k<len){
		if(k in O){
			accumlator = callback.call(undefined,accumlator,O[k],k,O)
		}
		k++;
	}
	return accumlator;
}

reduce的第一个参数为回调函数,第二个参数为callback的初始值,可选。回调函数接受四个参数:

初始值(前几项的累计返回值):initValue或者数组的第一个元素,currentValue:正在处理的元素,index:正在处理的索引,array:数组本身(this)