ES6 模拟实现Set Map

Set基本使用

初始化

Set 本身是一个构造函数,用来生成 Set 数据结构

var set = new Set();

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

let set = new Set([1, 2, 3, 4, 4]);
console.log(set);    // Set(4) {1, 2, 3, 4}


set = new Set(document.querySelectorAll('div'));
set.size // 56

set = new Set(new Set([1, 2, 3, 4, 4]));
console.log(set);     // Set(4) {1, 2, 3, 4}

属性和方法 

属性:

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

 操作方法:

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
let set = new Set();

console.log(set.add(1).add(2));    // Set(2) {1, 2}

console.log(set.delete(2));    // true

console.log(set.has(2));    // false

console.log(set.clear());    // undefined

console.log(set.size);        // 0

遍历操作 

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

let set = new Set(['a', 'b', 'c']);
 
for (let pair of set.entries()) {
  console.log(pair);
}
 
// ['a', 'a']
// ['b', 'b']
// ['c', 'c']
 
 
for (let key of set.keys()) {
  console.log(key);
}
 
// a
// b
// c
 
for (let value of set.values()) {
  console.log(value);
}
// a
// b
// c

set.forEach((value, key) => console.log(key + ': ' + value));
// a: a
// b: b
// c: c

可以看出Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。在 for-of 循环中,Set 集合的默认迭代器是 values() 方法 

实现Set

先实现 add、delete、has、clear、forEach 方法

/**
 * 模拟实现第一版
 */

function Set(data) {
  this.items = [];
  this.size = 0;

  data &&
    data.forEach(function (item) {
      this.add(item);
    }, this);
}

Set.prototype.add = function (value) {
  if (this.items.indexOf(value) == -1) {
    this.items.push(value);
    ++this.size;
  }
  return this;
};

Set.prototype.has = function (value) {
  return this.items.indexOf(value) !== -1;
};

Set.prototype.delete = function (value) {
  var index = this.items.indexOf(value);
  if (index == -1) return false;
  this.items.splice(index, 1);
  --this.size;
  return true;
};

Set.prototype.clear = function (value) {
  this.items = [];
  this.size = 0;
};

Set.prototype.forEach = function (callbackFn, thisArg) {
  thisArg = thisArg || window;
  for (var i = 0; i < this.items.length; i++) {
    callbackFn.call(thisArg, this.items[i], this.items[i], this);
  }
};

Set.length = 0;

 弥补indexOf的缺陷

实际上Set添加多个 NaN会去重 

let set = new Set();
set.add(NaN);
set.add(NaN);
console.log(set.size); // 1

但我们模拟的Set添加元素使用的是indexOf

因为indexOf本质上,还是使用 === 来进行比较,所以需要考虑NaN

var s = NaN;
s === NaN;    // false
console.log([NaN].indexOf(s));    // -1

 所以我们需要对 NaN 这个值进行单独的处理

处理的方式是当判断添加的值是 NaN 时,将其替换为一个独一无二的值,比如说一个很难重复的字符串类似于 @@NaNValue,当然了,说到独一无二的值,我们也可以直接使用 Symbol,代码如下:

即使用indexOf时判断下:

var NaNSymbol = Symbol("NaN");

var encodeVal = function (value) {
  return value !== value ? NaNSymbol : value;
};

var decodeVal = function (value) {
  return value === NaNSymbol ? NaN : value;
};

// ...


Set.prototype.add = function (value) {
  value = encodeVal(value);
  if (this.items.indexOf(value) == -1) {
    this.items.push(value);
    ++this.size;
  }
  return this;
};

Set.prototype.has = function (value) {
  return this.items.indexOf(encodeVal(value)) !== -1;
};

Set.prototype.delete = function (value) {
  var index = this.items.indexOf(encodeVal(value));
  if (index == -1) return false;
  this.items.splice(index, 1);
  --this.size;
  return true;
};


// ...

 实现 keys()、values()、entries() 

var NaNSymbol = Symbol("NaN");

var encodeVal = function (value) {
  return value !== value ? NaNSymbol : value;
};

var decodeVal = function (value) {
  return value === NaNSymbol ? NaN : value;
};

var makeIterator = function (array, iterator) {
  var nextIndex = 0;

  // new Set(new Set()) 会调用这里
  var obj = {
    next: function () {
      return nextIndex < array.length
        ? { value: iterator(array[nextIndex++]), done: false }
        : { value: void 0, done: true };
    },
  };

  // [...set.keys()] 会调用这里
  obj[Symbol.iterator] = function () {
    return obj;
  };

  return obj;
};

function forOf(obj, cb) {
  let iterable, result;

  if (typeof obj[Symbol.iterator] !== "function")
    throw new TypeError(obj + " is not iterable");
  if (typeof cb !== "function") throw new TypeError("cb must be callable");

  iterable = obj[Symbol.iterator]();

  result = iterable.next();
  while (!result.done) {
    cb(result.value);
    result = iterable.next();
  }
}

function Set(data) {
  this.items = [];
  this.size = 0;

  data && forOf(data, (item) => {
    this.add(item);
  });
}

Set.prototype.add = function (value) {
  value = encodeVal(value);
  if (this.items.indexOf(value) == -1) {
    this.items.push(value);
    ++this.size;
  }
  return this;
};

Set.prototype.has = function (value) {
  return this.items.indexOf(encodeVal(value)) !== -1;
};

Set.prototype.delete = function (value) {
  var index = this.items.indexOf(encodeVal(value));
  if (index == -1) return false;
  this.items.splice(index, 1);
  --this.size;
  return true;
};

Set.prototype.clear = function (value) {
  this.items = [];
  this.size = 0;
};

// keys() 和 values() 返回的是相同的迭代器
Set.prototype.values = Set.prototype.keys = function () {
  return makeIterator(this.items, function (value) {
    return decodeVal(value);
  });
};

// 键名与键值相同
Set.prototype.entries = function () {
  return makeIterator(this.items, function (value) {
    return [decodeVal(value), decodeVal(value)];
  });
};

// for of默认迭代器是 values() 方法 
Set.prototype[Symbol.iterator] = function () {
  return this.values();
};


Set.prototype.forEach = function (callbackFn, thisArg) {
  thisArg = thisArg || window;
  var iterator = this.entries();

  forOf(iterator, (item) => {
    callbackFn.call(thisArg, item[1], item[0], this);
  });
};

Set.length = 0;

 测试:

let set = new Set([1, 2, 3]);

set.add(NaN).add(NaN);


console.log([...set]);     // [1, 2, 3, NaN]


console.log([...set.keys()]);    // [1, 2, 3, NaN]

console.log([...set.values()]);    // [1, 2, 3, NaN]

console.log([...set.entries()]);    // [1, 2, 3, NaN]

set.forEach(function(value, key){
    console.log(key + ': ' + value);
})

// 1: 1
// 2: 2
// 3: 3
// NaN: NaN

Map基本使用

初始化

Map 本身是一个构造函数,用来生成 Map 数据结构

var map = new Map();

不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map构造函数的参数

let map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);
console.log(map);    // Map(2) {'name' => '张三', 'title' => 'Author'}


let set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
map = new Map(set);    // Map(2) {'foo' => 1, 'bar' => 2}

属性和方法 

属性:

  • Map.prototype.constructor:构造函数,默认就是Map函数。
  • Map.prototype.size:返回Map 结构的成员总数

 操作方法:

  • Map.prototype.set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
  • Map.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined
  • Map.prototype.delete(key):删除某个键,返回一个布尔值,表示删除是否成功。
  • Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • Map.prototype.clear():清除所有成员,没有返回值。
var map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

console.log(map.size); // 2
map.set('age', 20).set('age', 18);
console.log(map.has('name')); // true

console.log(map.get('name')); // "张三"

map.delete('title');

console.log(map.get('title')) // "Author"

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

// Key: name, Value: 张三
// Key: age, Value: 18

遍历操作 

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回所有成员的遍历器。
  • Set.prototype.forEach():遍历 Map 的所有成员。

let map = new Map()
 
map.set(0, 'a');
map.set(1, 'b');
map.set(2, 'c');
 
 
for (let pair of map.entries()) {
  console.log(pair);
}
 
// [0, 'a']
// [1, 'b']
// [2, 'c']
 
 
for (let key of map.keys()) {
  console.log(key);
}
 
// 0
// 1
// 2
 
for (let value of map.values()) {
  console.log(value);
}
 
// a
// b
// c

map.forEach(function(value, key, map) {
  console.log(key + ': ' + value);
});

// 0: a
// 1: b
// 2: c

可以看出Map 类型与数组类似

实现Map

function dealKey(key) {
  if (key === null) {
    return "NULL";
  } else if (key === undefined) {
    return "UNDEFINED";
  } else if (
    Object.prototype.toString.call(key) === "[object Object]" ||
    Object.prototype.toString.call(key) === "[object Array]"
  ) {
    return JSON.stringify(key);
  }
  return key.toString();
}

var makeIterator = function (o, iterator) {
  var keys = Object.keys(o);
  var nextIndex = 0;
  var obj = {
    next: function () {
      return nextIndex < keys.length
        ? { value: iterator(keys[nextIndex], o[keys[nextIndex++]]), done: false }
        : { value: void 0, done: true };
    },
  };

  obj[Symbol.iterator] = function () {
    return obj;
  };

  return obj;
};

function forOf(obj, cb) {
  let iterable, result;

  if (typeof obj[Symbol.iterator] !== "function")
    throw new TypeError(obj + " is not iterable");
  if (typeof cb !== "function") throw new TypeError("cb must be callable");

  iterable = obj[Symbol.iterator]();

  result = iterable.next();
  while (!result.done) {
    cb(result.value);
    result = iterable.next();
  }
}

function Map(data) {
  this.items = {};
  this.size = 0;

  data && forOf(data, (item) => {
    this.set(item[0], item[1]);
  });
}

Map.prototype.set = function (key, value) {
  if (!this.has(key)) {
    ++this.size;
  }
  this.items[dealKey(key)] = value;
  return this;
};

Map.prototype.get = function (key) {
  return this.items[dealKey(key)];
};

Map.prototype.has = function (key) {
  return this.items[dealKey(key)] !== undefined;
};

Map.prototype.delete = function (key) {
  if (this.has(key)) {
    delete this.items[key];
    --this.size;
  }
  return this;
};

Map.prototype.clear = function () {
  this.items = {};
  this.size = 0;
};

Map.prototype.keys = function () {
  return makeIterator(this.items, function (key, value) {
    return key;
  });
};

Map.prototype.values = function () {
  return makeIterator(this.items, function (key, value) {
    return value;
  });
};

// 键名与键值相同
Map.prototype.entries = function () {
  return makeIterator(this.items, function (key, value) {
    return [key, value];
  });
};

// for of默认迭代器是 entries() 方法
Map.prototype[Symbol.iterator] = function () {
  return this.entries();
};

Map.prototype.forEach = function (callbackFn, thisArg) {
  thisArg = thisArg || window;
  var iterator = this.entries();

  forOf(iterator, (item) => {
    callbackFn.call(thisArg, item[1], item[0], this);
  });
};

  测试下:


let map = new Map()
 
map.set(0, 'a');
map.set(1, 'b');
map.set(2, 'c');
 
 
for (let pair of map.entries()) {
  console.log(pair);
}
 
// [0, 'a']
// [1, 'b']
// [2, 'c']
 
 
for (let key of map.keys()) {
  console.log(key);
}
 
// 0
// 1
// 2
 
for (let value of map.values()) {
  console.log(value);
}
 
// a
// b
// c

map.forEach(function(value, key, map) {
  console.log(key + ': ' + value);
});

// 0: a
// 1: b
// 2: c

参考资料:

https://github.com/mqyqingfeng/Blog/issues/91

阮一峰- ES6-Set 和 Map 数据结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值