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 callArray(…)
is equivalent to the object creation expressionnew 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.of
与Array
构造器等同
Array.of(8.0, 5); // [8, 5]
Array(8.0, 5); // [8, 5]
Array.of('8'); // ["8"]
Array('8'); // ["8"]
因此,若是需要使用数组包裹元素,推荐优先使用Array.of
方法
即使其他版本浏览器不支持也不必担心,由于Array.of
与Array
构造器得这种高度相似性,实现一个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.isArray
的polyfill
通常长这样
Polyfill
是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。
if(!Array.isArray){
Array.isArray = function(){
return Object.prototype.toString.call(arg) === '[object Array]'
}
}
原型
继承的常识告诉我们,js
中所有的数组方法均来自于Array.prototype
,和其他构造函数一样,你可以通过扩展Array
的prototype
属性上的方法来给所有数组实例增加方法
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 个,分别为pop
、push
、reverse
、shift
、sort
、splice
、unshift
,以及俩个ES6
新增的方法copyWithin
和fill
对于能改变自身值得数组方法,日常开发中需要特别注意,尽量避免在循环遍历中去改变原数组得项
栈的概念
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除的一端称为栈顶,另一端称为栈底。栈中数据元素遵循先进后出原则。
压栈:栈的插入操作叫做入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
鸭式辨型
**鸭式辨型:**像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子
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。