Skip to content

Latest commit

 

History

History
349 lines (286 loc) · 10.4 KB

09.处理集合.md

File metadata and controls

349 lines (286 loc) · 10.4 KB

09.处理集合

  • 创建、修改数组
  • 使用、复用数组函数
  • 使用 Map 创建字典
  • 使用 Set 创建不重复的对象的集合

数组

创建数组

使用数组字面量创建数组优于数组构造函数。主要原因很简单: []new Array()(2 个字符与 11 个字符)。此外,由于 JavaScript 的高度动态特性,无法阻止修改内置的 Array 构造函数,也就意味着 new Array()创建的不一定是数组。因此,推荐坚持使用数组字面量。

数组操作

  • push:在数组末尾添加元素。
  • unshift: 在数组开头添加元素。
  • pop: 从数组末尾删除元素。
  • shift: 从数组开头删除元素。
  • slice
  • splice

性能考虑:pop 和 push 与 shift 和 unshift

poppush 方法只影响数组最后一个元素: pop 移除最后一个元素,push 在数组末尾增加元素。shiftunshift 方法修改第一个元素,之后的每一个元素的索引都需要调整。因此,poppush 方法比 shiftunshift 要快很多,非特殊情况不建议使用 shiftunshift 方法。

数组常用操作

  • 遍历数组。
  • 基于现有的数组元素映射创建新数组。
  • 验证数组元素是否匹配指定的条件。
  • 查找特定数组元素。
  • 聚合数组,基于数组元素计算(例如,计算数组元素之和)。

数组遍历

  • for
  • forEach:数组内置方法

映射数组

提取 weapon 数组的粗略方法:

const ninjas = [
  { name: 'Yagyu', weapon: 'shuriken' },
  { name: 'Yoshi', weapon: 'katana' },
  { name: 'Kuma', weapon: 'wakizashi' }
];
const weapons = [];
ninjas.forEach(ninja => {
  weapons.push(ninja.weapon);
});

映射数组:

const ninjas = [
  { name: 'Yagyu', weapon: 'shuriken' },
  { name: 'Yoshi', weapon: 'katana' },
  { name: 'Kuma', weapon: 'wakizashi' }
];
const weapons = ninjas.map(ninja => ninja.weapon); // map 函数对数组的每个元素执行回调函数,使用返回值创建新数组

测试数组元素

使用 every 和 some 方法测试数组:

const ninjas = [
  { name: 'Yagyu', weapon: 'shuriken' },
  { name: 'Yoshi' },
  { name: 'Kuma', weapon: 'wakizashi' }
];
const allNinjasAreNamed = ninjas.every(ninja => 'name' in ninja); // true
const allNinjasAreArmed = ninjas.every(ninja => 'weapon' in ninja); // false
const someNinjasAreArmed = ninjas.some(ninja => 'weapon' in ninja); // true

数组查找

const ninjas = [
  { name: 'Yagyu', weapon: 'shuriken' },
  { name: 'Yoshi' },
  { name: 'Kuma', weapon: 'wakizashi' }
];
const ninjaWithWakizashi = ninjas.find(ninja => {
  return ninja.weapon === 'wakizashi';
});
console.log(
  ninjaWithWakizashi.name === 'Kuma' &&
    ninjaWithWakizashi.weapon === 'wakizashi'
);
const ninjaWithKatana = ninjas.find(ninja => {
  return ninja.weapon === 'katana';
});
console.log(ninjaWithKatana === undefined);
const armedNinjas = ninjas.filter(ninja => 'weapon' in ninja);
console.log(armedNinjas.length === 2);
console.log(armedNinjas[0].name === 'Yagyu' && armedNinjas[1].name === 'Kuma');

查找数组索引

findIndex()

const ninjas = ['Yagyu', 'Yoshi', 'Kuma', 'Yoshi'];
console.log(ninjas.indexOf('Yoshi') === 1);
console.log(ninjas.lastIndexOf('Yoshi') === 3);
const yoshiIndex = ninjas.findIndex(ninja => ninja === 'Yoshi');
console.log(yoshiIndex === 1);

findIndex 方法接收回调函数,并返回第一个回调函数返回 true 的元素。本质上 findIndexfind 方法类似,唯一的区别是 find 方法返回元素本身,而 findIndex 方法返回元素的索引。

数组排序

const ninjas = [{ name: 'Yoshi' }, { name: 'Yagyu' }, { name: 'Kuma' }];
ninjas.sort(function(a, b) {
  if (a.name < b.name) {
    return -1;
  }
  if (a.name > b.name) {
    return 1;
  }
  return 0;
});

合计数组元素

使用 reduce 合计数组元素:

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((aggregated, number) => aggregated + number, 0);

reduce 方法接收初始值,对数组每个元素执行回调函数,回调函数接收上一次回调结果以及当前的数组元素作为参数。最后一次回调函数的结果作为 reduce 的结果。

复用内置的数组函数

<body>
  <input id="first"/>
  <input id="second"/>
  <script>
    const elems = {
      length: 0,
      add: function(elem) {
        Array.prototype.push.call(this, elem);
      },
      gather: function(id) {
        this.add(document.getElementById(id));
      },
      find: function(callback) {
        return Array.prototype.find.call(this, callback);
      }
    };
    elems.gather("first");
    console.log(elems.length === 1 && elems[0].nodeType);
    elems.gather("second");
    console.log(elems.length === 2 && elems[1].nodeType);
    elems.find(elem => elem.id === "second");
    console.log(found && found.id === "second");
  </script>
</body>

Map

别把对象当做 Map

访问对象未显式定义的属性

const dictionary = {
  ja: {
    'Ninjas for hire': 'レンタル用の忍者'
  },
  zh: {
    'Ninjas for hire': '忍者出租'
  },
  ko: {
    'Ninjas for hire': '고용 닌자'
  }
};
console.log(dictionary.ja['Ninjas for hire'] === 'レンタル用の忍者'); // true
console.log(typeof dictionary.ja['constructor'] === 'undefined'); // false

原型继承属性以及 key 仅支持字符串,所以通常不能使用对象作为 map。由于这种限制,ECMAScript 委员会定义了一个全新类型: Map

创建 map

const ninjaIslandMap = new Map();
const ninja1 = { name: 'Yoshi' };
const ninja2 = { name: 'Hattori' };
const ninja3 = { name: 'Kuma' };
ninjaIslandMap.set(ninja1, { homeIsland: 'Honshu' });
ninjaIslandMap.set(ninja2, { homeIsland: 'Hokkaido' });

除了 getset 方法之外,map

  • size:创建了多少个映射
  • has: 判断指定的 key 是否存在
  • delete:删除映射

遍历 map

const directory = new Map();
directory.set('Yoshi', '+81 26 6462');
directory.set('Kuma', '+81 52 2378 6462');
directory.set('Hiro', '+81 76 277 46');
for (let item of directory) {
  console.log(item[0] !== null, 'Key:' + item[0]);
  console.log(item[1] !== null, 'Value:' + item[1]);
}
for (let key of directory.keys()) {
  console.log(key !== null, 'Key:' + key);
  console.log(directory.get(key) != null, 'Value:' + directory.get(key));
}
for (var value of directory.values()) {
  console.log(value !== null, 'Value:' + value);
}

Set

集合中的每个元素都是唯一的(每个 元素只能出现一次),这种集合称为 Set。

通过对象模拟 Set

function Set() {
  this.data = {};
  this.length = 0;
}
Set.prototype.has = function(item) {
  return typeof this.data[item] !== 'undefined';
};
Set.prototype.add = function(item) {
  if (!this.has(item)) {
    this.data[item] = true;
    this.length++;
  }
};
Set.prototype.remove = function(item) {
  if (this.has(item)) {
    delete this.data[item];
    this.length--;
  }
};
const ninjas = new Set();
ninjas.add('Hattori');
ninjas.add('Hattori');
console.log(ninjas.has('Hattori') && ninjas.length === 1);
ninjas.remove('Hattori');
console.log(!ninjas.has('Hattori') && ninjas.length === 0);

创建 Set

const ninjas = new Set(['Kuma', 'Hattori', 'Yagyu', 'Hattori']);
console.log(ninjas.has('Hattori'));
console.log(ninjas.size === 3);
console.log(!ninjas.has('Yoshi'));
ninjas.add('Yoshi');
console.log(ninjas.has('Yoshi'));
console.log(ninjas.size === 4);
console.log(ninjas.has('Kuma'));
ninjas.add('Kuma');
console.log(ninjas.size === 4);
for (let ninja of ninjas) {
  console.log(ninja !== null, ninja);
}

Map 和数组类似,Set 也是集合,因此可以使用 for-of 循环进行遍历。

并集

两个集合的并集指的是创建一个新的集合,同时包含 A 和 B 中的所有成员。

const ninjas = ['Kuma', 'Hattori', 'Yagyu'];
const samurai = ['Hattori', 'Oda', 'Tomoe'];
const warriors = new Set([...ninjas, ...samurai]);
assert(warriors.has('Kuma'), 'Kuma is here');
assert(warriors.has('Hattori'), 'And Hattori');
assert(warriors.has('Yagyu'), 'And Yagyu');
assert(warriors.has('Oda'), 'And Oda');
assert(warriors.has('Tomoe'), 'Tomoe, last but not least');
assert(warriors.size === 5, 'There are 5 warriors in total');

交集

两个集合的交集指的是创建新集合,该集合中只包含集合 A 与 B 中同时出现的成 员。

const ninjas = new Set(['Kuma', 'Hattori', 'Yagyu']);
const samurai = new Set(['Hattori', 'Oda', 'Tomoe']);
const ninjaSamurais = new Set([...ninjas].filter(ninja => samurai.has(ninja)));
assert(ninjaSamurais.size === 1, "There's only one ninja samurai");
assert(ninjaSamurais.has('Hattori'), 'Hattori is his name');

差集

两个集合的差集指的是创建新集合,只包含存在于集合 A、但不在集合 B 中的元素。

const ninjas = new Set(['Kuma', 'Hattori', 'Yagyu']);
const samurai = new Set(['Hattori', 'Oda', 'Tomoe']);
const pureNinjas = new Set([...ninjas].filter(ninja => !samurai.has(ninja)));
assert(pureNinjas.size === 2, "There's only one ninja samurai");
assert(pureNinjas.has('Kuma'), 'Kuma is a true ninja');
assert(pureNinjas.has('Yagyu'), 'Yagyu is a true ninja');

小结

  • 数组是特殊的对象,具有 length 属性,原型是 Array.prototype
  • 可以使用数组字面量([])或 Array 构造函数创建数组。
  • 通过使用数组对象的方法可以修改数组的内容。
    • 使用 pushpop 方法从数组结束位置添加或删除元素。
    • 使用 shiftunshift 方法从数组起始位置添加或删除元素。
    • splice 方法可以从任意位置添加或删除元素。
  • 数组可以访问很多有用的方法。
    • map 方法可对数组成员调用回调函数,并使用调用结果创建新数组。
    • every 与 some 方法检测全部或部分元素是否匹配某些条件。
    • find 与 filter 方法查找满足某些条件的元素。
    • sort 方法对数组排序。
    • reduce 方法将数组成员合计为一个值。
  • 可以在自定义对象上,显式定义对象方法,使用 callapply 方法对数组的方法进行复用。
  • Map 和字典是包含 keyvalue 映射关系的对象。
  • JavaScript 中的对象是糟糕的 map,只能使用字符串类型作为 key,并且存在访问原型属性的风险。因此,使用内置的 Map 集合。
  • 可以使用 for...of 循环遍历 Map 集合。
  • Set 成员的值都是唯一的。