JavaScript数组API全解密(一)

JavaScript数组API全解密(一)

Array构造器

语法及介绍: Array构造器根据参数长度的不同,有如下两种不同的处理:

  • new Array(arg1, arg2,…),参数长度为0或长度大于等于2时,传入的参数将按照顺序依次成为新数组的第0至N项(参数长度为0时,返回空数组)。

  • new Array(len),当len不是数值时,处理同上,返回一个只包含len元素一项的数组;当len为数值时,根据如下规范,len最大不能超过32位无符号整型,即需要小于2的32次方(len最大为Math.pow(2,32) -1-1>>>0),否则将抛出RangeError。

    If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.

    //谷歌翻译

    如果参数len是一个Number并且ToUint32(len)等于len,则新构造的对象的length属性将设置为ToUint32(len)。 如果参数len是Number并且ToUint32(len)不等于len,则会引发RangeError异常。

以上,请注意Array构造器对于单个数值参数的特殊处理,如果仅仅需要使用数组包裹📦 若干参数,不妨使用Array.of

Array`构造器用于创建一个新的数组。通常,我们推荐使用对象字面量创建数组,但是当想要创建一个长度为 8 得数组得时候,

//使用构造器
var a = Array(8) // [undefined * 8 ]
//使用对象构造器
 var b = []
 b.length = 8 //[undefined * 8 ]

大家可能发现 这里 使用得 Array(8) 而不是 new Array(8),这会不会有什么影响,这得益于Array构造器内部对this指针得判断, ELS5_HTML规范是这么说的:

When Array is called as a function rather than as a constructor, it creates and initialises a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

//谷歌翻译

当将Array调用为函数而不是构造函数时,它将创建并初始化一个新的Array对象。 因此,函数调用Array(…)等效于具有相同参数的对象创建表达式new Array(…)。

从规范来看,浏览器内部大致做了如下类似得实现:

function Array(){
	//如果 this 不是 Array 得实例,那就重新new 一个实例
	if(!(this instanceof arguments.callee)){
		return new arguments.callee()
	}
}

Array.of

Array.of用于将参数一次转化为数组中得一项,然后返回这个新数组,而不管这个参数数字还是其他。它基本上于Array构造器一致,唯一区别就在单个数字参数得处理上

Array.of(8.0)   //[8]
Array(8.0) //[empty * 8]

参数为多个,或单个参数不是数字时,Array.ofArray构造器等同

Array.of(8.0, 5); // [8, 5]
Array(8.0, 5); // [8, 5]

Array.of('8'); // ["8"]
Array('8'); // ["8"]

因此,若是需要使用数组包裹元素,推荐优先使用Array.of方法

即使其他版本浏览器不支持也不必担心,由于Array.ofArray构造器得这种高度相似性,实现一个polyfill十分简单

if(!Array.of){	
	Array.of = function(){
		return Array.prototype.slice.call(argument)
   }
}

Array.from

语法:Array.from(arrayLike[,processingFn[, thisArg]])

Array.from得设计初衷是快速便捷得基于其他对象创建新数组,准确来说就是聪一个类似数组可迭代对象创建一个新的数组实例**,说人话就是**,只要一个对象有迭代器,Array.from就能把它变成一个数组(返回新数组,不改变原对象)

从语法上看,Array.from拥有3个形参,

  • 第一个为类数组得对象,必选。
  • 第二个为加工函数,新生成得数组会经过该函数的加工在返回,可选。
  • 第三个为 this作用域,表示加工函数执行时this的值,可选
var 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)
//repeat() 构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本。

执行结果如下

a 0 {0: "a", 1: "b", 2: "c", length: 3} 2

(3) ["aaa", "bbb", "ccc"]

可以看到加工函数的this作用域被 obj对象取代,也可以看到加工函数默认拥有俩个形参,分别为迭代器当前元素的值和其索引

注意,一旦使用加工函数,必须明确指定返回值,否则将隐式返回undefined,最终生成的数组也会变成一个只包含若干个undefined元素的空数组

(3) [undefined, undefined, undefined]

实际上,如果不需要指定this,加工函数完全可以是一个箭头函数

Array.from(obj,(value,index)=>value.repeat(3))

除了上述obj对象以外,拥有迭代器的对象还包括这些:String,Set,Map,arguments等,Array.from统统可以处理

//String
Array.from('abc') //["a", "b", "c"]
//Set
Array.from(new Set(['abc','def']))   //['abc','def']
//Map
Array.from(new Map([[1, 'abc'], [2, 'def']]))) 
//[[1, 'abc'], [2, 'def']]

//天生的类数组对象`arguments`
function fn(){
	return Array.from(arguments)
}
fn(1,2,3)   // [1,2,3]
Array.from扩展
//比如说生成一个从0到指定数字的新数组
Array.from({length: 10}, (v, i) => i); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Array.isArray

顾名思义,Array.isArray用来判断一个变量是否数组类型。 在ES5提供该方法之前,我们至少有如下5种方式去判断一个值是否数组:

var a = []
// 1 、基于 instanceof
a instanceof Array
//2、基于 构造函数 constructor
a.constructor === Array
//3、基于Objcet.prototype.isPrototypeof
Array.prototype.isPrototypeOf(a);
//4、基于getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype;
// 5.基于Object.prototype.toString
Object.prototype.toString.apply(a) === '[object Array]';

注:除了Object.prototype.toString外,其他方法都不能正确判断变量的类型,且看:

var a = {
  __proto__: Array.prototype
};
// 分别在控制台试运行以下代码
// 1.基于instanceof
a instanceof Array; // true
// 2.基于constructor
a.constructor === Array; // true
// 3.基于Object.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(a); // true
// 4.基于getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype; // true

不仅如此,我们还知道,Array是堆数据,变量指向的只是它的引用地址,因此每个页面的Array对象引用的地址都是不一样的。iframe中声明的数组,它的构造函数是iframe中的Array对象。如果在iframe声明了一个数组x,将其赋值给父页面的变量y,那么在父页面使用y instanceof Array ,结果一定是false的。而最后一种返回的是字符串,不会存在引用问题。实际上,多页面或系统之间的交互只有字符串能够畅行无阻。

所以推荐使用最后一种办法 Object.prototype.toString.apply(a) === '[object Array]';

相反,使用Array.isArray则非常简单,如下:

Array.isArray([])   //true
Array.isArray({0:'a',length:1})

实际上 通过Object.prototype.toString去判断一个值的类型,也是各大主流库的标准。因此Array.isArraypolyfill通常长这样

  • Polyfill是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。
if(!Array.isArray){
	Array.isArray = function(){
		return Object.prototype.toString.call(arg) === '[object Array]'
	}
}

原型

继承的常识告诉我们,js中所有的数组方法均来自于Array.prototype,和其他构造函数一样,你可以通过扩展Arrayprototype属性上的方法来给所有数组实例增加方法

Array.prototype本身就是一个数组

Array.isArray(Array.prototype) //true
console.log(Array.prototype.length)  //0


console.log([].__proto__.length);// 0
console.log([].__proto__);// [Symbol(Symbol.unscopables): Object]

方法

数组原型提供的方法非常之多,主要分为三种,一种是改变自身值,一种是不会改变自身值,另外一种是遍历方法。

由于 Array.prototype 的某些属性被设置为[[DontEnum]],因此不能用一般的方法进行遍历,我们可以通过如下方式获取 Array.prototype 的所有方法:

Object.getOwnPropertyNames(Array.prototype)
//(33) ["length", "constructor", "concat", "copyWithin", "fill", "find", "findIndex", "lastIndexOf", "pop", "push", "reverse", "shift", "unshift", "slice", "sort", "splice", "includes", "indexOf", "join", "keys", "entries", "values", "forEach", "filter", "flat", "flatMap", "map", "every", "some", "reduce", "reduceRight", "toLocaleString", "toString"]

改变自身值的方法(9个)

基于ES6,改变自身值的方法一共有 9 个,分别为poppushreverseshiftsortspliceunshift,以及俩个ES6新增的方法copyWithinfill

对于能改变自身值得数组方法,日常开发中需要特别注意,尽量避免在循环遍历中去改变原数组得项

栈的概念

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除的一端称为栈顶,另一端称为栈底。栈中数据元素遵循先进后出原则。
压栈:栈的插入操作叫做入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶

鸭式辨型

**鸭式辨型:**像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子

pop

pop()方法删除一个数组中得最后得一个元素,并且返回这个元素,如果是栈得话,这个过程就栈顶弹出

  • 栈:后进先出 最后插入的元素最先出来
  • 队列:先进先出:最先插入的元素最先出来
var array = ['cat','dog','cow','chicken','mouse']
var item = array.pop()
console.log(array); // ["cat", "dog", "cow", "chicken"]
console.log(item); // mouse

由于设计上的巧妙,pop方法可以应用在类数组对象上,即 鸭式辨型,如下:

var o = {0:"cat", 1:"dog", 2:"cow", 3:"chicken", 4:"mouse", length:5}
var item = Array.prototype.pop.call(o);
console.log(o); // Object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", length: 4}
console.log(item); // mouse

但如果类数组对象不具有length属性,那么该对象将被创建length属性,length值为0.

var o = {0:"cat", 1:"dog", 2:"cow", 3:"chicken", 4:"mouse"}
var item = Array.prototype.pop.call(o);
console.log(array); // Object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", 4: "mouse", length: 0}
console.log(item); // undefined

push

push()方法添加一个或者多个元素到数组末尾,并且返回数组新的长度,如果是栈的话,这个过程就是栈顶压入

语法:arr.push(element1,...,elementN)

var array = ["football", "basketball", "volleyball", "Table tennis", "badminton"];
var i = array.push("golfball");
console.log(array); // ["football", "basketball", "volleyball", "Table tennis", "badminton", "golfball"]
console.log(i); // 6

pop方法一样,push方法也可以应用到类数组对象上,如果length不能被转成一个数值或者不存在length属性时,则插入的元素索引为0,且length属性不存在时,将会创建它。

var o = {0:"football", 1:"basketball"};
var i = Array.prototype.push.call(o, "golfball");
console.log(o); // Object {0: "golfball", 1: "basketball", length: 1}
console.log(i); // 1

实际上,push方法是根据length属性来决定从哪里开始插入给定的值

var array = ["football", "basketball"];
var array2 = ["volleyball", "golfball"];
var i = Array.prototype.push.apply(array,array2);
console.log(array); // ["football", "basketball", "volleyball", "golfball"]
console.log(i); // 4

reverse

reverse()方法颠倒数组中元素的位置,第一个会成为最后一个,最后一个会成为第一个,该方法返回对数组的引用

语法:arr.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

同上,reverse 也是鸭式辨型的受益者,颠倒元素的范围受length属性制约。如下:

var o = {0:"a", 1:"b", 2:"c", length:2};
var o2 = Array.prototype.reverse.call(o);
console.log(o); // Object {0: "b", 1: "a", 2: "c", length: 2}
console.log(o === o2); // true

如果 length 属性小于2 或者 length 属性不为数值,那么原类数组对象将没有变化。即使 length 属性不存在,该对象也不会去创建 length 属性。特别的是,当 length 属性较大时,类数组对象的『索引』会尽可能的向 length 看齐。如下:

var o = {0:"a", 1:"b", 2:"c",length:100};
var o2 = Array.prototype.reverse.call(o);
console.log(o); // Object {97: "c", 98: "b", 99: "a", length: 100}
console.log(o === o2); // true

shift

shift()方法删除数组的第一个元素,并返回这个元素。如果是栈的话,这个过程就是栈底弹出

语法:arr.shift()

var array = [1,2,3,4,5]
var item = array.shift()
console.log(array); // [2,3,4,5]
console.log(item); // 1

同样受益于鸭式辨型,对于类数组对象,shift仍然能够处理。如下:

var o = {0:"a", 1:"b", 2:"c", length:3};
var item = Array.prototype.shift.call(o);
console.log(o); // Object {0: "b", 1: "c", length: 2}
console.log(item); // a

如果类数组对象length属性不存在,将添加length属性,并初始化为0。如下:

var o = {0:"a", 1:"b", 2:"c"};
var item = Array.prototype.shift.call(o);
console.log(o); // Object {0: "a", 1: "b", 2:"c" length: 0}
console.log(item); // undefined

unshift

unshift()方法用于在数组开始处插入一些元素(就像是栈底插入),并返回数组新长度

语法:arr.unshift(ele1,...,eleN)

var array = ["red", "green", "blue"];
var length = array.unshift("yellow");
console.log(array); // ["yellow", "red", "green", "blue"]
console.log(length); // 

如果给unshift方法传入一个数组呢?

var array = ["red", "green", "blue"];
var length = array.unshift(["yellow"]);
console.log(array); // [["yellow"], "red", "green", "blue"]
console.log(length); // 4, 可见数组也能成功插入

同上,unshift也受益于鸭式辨型,呈上栗子:

var o = {0:"red", 1:"green", 2:"blue",length:3};
var length = Array.prototype.unshift.call(o,"gray");
console.log(o); // Object {0: "gray", 1: "red", 2: "green", 3: "blue", length: 4}
console.log(length); // 4

注意:如果类数组对象不指定length属性,则返回结果是这样的 Object {0: "gray", 1: "green", 2: "blue", length: 1},shift会认为数组长度为0,此时将从对象下标为0的位置开始插入,相应位置属性将被替换,此时初始化类数组对象的length属性为插入元素个数。

var o = {0:"red", 1:"green", 2:"blue"};
var length = Array.prototype.unshift.call(o,"gray");
console.log(o); 
console.log(length); // 4
// {0: "gray", 1: "green", 2: "blue", length: 1}

sort

sort()方法对数组元素进行排序,并返回这个数组。

语法:arr.sort([comparefn])

comparefn是可选的,如果省略,数组元素将按照各自转换为字符串的Unicode(万国码)位点顺序排序,例如”Boy”将排到”apple”之前。当对数字排序的时候,25将会排到8之前,因为转换为字符串后,”25”将比”8”靠前。例如:

var array = ["apple","Boy","Cat","dog"];
var array2 = array.sort();
console.log(array); // ["Boy", "Cat", "apple", "dog"]
console.log(array2 == array); // true

array = [10, 1, 3, 20];
var array3 = array.sort();
console.log(array3); // [1, 10, 20, 3]

splice

splice()方法用新元素替换就元素的方式来修改数组。他是一个常用的方法,复杂的数组操作场景通常都会有他的身影,特别是需要维持原数组引用时,就地删除或者新增元素,splice是最合适的

语法:arr.splice(start,delCount[,item1,item2[,...]])

start指定从哪一位开始修改内容。如果超过了数组长度,则从数组末尾开始天机啊内容;然后过是赋值,则起指定的索引位置等同于length + start(length为数组的长度),表示从数组末尾开始的第 - start

deleteCount指定要删除的元素个数,若等于 0 ,则不删除。这种情况下,至少应该添加一位新元素,若大于start之后的元素总和,则start及之后的元素都会被删除

itemN指定新增的元素,如果缺省,则该方法只删除数组元素。返回值 由原数组中被删除元素组成的数组,如果没有删除,则返回一个空数组。

var array = ["apple","boy"];
var splices = array.splice(1,1);
console.log(array); // ["apple"]
console.log(splices); // ["boy"] ,可见是从数组下标为1的元素开始删除,并且删除一个元素,由于itemN缺省,故此时该方法只删除元素

array = ["apple","boy"];
splices = array.splice(2,1,"cat");
console.log(array); // ["apple", "boy", "cat"]
console.log(splices); // [], 可见由于start超过数组长度,此时从数组末尾开始添加元素,并且原数组不会发生删除行为

array = ["apple","boy"];
splices = array.splice(-2,1,"cat");
console.log(array); // ["cat", "boy"]
console.log(splices); // ["apple"], 可见当start为负值时,是从数组末尾开始的第-start位开始删除,删除一个元素,并且从此处插入了一个元素

array = ["apple","boy"];
splices = array.splice(-3,1,"cat");
console.log(array); // ["cat", "boy"]
console.log(splices); // ["apple"], 可见即使-start超出数组长度,数组默认从首位开始删除

array = ["apple","boy"];
splices = array.splice(0,3,"cat");
console.log(array); // ["cat"]
console.log(splices); // ["apple", "boy"], 可见当deleteCount大于数组start之后的元素总和时,start及之后的元素都将被删除

同上, splice一样受益于鸭式辨型, 比如:

var o = {0:"apple",1:"boy",length:2};
var splices = Array.prototype.splice.call(o,1,1);
console.log(o); // Object {0: "apple", length: 1}, 可见对象o删除了一个属性,并且length-1
console.log(splices); // ["boy"]

注意:如果类数组对象没有length属性,splice将为该类数组对象添加length属性,并初始化为0。

如果需要删除数组中一个已存在的元素,可参考如下:

var array = ['a','b','c'];
array.splice(array.indexOf('b'),1);

copyWithin(ES6)

copyWithin() 方法基于ECMAScript 2015(ES6)规范,用于数组内元素之间的替换,即替换元素和被替换元素均是数组内的元素。

语法:*arr.copyWithin(target, start[, end = this.length])*

taget指定被替换元素的索引,start 指定替换元素起始的索引,end 可选,指的是替换元素结束位置的索引。

如果start为负,则其指定的索引位置等同于length+start,length为数组的长度。end也是如此。

注:目前只有Firefox(版本32及其以上版本)实现了该方法。

var array = [1,2,3,4,5]; 
var array2 = array.copyWithin(0,3);
console.log(array===array2,array2); // true [4, 5, 3, 4, 5]

var array = [1,2,3,4,5]; 
console.log(array.copyWithin(0,3,4)); // [4, 2, 3, 4, 5]

var array = [1,2,3,4,5]; 
console.log(array.copyWithin(0,-2,-1)); // [4, 2, 3, 4, 5]

同上,copyWithin一样受益于鸭式辨型,例如:

var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
var o2 = Array.prototype.copyWithin.call(o,0,3);
console.log(o===o2,o2); // true Object { 0=4,  1=5,  2=3,  更多...}

如需在Firefox之外的浏览器使用copyWithin方法,请参考 Polyfill

fill(ES6)

fill() 方法基于ECMAScript 2015(ES6)规范,它同样用于数组元素替换,但与copyWithin略有不同,它主要用于将数组指定区间内的元素替换为某个值。

语法:*arr.fill(value, start[, end = this.length])*

value 指定被替换的值,start 指定替换元素起始的索引,end 可选,指的是替换元素结束位置的索引。

如果start为负,则其指定的索引位置等同于length+start,length为数组的长度。end也是如此。

注:目前只有Firefox(版本31及其以上版本)实现了该方法。

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
// 其他的举例请参考copyWithin

同上,fill 一样受益于鸭式辨型,例如:

var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
var o2 = Array.prototype.fill.call(o,10,0,2);
console.log(o===o2,o2); true Object { 0=10,  1=10,  2=3,  更多...}

如需在Firefox之外的浏览器使用fill方法,请参考 Polyfill

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值