深入理解ES6 改进的数组功能

Set集合与Map集合
迭代器(Iterator)和生成器(Generator)
改进的数组功能

改进的数组功能

1.创建数组

ES6之前,创建数组的方式主要有两种,一种是调用Array构造函数,另一种是用数组字面量语法,这两种方法均需列举数组中的元素,功能非常受限。如果想将一个类数组对象(具有数值型索引和length属性的对象)转换为数组,可选的方法也十分有限,经常需要编写额外的代码。为了进一步简化JS数组的创建过程,ES6新增了Array.of()Array.from()两个方法

//使用new Array() 创建数组
var arr1 = new Array();     // 空数组
var arr2 = new Array('苹果', '橘子', '香蕉', '桃子');      //含有4个元素
//使用字面量来创建数组
var arr1 = [];      //空数组
var arr2 = ['苹果', '橘子', '香蕉', '桃子'];     //含有4个元素

[Array.of()]

ES6之所以向JS添加新的创建方法,是要帮助开发者们规避通过Array构造函数创建数组时的怪异行为

let items =new Array(2);
console.log(items.length); // 2
console.log(items[0]);// undefined
console.log(items[1]);// undefined
items =new Array("2");
console.log(items.length); // 1
console.log(items[0]);// "2"
items =new Array(1, 2);
console.log(items.length); // 2
console.log(items[0]);// 1
console.log(items[1]);// 2
items =new Array(3, "2");
console.log(items.length); // 2
console.log(items[0]);// 3
console.log(items[1]);// "2"
  • 如果给Array构造函数传入一个数值型的值,那么数组的length属性会被设为该值。如果传入多个值,此时无论这些值是不是数值型的,都会变为数组的元素。这个特性令人感到困惑,不可能总是注意传入数据的类型,所以存在一定的风险
  • ES6通过引入Array.of()方法来解决这个问题。Array.of()Array构造函数的工作机制类似,只是不存在单一数值型参数值的特例,无论有多少参数,无论参数是什么类型的,Array.of()方法总会创建一个包含所有参数的数组
let items = Array.of();
console.log(items.length); // 0
items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]);// 1
console.log(items[1]);// 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]);// 2
items = Array.of("2");
console.log(items.length); // 1
console.log(items[0]);// "2"

[Array.from()]

类数组对象
类数组对象首先它是一个对象,但是有数组的某些特点。怎么构造类数组对象呢?先把对象中的键都改为从0开始递增的值,然后我们需要定义一个length属性,手动模拟数组的length属性。
const arrObj = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
JS不支持直接将非数组对象转换为真实数组,如果要把它当作数组使用则必须先转换该对象的类型。在ES5中,可能需要编写如下函数来把类数组对象转换为数组

const arguments = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
function makeArray(arrayLike) { 
  var result = [];   
  for(vari = 0, len = arrayLike.length; i < len; i++) {
    result.push(arrayLike[i]);
  }   
  return result;
}
const args = makeArray(arguments);  // ['dog', 'cat', 'rabbit']
  • 这种方法先是手动创建一个result数组,再将arguments对象里的每一个元素复制到新数组中。尽管这种方法有效,但需要编写很多代码才能完成如此简单的操作。最终,开发者们发现了一种只需编写极少代码的新方法,调用数组原生的slice()方法可以将非数组对象转换为数组
const arguments = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
const s1 = Array.prototype.slice.call(arguments); // ['dog', 'cat', 'rabbit']
const s2 = Array.prototype.splice.call(arguments, 0); // ['dog', 'cat', 'rabbit']
const s3 = Array.prototype.concat.apply([], arguments); // ['dog', 'cat', 'rabbit']

  • Array.from()方法可以接受可迭代对象或类数组对象作为第一个参数,最终返回一个数组
const arguments = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
const s = Array.from(arguments) ; // ['dog', 'cat', 'rabbit'] 

[映射转换]

如果想要进一步转化数组,可以提供一个映射函数作为Array.from()的第二个参数,这个函数用来将类数组对象中的每一个值转换成其他形式,最后将这些结果储存在结果数组的相应索引中

const numbers = Array.from([1,2,3], value => value + 1); // [2,3,4]
  • 在这段代码中,为Array.from()方法传入映射函数(value)=>value+1,数组中的每个元素在储存前都会被加1。如果用映射函数处理对象,也可以给Array.from()方法传入第三个参数来表示映射函数的this值
let helper ={
  diff: 1,
  add(value) {    
    return value +this.diff;
}
};
function translate() {    
  return Array.from(arguments, helper.add, helper);
}
let numbers = translate(1, 2, 3);
console.log(numbers); // 2,3,4
  • 此示例传入helper.add()作为转换用的映射函数,由于该方法使用了this.diff属性,因此需要为Array.from()方法提供第三个参数来指定this的值,从而无须通过调用bind()方法或其他方式来指定this的值了用Array.from()转换可迭代对象
  • Array.from()方法可以处理类数组对象和可迭代对象,也就是说该方法能够将所有含有Symbol.iterator属性的对象转换为数组
let numbers = {   
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};
let numbers2 = Array.from(numbers, (value) => value + 1);
console.log(numbers2); /
  • 由于numbers是一个可迭代对象,因此可以直接将它传入Array.from()来转换成数组。此处的映射函数将每一个数字加1,所以结果数组最终包含的值为2、3和4

[注意]如果一个对象既是类数组又是可迭代的,那么Array.from()方法会根据迭代器来决定转换哪个值

2.为所有数组添加的新方法

ES6延续了ES5的一贯风格,也为数组添加了几个新的方法:includes()方法返回一个布尔值,表示数组是否包含给定的值;find()方法和findIndex()方法可以协助开发者在数组中查找任意值;fill()方法和copyWithin()方法的灵感则来自于定型数组的使用过程,定型数组也是ES6中的新特性,是一种只包含数字的数组

[1, 2, 3].includes(2)// true
[1, 2, 3].includes(4)// false
[1, 2, NaN].includes(NaN)// true
// 第二个参数表示起始位置,默认0
// 如果第二个参数为负数,则表示倒数的位置
// 如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始
[1, 2, 3].includes(3, 3);// false
[1, 2, 3].includes(3, -1);// true
  • 没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值 。indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判
if(arr.indexOf(el) !== -1) { 
 // ...
}
// 会有误判
[NaN].indexOf(NaN)// -1

另外,MapSet 数据结构有一个has方法,需要注意与includes区分
1、Map结构的has方法,是用来查找键名的,比如Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target,propertyKey)
2、Set结构的has方法,是用来查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

[fill()]

let numbers = [1, 2, 3, 4];
numbers.fill(1); // [1,1,1,1]
numbers.fill(99, 2); // [1,2,99,99]
// 从索引为2的位置开始,没有结束索引,即填充至最后
numbers.fill(99, 1, 3); // [1,99,99,1]
// 从索引为1的位置开始,到索引为3 - 1的位置

[注意]如果开始索引或结束索引为负值,那么这些值会与数组的length属性相加来作为最终位置。例如,如果开始位置为-1,那么索引的值实际为array.length-1array为调用fill()方法的数组

[copyWithin()]

copyWithin()方法与fill()方法相似,其也可以同时改变数组中的多个元素。fill()方法是将数组元素赋值为一个指定的值,而copyWithin()方法则是从数组中复制元素的值。调用copyWithin()方法时需要传入两个参数:一个是该方法开始填充值的索引位置,另一个是开始复制值的索引位置

numbers = [1, 2, 3, 4, 5, 6, 7, 8 ,9, 10];
// 从索引 4的位置开始粘贴
// 从数组索引 0 的位置开始复制数据
numbers.copyWithin(4, 0); // [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
  • 默认情况下,copyWithin()会一直复制直到数组末尾的值,但是可以提供可选的第三个参数来限制被重写元素的数量。第三个参数是不包含结束索引,用于指定停止复制值的位置
numbers = [1, 2, 3, 4, 5, 6, 7, 8 ,9, 10];
// 从索引 4的位置开始粘贴
// 从数组索引 0 的位置开始复制数据
// 在遇到索引 1 时停止复制
numbers.copyWithin(4, 0, 1); // [1, 2, 3, 4, 1, 6, 7, 8, 9, 10]
  • 在这个示例中,由于可选的结束索引被设置为了1,因此只有位于索引0的值被复制了,数组中的最后一个元素保持不变

[注意]正如fill()方法一样,copyWithin()方法的所有参数都接受负数值,并且会自动与数组长度相加来作为最终使用的索引

3.定型数组

定型数组是一种用于处理数值类型(正如其名,不是所有类型)数据的专用数组,支持存储和操作以下8种不同的数值类型
无符号的8位整数(uint8)
有符号的16位整数(int16)
无符号的16位整数(uint16)
有符号的32位整数(int32)
无符号的32位整数(uint32)
32位浮点数(float32)
64位浮点数(float64)

不同点

定型数组与普通数组最重要的差别是:定型数组不是普通数组。它不继承自Array,通过Array.isArray()方法检查定型数组返回的是false

【数组缓冲区】
let buffer = New ArrayBuffer(10) // 分配10字节
视图操作
view = new DataView(buffer);

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容