Map과 WeakMap

Map과 WeakMap

맵은 Key와 Value를 연결한다는 점에서 객체와 유사하다. 하지만 객체를 사용할때 발생하는 문제점들이 있는데 아래와 같다.

  • 프로토타입 체인으로 인해 의도하지 않은 연결이 생김
  • 객체는 프로퍼티의 순서를 보장하지 않음
  • 객체 안에 연결된 키와 값이 몇 개 인지 알기 어려움
  • 객체의 키는 반드시 문자열 또는 Symbol이어야 하므로 객체를 키로 사용할 수 없음

Map은 위에서 나열한 객체의 단점들을 해결했다고 한다.

Map

1
2
3
4
5
6
let m = new Map();

m.set('data', [1, 2, 3]);
m.set('data2', 1);

console.log(m.get('data')); // [1, 2, 3]

Set과 비슷하게 작동한다. set()을 통해 맵에 데이터를 추가할 수 있고 get()으로 가져올 수 있다.

WeakMap

WeakSet과 비슷하게 동작한다. 위크셋과 마친가지로 참조 타입의 객체만 포함할 수 있다. 다음은 위크셋의 키로 myfunc라는 함수를 사용하고 반복문을 통해 값을 1부터 10까지 더한 후 저장하는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
let wm = new WeakMap();
let myfunc = function(){};

wm.set(myfunc, 0);

sum = 0;
for (let i=1;i<=10;i++){
sum += i;
}
wm.set(myfunc, sum);
console.log(wm); // WeakMap { myfunc() -> 55}

두 번째로 위크맵을 통해 인스턴스 변수를 보호하는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
function Area(height, width){
this.height = height;
this.width = width;
}

Area.prototype.getArea = function(){
return this.height * this.width;
}

let myarea = new Area(10, 20);
console.log(myarea.getArea()); // 200
console.log(myarea.height); // 10

맨 마지막줄을 살펴보자. 아마도 height 변수의 값이 출력 될 것이다. 이처럼 외부에서 클래스의 내부 인스턴스 변수를 직접 접근하는 것은 좋지 않다. WeakMap을 이용하여 외부로부터의 접근을 막아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const wm = new WeakMap();

function Area(height, width){
wm.set(this, { height, width });
}

Area.prototype.getArea = function(){
const { height, width } = wm.get(this);
return height * width;
};

let myarea = new Area(10, 20);
console.log(myarea.getArea()); // 200
console.log(myarea.height); // undefined

WeakMap과 destructuring을 통해서 코드를 작성했다. 위크맵이 전역적으로 사용되는 단점이 있지만 외부 접근을 막는 데에는 성공한 것을 볼 수 있다!

Share

Set과 WeakSet

Set과 WeakSet

배열과 비슷하게 생긴 것중에 Set과 WeakSet 이라는 게 있다.

Set

일반적인 배열과는 다르게 중복을 허용하지 않고 유일한 값만을 저장한다. 이미 존재하는 지 확인할 때 유용하다.

1
2
let mySet = Set();
console.log(toString.call(mySet)); // [object Set]

형태는 배열과 똑같이 생겼지만 Set 안에 중복된 값이 있을 경우 하나만 저장된다. 메소드를 통해 값을 넣고 뺄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
let mySet = Set();

mySet.add('abc');
mySet.add('Hello');
mySet.add('abc');
mySet.add('abc');

mySet.forEach(function(value){
console.log(value);
});

// 'abc'
// 'Hello'

“abc”라는 문자열을 총 세 번 입력했지만 Set의 특성상 중복을 제거해 주기 때문에 한 번밖에 사용되지 않았다.

1
mySet.delete('abc'); // ['Hello']

삭제를 위해서 delete 메소드를 사용했다.

1
2
3
4
5
if (mySet.has('Hello')) {
console.log('True');
} else {
console.log('False');
}

mySet이라는 Set에 “Hello” 문자열이 있는지 판단했다. 만약 해당 문자열이 있다면 “True”를 아니라면 “False”를 출력한다. 정리하자면 Set은 중복을 허용하지 않는 데이터 집합이라고 할 수 있다.

WeakSet

참조를 가지고 있는 객체만 저장이 가능하다. 그리고 이 객체들은 가비지 콜렉션의 대상이 된다.

1
2
3
4
5
6
7
8
9
let data = [1, 2, 3, 4];
let ws = new WeakSet();

ws.add(data);
ws.add(123); // error
ws.add('haha'); // error
ws.add(function(){});

console.log(ws); // WeakSet{[1, 2, 3, 4], ()}

숫자 123과 문자열 ‘haha’는 참조를 가지고 있는 객체 타입이 아닌 기본형(primitive type) 타입 이기 때문에 WeakSet에 저장하려고 시도하면 에러가 발생한다. 그러나 함수는 참조 타입이므로 문제없이 WeakSet에 저장할 수 있다.

그렇다면 WeakSet에 저장되어 있는 객체들이 가비지 콜렉션의 대상이 된다는 말을 무엇일까?

1
2
3
4
5
6
7
8
9
10
let data = [1, 2, 3, 4];
let data2 = ['a', 'b', 'c'];
let obj = { data, data2 };
let ws = new WeakSet();

ws.add(data);
ws.add(data2);
ws.add(obj);

console.log(ws); // WeakSet([1, 2, 3, 4], ['a', 'b', 'c'], { data, data2 })

위의 코드를 보면 위크셋 ws에는 두 개의 배열과 하나의 객체가 들어가있다. 위크셋에 저장되어있는 변수 data에 null을 대입해보자.

1
2
3
data = null;

console.log(ws); // WeakSet([1, 2, 3, 4], ['a', 'b', 'c'], { data, data2 })

콘솔에 출력된 결과는 null을 대입하기 이전과 다를 것이 없어보인다. 하지만 has 메소드를 통해서 확인해보면 정말 유효한지 아닌지 알 수 있다.

1
console.log(ws.has(data), ws.has(data2)); // false, true

data는 null을 대입해 주었기 때문에 더 이상 유효하지 않으며 data2는 그대로 배열을 가지고 있어서 true를 출력한다.

Share

Array와 반복문

배열(Array)

자바스크립트에서 배열을 반복하는 방법엔 여러가지가 있다.

forEach
1
2
3
4
5
6
7
8
9
let data = [1, 2, undefined, ""];
data.forEach(function(value) {
console.log(value);
});

// 1
// 2
// undefined
// ""
for … in

객체를 순회할때 사용한다. 과거에는 객체 뿐만아니라 array에도 사용을 했으며 프로토타입에 걸려있는 함수 또는 변수도 출력하기 때문에 주의를 요구한다. 프로토타입에 추가적으로 정의된 게 없다면 문제없지만 프로토타입에 아무것도 없다는 것을 보장하기 힘들기 때문에 배열에는 사용하지 않는 편이다. 만약 배열에 반복문을 사용하고 싶다면 es6에서 새로 추가된 for … of 반복문을 사용하는 것이 좋다.

1
2
3
4
5
6
7
8
9
let data = {
'a': 1,
'b': 2,
'c': 3
};

for (let key in data) {
console.log(key, data[key]);
}

for … in 구문은 객체의 key를 사용한다. 따라서 객체의 key에 따른 value를 얻기 위해서는 data[key]의 형식으로 적어주어야 한다. 앞서 말한것처럼 객체가 아닌 Array에 for … in 구문을 사용하게 되면 생각지 못했던 값이 출력될 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
let data = ['a', 'b', 'd'];

Array.prototype.testFunc = function() {};
for (let key in data) {
console.log(data[key]);
}

// 출력 결과
// 'a'
// 'b'
// 'd'
// function(){}
for … of

es6에서 추가된 반복문이다. 컬렉션의 요소에 루프를 실행하기 위해서 사용한다.

1
2
3
4
5
let data = [1, 2, undefined, ""];

for (let value of data) {
console.log(value);
};

for … in 반복문과는 다르게 컬렉션의 값을 직접 사용할 수 있다. 뿐만아니라 문자열에도 사용할 수 있다.

1
2
3
4
5
let data = "hello world";

for (let v of data) {
console.log(v);
}

spread operator

파이썬의 튜플 패킹 / 언패킹과 비슷하게 작동한다.

1
2
3
4
5
6
7
8
9
let first = ['snake', 'pig', 'hello'];
let newData = [...first];

console.log(first); // ['snake', 'pig', 'hello']
console.log(newData); // ['snake', 'pig', 'hello']

// 결과는 같지만 서로 다른 배열임
// 기존의 참조를 끊고 새로 연결함
console.log(first === newData);
삽입하기
1
2
3
4
let test = ['a', 'b', 'c', 'd'];
let data = [1, 2, ...test, 3, 4];

console.log(data); // [1, 2, "a", "b", "c", "d", 3, 4]
인자로 풀어넣기
1
2
3
4
5
6
function mysum(a, b, c) {
return a + b + c;
};

let data = [1, 2, 3];
console.log(mysum(...data));
가변인자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function addMark() {

// 첫 번재 방법 - for 반복문과 arguments 객체 이용
let result = [];
for (let idx=0; idx<arguments.length; idx++) {
result.push(arguments[idx] + "!!");
}

// 두 번재 방법 - from과 map 그리고 arguments 객체 이용
let newArray = Array.from(arguments);
let result = newArray.map(function(value) {
return value + "!!";
});

console.log(result);
};

addMark(1, 2, 3, 4);

arguments 객체는 모든 함수 내에서 이용 가능한 지역변수이다. 보통은 함수에 인자가 몇 개 들어올 지 예상할 수 없을때 사용한다. arguments 객체는 Array처럼 생기긴 했지만 length 메소드 빼고는 어떤 Array 속성도 가지고 있지 않다고 한다.

1
2
3
let result = arguments.map(function(value) {
return value + "!";
});

만약 두 번재 방법에서 위와 같이 사용했다면 에러가 발생할 것이다. 왜냐하면 arguments 객체는 배열처럼 보일뿐 실제로 배열은 아니기 때문이다.

Share