[javascript] JavaScript에서 맵과 객체

방금 chromestatus.com을 발견하고 하루 동안 몇 시간을 잃은 후이 기능 항목을 발견 했습니다 .

맵 : 맵 객체는 간단한 키 / 값 맵입니다.

그것은 나를 혼란스럽게했다. 일반 JavaScript 객체는 사전이므로 사전과 Map다른 점은 무엇입니까? 개념적으로는 동일합니다 ( 지도와 사전의 차이점은 무엇입니까? )

chromestatus 참조 문서는 다음 중 어느 것도 도움이되지 않습니다.

맵 객체는 키와 값이 임의의 ECMAScript 언어 값일 수있는 키 / 값 쌍의 모음입니다. 고유 한 키 값은지도 모음 내에서 하나의 키 / 값 쌍에서만 발생할 수 있습니다. 맵 작성시 선택되는 비교 알고리즘을 사용하여 식별 된 고유 키 값.

Map 객체는 요소를 삽입 순서대로 반복 할 수 있습니다. 맵 객체는 해시 테이블 또는 평균적으로 컬렉션의 요소 수에 대한 하위 선형 액세스 시간을 제공하는 다른 메커니즘을 사용하여 구현해야합니다. 이 Map 객체 사양에 사용 된 데이터 구조는 Map 객체의 필수 관찰 의미를 설명하기위한 것입니다. 실행 가능한 구현 모델이 아닙니다.

… 여전히 나에게 물체처럼 들리므로 분명히 무언가를 놓쳤습니다.

JavaScript가 (잘 지원되는) Map객체를 얻는 이유는 무엇 입니까? 무엇을합니까?



답변

모질라에 따르면 :

Map 객체는 삽입 순서대로 요소를 반복 할 수 있습니다. for..of 루프는 각 반복에 대해 [키, 값] 배열을 반환합니다.

객체는지도와 유사하여 키를 값으로 설정하고, 값을 검색하고, 키를 삭제하고, 키에 무언가가 저장되어 있는지 여부를 감지 할 수 있습니다. 이 때문에 객체는 역사적으로지도로 사용되었습니다. 그러나지도를 더 잘 사용하도록하는 객체와지도에는 중요한 차이점이 있습니다.

객체에는 프로토 타입이 있으므로 맵에 기본 키가 있습니다. 그러나 map = Object.create (null)을 사용하여 무시할 수 있습니다. 객체의 키는 문자열이며지도의 값이 될 수 있습니다. 오브젝트의 크기를 수동으로 추적해야하는 동안 맵의 크기를 쉽게 얻을 수 있습니다.

런타임까지 키를 알 수없는 경우와 모든 키가 동일한 유형이고 모든 값이 같은 유형 인 경우 오브젝트에 대한 맵을 사용하십시오.

개별 요소에서 작동하는 논리가있는 경우 개체를 사용하십시오.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

순서대로 반복성은 모든 브라우저에서 동일한 성능을 보장하기 때문에 개발자가 오랫동안 원했던 기능입니다. 나에게 그것은 큰 것입니다.

myMap.has(key)방법은 특히 유용하며 myMap.size속성 도 있습니다.


답변

주요 차이점은지도에서 거의 모든 키 유형을 지원하는 경우 객체는 문자열 키만 지원한다는 것입니다.

내가 할 경우 obj[123] = true다음과 Object.keys(obj)그때 얻을 것 ["123"]보다는 [123]. 지도는 키의 유형을 유지하고 [123]훌륭합니다. 지도를 사용하면 객체를 키로 사용할 수도 있습니다. 전통적으로 이렇게하려면 객체를 해시하기 위해 일종의 고유 식별자를 객체에 제공해야합니다 ( getObjectIdJS에서 표준의 일부로 본 적이 없다고 생각 합니다). 지도는 또한 주문 보존을 보장하므로 보존에 더 적합하며 때로는 몇 가지 종류의 작업을 수행 할 필요가 없습니다.

실제로지도와 객체 사이에는 몇 가지 장단점이 있습니다. 객체는 JavaScript의 핵심에 매우 밀접하게 통합되어 장점과 단점을 모두 얻을 수 있으며, 이는 주요 지원의 차이를 뛰어 넘어 Map과 크게 차별화됩니다.

즉각적인 이점은 객체를 구문 적으로 지원하여 요소에 쉽게 액세스 할 수 있다는 것입니다. 또한 JSON으로 직접 지원합니다. 해시로 사용하면 속성이없는 객체를 얻는 것이 귀찮습니다. 기본적으로 객체를 해시 테이블로 사용하려는 경우 객체가 오염되어 hasOwnProperty속성에 액세스 할 때 종종 해당 객체를 호출 해야합니다. 기본적으로 객체가 오염되는 방법과 해시로 사용하기 위해 희망적으로 오염되지 않은 객체를 만드는 방법을 볼 수 있습니다.

({}).toString
    toString() { [native code] }
JSON.parse('{}').toString
    toString() { [native code] }
(Object.create(null)).toString
    undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
    undefined

객체에 대한 오염은 코드를 더 성가 시게하거나 느리게 만들뿐만 아니라 보안에 잠재적 인 영향을 줄 수 있습니다.

객체는 순수한 해시 테이블이 아니지만 더 많은 노력을 기울이고 있습니다. 같은 두통 hasOwnProperty이 있고 길이를 쉽게 얻을 수 없습니다 ( Object.keys(obj).length) 등. 객체는 순수하게 해시 맵으로 사용되는 것이 아니라 동적 확장 가능한 객체로도 사용되므로 순수한 해시 테이블 문제가 발생할 때 사용할 수 있습니다.

다양한 일반적인 작업 비교 / 목록 :

    Object:
       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       if(o.hasOwnProperty('key'));
       delete(o.key);
       Object.keys(o).length
    Map:
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;
       if(m.has('key'));
       m.delete('key');
       m.size();

다양한 기복 (성능, 간결함, 휴대용, 확장 가능 등)과 함께 몇 가지 다른 옵션, 접근 방식, 방법론 등이 있습니다. 객체는 언어의 핵심이되는 약간 이상하기 때문에 작업을위한 많은 정적 메소드가 있습니다.

키 유형을 유지하는지도의 장점 외에도 객체와 같은 것들을 객체가 갖는 부작용으로부터 격리 된 키로 지원할 수 있다는 장점이 있습니다. 지도는 순수한 해시로, 동시에 객체가 되려는 것에 대한 혼란이 없습니다. 프록시 기능으로 맵을 쉽게 확장 할 수도 있습니다. 객체는 현재 프록시 클래스를 가지고 있지만 성능 및 메모리 사용량은 심각합니다. 실제로 객체에 대한 맵처럼 보이는 자체 프록시를 만드는 것은 현재 프록시보다 성능이 뛰어납니다.

지도의 실질적인 단점은 JSON에서 직접 지원되지 않는다는 것입니다. 구문 분석이 가능하지만 몇 가지 끊기가 있습니다.

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;
});

위의 내용은 성능에 심각한 영향을 미치며 문자열 키도 지원하지 않습니다. JSON 인코딩은 훨씬 어렵고 문제가 많습니다 (여러 가지 접근 방식 중 하나임).

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())
    });
};

순전히지도를 사용하는 경우 그렇게 나쁘지는 않지만 유형을 혼합하거나 스칼라 이외의 값을 키로 사용할 때 문제가 발생합니다 (JSON은 IE 원형 객체 참조와 같은 문제에 완벽하지는 않습니다). 테스트하지는 않았지만 stringify에 비해 성능이 크게 저하 될 수 있습니다.

다른 스크립팅 언어는 종종 Map, Object 및 Array에 대해 스칼라가 아닌 명시 적 유형을 갖는 것과 같은 문제가 없습니다. 웹 개발은 종종 스칼라가 아닌 유형에 어려움을 겪습니다 .PHP는 속성에 A / M을 사용하여 객체와 배열 / 맵을 병합하고 JS는 배열 확장 M / O와 맵 / 객체를 병합합니다. 복잡한 유형을 병합하는 것은 고급 스크립팅 언어에 대한 악마의 골칫거리입니다.

지금까지는 구현과 관련하여 주로 문제가되었지만 기본 작업의 성능도 중요합니다. 엔진과 사용량에 따라 성능도 복잡합니다. 실수를 배제 할 수 없기 때문에 소금 한 알로 시험을 치십시오 (이걸 서 두어야합니다). 또한 매우 간단한 시나리오 만 검토하여 대략적인 지표 만 제시하는지 확인하기 위해 자체 테스트를 실행해야합니다. 매우 큰 객체 / 맵에 대한 Chrome의 테스트에 따르면 삭제로 인해 객체의 성능이 좋지 않습니다. 삭제는 O (1)이 아닌 키 수에 비례합니다.

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome은 가져오고 업데이트하는 데 큰 장점이 있지만 삭제 성능은 끔찍합니다. 이 경우 맵은 오버 헤드를 조금만 사용하지만 오버 헤드가 수백만 키로 테스트되는 객체 / 맵이 하나만 있으면 맵 오버 헤드의 영향이 잘 표현되지 않습니다. 메모리 관리를 사용하면 프로파일을 올바르게 읽고 있으면 객체를 선호하는 이점이있을 수 있습니다.

이 특정 벤치 마크에 대한 Firefox에서는 다른 이야기입니다.

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

Firefox의 객체 에서이 특정 벤치 마크를 삭제해도 문제가 발생하지 않지만 다른 벤치 마크에서는 특히 Chrome과 같이 많은 키가있을 때 문제가 발생했음을 즉시 지적해야합니다. 대규모 컬렉션의 경우 Firefox에서 맵이 확실히 우수합니다.

그러나 이것은 이야기의 끝이 아닙니다. 많은 작은 물체 나지도는 어떻습니까? 나는 이것에 대한 빠른 벤치 마크를했지만 위의 작업에서 적은 수의 키로 가장 잘 수행되는 철저한 벤치 마크 (설정 / 가져 오기)가 아닙니다. 이 테스트는 메모리와 초기화에 관한 것입니다.

Map Create: 69    // new Map
Object Create: 34 // {}

다시이 수치는 다양하지만 기본적으로 Object는 좋은 리드를 가지고 있습니다. 어떤 경우에는 맵에서 오브젝트의 리드가 극단적 (~ 10 배 더 낫습니다)이지만 평균적으로 2-3 배 정도 더 좋습니다. 극단적 인 성능 스파이크가 양방향으로 작동 할 수있는 것 같습니다. 메모리 사용량과 오버 헤드를 프로파일 링하기 위해 Chrome에서만 생성하고 테스트했습니다. Chrome에서 하나의 키를 가진지도가 하나의 키를 가진 객체보다 약 30 배 더 많은 메모리를 사용하는 것으로 나타났습니다.

위의 모든 작업 (4 키)으로 많은 작은 개체를 테스트하려면 다음을 수행하십시오.

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

메모리 할당 측면에서 이들은 해제 / GC 측면에서 동일하게 동작했지만 Map은 5 배 더 많은 메모리를 사용했습니다. 이 테스트는 마지막 테스트 에서처럼 하나의 키만 설정 한 4 개의 키를 사용하여 메모리 오버 헤드 감소를 설명합니다. 이 테스트를 몇 번 실행했으며 Map / Object는 전체 속도 측면에서 Chrome의 전반적인 목과 목입니다. 작은 오브젝트 용 Firefox에서는 전체 맵에 비해 확실한 성능 이점이 있습니다.

물론 여기에는 크게 다를 수있는 개별 옵션이 포함되어 있지 않습니다. 나는이 수치를 미세 최적화하는 조언을하지 않을 것입니다. 이를 극복 할 수있는 것은 경험상 엄청나게 큰 키 값 저장소 및 작은 키 값 저장소의 오브젝트에 대해 맵을 더 강력하게 고려한다는 것입니다.

이 두 가지를 사용하는 가장 좋은 전략을 넘어서서 구현하고 먼저 작동하게하십시오. 프로파일 링 할 때 가끔 객체 키 삭제 사례에서 볼 수 있듯이 엔진 문제로 인해 느릴 것으로 생각하지 않는 것이 느려질 수 있음을 명심해야합니다.


답변

나는 지금까지 답변에서 다음 사항을 언급하지 않았다고 생각하며 언급 할 가치가 있다고 생각했습니다.


지도가 더 클 수 있습니다

크롬에서 내가 얻을 수있는 16.7 와 만 키 / 값 쌍 Map11.1 일반 개체 만. 와 거의 정확히 50 % 더 많은 쌍 Map. 둘 다 충돌하기 전에 약 2GB의 메모리를 차지하므로 크롬에 의한 메모리 제한과 관련이 있다고 생각합니다 ( 편집 : 예, 2를 채우 Maps십시오. 이 코드로 직접 테스트 할 수 있습니다 (명확하게 동시에 실행하지 말고 별도로 실행하십시오).

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

객체에는 이미 몇 가지 속성 / 키가 있습니다

이 사람은 전에 나를 트립했다. 일반 객체가 toString, constructor, valueOf, hasOwnProperty, isPrototypeOf등의 기존 특성의 무리. 이것은 대부분의 사용 사례에서 큰 문제는 아니지만 이전에 문제를 일으켰습니다.

지도가 느려질 수 있습니다.

.get함수 호출 오버 헤드와 내부 최적화 부족 으로 인해 일부 작업의 경우 맵 이 일반 오래된 JavaScript 객체보다 상당히 느려질 수 있습니다 .


답변

Javascript는 동적으로 유형이 지정되므로 언제든지 객체의 속성을 추가하거나 제거 할 수 있으므로 객체는 사전처럼 작동 할 수 있습니다.

그러나 다음과 같은 Map()이유로 새로운 기능이 훨씬 좋습니다.

  • 그것은 제공 get, set, has, 및 delete방법을.
  • 문자열 대신 모든 유형의 키를 허용합니다.
  • for-of사용 하기 쉬운 반복자를 제공하고 결과 순서를 유지합니다.
  • 반복 또는 복사 중에 프로토 타입 및 기타 속성이 표시되는 엣지 케이스가 없습니다.
  • 수백만 개의 항목을 지원합니다.
  • 자바 스크립트 엔진이 향상됨에 따라 매우 빠르며 계속 빨라집니다.

시간의 99 %는을 사용해야합니다 Map(). 그러나 문자열 기반 키만 사용하고 최대 읽기 성능이 필요한 경우 객체를 선택하는 것이 더 좋습니다.

세부 사항은 (거의 모든) 자바 스크립트 엔진이 백그라운드에서 C ++ 클래스로 객체를 컴파일한다는 것입니다. 이러한 유형은 “개요”에 의해 캐시되고 재사용되므로 동일한 속성을 가진 새 객체를 만들면 엔진이 기존 백그라운드 클래스를 재사용합니다. 이러한 클래스의 속성에 대한 액세스 경로는 매우 최적화되어 a의 조회보다 훨씬 빠릅니다 Map().

속성을 추가하거나 제거하면 캐시 된 백업 클래스가 다시 컴파일되므로 키 추가 및 삭제가 많은 사전으로 개체를 사용하는 것이 매우 느리지 만 개체를 ​​변경하지 않고 기존 키를 읽고 할당하는 것이 매우 빠릅니다.

따라서 문자열 키를 사용하여 한 번만 읽은 읽기 작업량이 많은 object경우 특수한 고성능 사전으로 사용하지만 다른 모든 경우에는를 사용하십시오 Map().


답변

다른 답변 외에도지도가 객체보다 다루기 쉽고 장황한 것으로 나타났습니다.

obj[key] += x
// vs.
map.set(map.get(key) + x)

더 짧은 코드는 더 빨리 읽고, 더 직접적으로 표현 하며, 프로그래머의 머리에 더 잘 유지 되기 때문에 이것은 중요 합니다.

또 다른 측면 : set ()은 값이 아닌 맵을 반환하기 때문에 할당을 연결하는 것은 불가능합니다.

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

지도 디버깅도 더 힘들다. 아래에서는 실제로지도에 어떤 키가 있는지 볼 수 없습니다. 그렇게하려면 코드를 작성해야합니다.

Map Iterator를 평가하는 행운을 빕니다

모든 IDE에서 객체를 평가할 수 있습니다.

객체 평가 WebStorm


답변

요약:

  • Object: 데이터가 키 값 쌍으로 저장되는 데이터 구조. 객체에서 키는 숫자, 문자열 또는 기호 여야합니다. 이 값은 다른 객체, 함수 등도 될 수 있습니다. 객체는 정렬되지 않은 데이터 구조입니다. 즉 키 값 쌍의 삽입 순서는 기억되지 않습니다
  • ES6 Map: 데이터가 키 값 쌍으로 저장되는 데이터 구조. 있는 고유 키 값에 매핑 . 키와 값은 모두 모든 데이터 유형이 될 수 있습니다 . 맵은 반복 가능한 데이터 구조입니다. 즉, 삽입 순서가 기억되고 for..of루프 등 의 요소에 액세스 할 수 있습니다.

주요 차이점 :

  • A Map는 정렬되고 반복 가능하지만 객체는 정렬되지 않고 반복 가능하지 않습니다

  • 모든 유형의 데이터를 Map키로 넣을 수 있지만 객체는 숫자, 문자열 또는 기호 만 키로 가질 수 있습니다.

  • 에서 Map상속 Map.prototype합니다. 이것은 모든 종류의 유틸리티 기능과 속성을 제공하여 Map객체 작업을 훨씬 쉽게 만듭니다.

예:

목적:

let obj = {};

// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;

// getting nr of properties of the object
console.log(Object.keys(obj).length)

// deleting a property
delete obj[2]

console.log(obj)

지도:

const myMap = new Map();

const keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"

console.log(myMap.get('a string'));   // "value associated with 'a string'"
                         // because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

출처 : MDN


답변

또한 잘 정의 된 순서로 반복 가능하고 임의의 값을 키로 사용하는 기능 (제외 -0)을 사용하면 다음과 같은 이유로 맵이 유용 할 수 있습니다.

  • 이 스펙은 맵 조작이 평균적으로 서브 리니어되도록 강제합니다.

    바보가 아닌 객체 구현은 해시 테이블 또는 이와 유사한 것을 사용하므로 속성 조회는 평균적으로 일정 할 것입니다. 그러면 객체가지도보다 훨씬 빠를 수 있습니다. 그러나 이것은 사양에 필요하지 않습니다.

  • 개체에 예기치 않은 동작이 발생할 수 있습니다.

    예를 들어, foo속성을 새로 만든 object로 설정하지 않았 obj으므로 obj.fooundefined를 반환 한다고 가정 해 봅시다 . 그러나 foo에서 상속받은 기본 제공 속성 일 수 있습니다 Object.prototype. 또는 obj.foo할당을 사용하여 만들려고 하지만 Object.prototype값을 저장하는 대신 일부 세터가 실행됩니다.

    지도는 이런 종류의 것들을 막습니다. 글쎄, 일부 스크립트가 엉망이되지 않는 한 Map.prototype. 그리고 Object.create(null)작동하지만 간단한 객체 이니셜 라이저 구문이 손실됩니다.