最合适首先请“Map”简单介绍一下自己。“Map”映射是一种经典的数据结构类型,其中数据以“key/value”键值对的形式存在。Map对象的默认值默认不包含任何值,只包含显式插入的键。一个对象有一个原型。原型上的键名可能与自身对象上设置的键名冲突。TypeArbitraryStringorSymbolLength键值对通过size属性获取键值对的个数只能手动计算。在频繁增删键值对的场景下性能更好。对于频繁增删键值对的场景,没有优化。Any!!!consttestMap=newMap()letstr='今天不学习',num=666,keyFunction=function(){},keySymbol=Symbol('Web'),keyNull=null,keyUndefined=undefined,keyNaN=NaN//添加键值对//基本用法testMap.set('key','value')//Map(1){"key"=>"value"}testMap.set(str,'明天会变成麻辣鸡')testMap.set(num,'前端运动鞋')testMap.set(keyFunction,'你的函数写的真好')testMap.set(keySymbol,'大前端')testMap.set(keyNull,'我是一个Null')testMap.set(keyUndefined,'我是一个Undefined')testMap.set(keyNaN,'我是NaN')testMap.get(function(){})//undefinedtestMap.get(Symbol('Web'))//undefined//虽然是NaN!==NaN但是作为一个mapkey名字没有区别testMap.get(NaN)//"IamaNaN"testMap.get(Number('NaN'))//"IamaNaN"除了NaN比较特殊,其他"Map》的get方法是通过比较键名是否相等(===)得到的,不相等则返回undefined比较Map和Object定义//Mapconstmap=newMap();map.set('key','value');//Map(1){"key"=>"value"}map.get('key');//'value'//ObjectconstsomeObject={};someObject.key='value';someObject.key;//'value'这里可以明显看出定义的行为非常相似。必须看到,你没有看到什么时候使用“地图”是最佳实践。别担心键名类型JavaScript“Object”只接受String和Symbol两种类型的键名,可以使用其他类型的键名,但是JavaScript最后会隐式转换为字符串constobj={}//直接看一些特殊的ones键名obj[true]='Boolean'obj[1]='Number'obj[{'frontend':'Sneaker'}]='666'Object.keys(obj)//["1","true",[objectObject]"]我们再来看"Map",它接受任意类型的键名,并保留其键名类型(这里是一个简单的例子,见开头的"Map"的基本使用详情见文章)constmap=newMap();map.set(1,'value');map.set(true,'value');map.set({'key':'value'},'value');for(constkeyofmap.keys()){console.log(key);}//1//true//{key:"value"}//另外,Map也支持正则表达式作为键名map.set(/^1[3456789]\d{9}$/,'常用电话号码')//Map(1){/^1[3456789]\d{9}$/=>"常用电话号码"}"Map”支持正则表达式作为键名,在Object中是不允许的,直接报一个错误。原型Prototype“Object”不同于“Map”,它不仅仅是你表面上看到的东西。“Map”只包含你定义的键值对,但“Object”对象在其原型中有一些内置属性constnewObject={};newObject.constructor;//?Object(){[nativecode]}如果不做正确遍历对象属性可能会导致问题并产生意想不到的错误constcountWords=(words)=>{constcounts={};for(constwordofwords){counts[word]=(counts[word]||0)+1;}returncounts;};constcounts=countWords(['constructor','creates','a','bug']);//{constructor:"functionObject(){[nativecode]}1",creates:1,a:1,bug:1}这个例子是受书《Effective TypeScript》[1]迭代器"Map"是可迭代的,可以直接迭代,比如forEach循环或者for...of...loop//forEachconstmap=newMap();map.set('key1','value1');map.set('key2','value2');map.set('key3','value3');map.forEach((value,key)=>{console.log(key,value);});//key1value1//key2value2//key3value3//for...of...for(constentryofmap){console.log(entry);}//["key1","value1"]//["key2","value2"]//["key3","value3"]但是你不能迭代直接为“Object”,当你尝试迭代时会导致错误(entry);}//UncaughtTypeError:objectisnotiterable这时候你需要额外的一步来获取它的键名,键值或者键值对for(constkeyofObject.keys(object)){console.log(key);}//key1//key2//key3for(constvalueofObject.values(object)){console.log(value);}//value1//value2//value3for(constentryofObject.entries(object)){console.log(entry);}//["key1","value1"]//["key2","value2"]//["key3","value3"]for(const[key,value]ofObject.entries(object)){console.log(key,value);}//"key1","value1"//"key2","value2"//"key3","value3"当然你也可以用for...in...遍历循环键名for(constkeyinobject){console.log(key);}//key1//key2//key3元素顺序和长度Map跟踪长度以便在O(1)复杂度下访问constmap=newMap();map.set('key1','value1');map.set('key2','value2');map.set('key3','value3');map.size;//3另一方面,对于“Object”,如果要获取对象的属性长度,需要手动迭代,使其复杂度为O(n),上面例子中的属性长度为n,我们可以看到“Map”总是按插入顺序返回键,而“Object”则不然。从ES6开始,String和Symbol的key是按顺序存储的,但是隐式转换成String保存的key是乱序??的constobject={};object['key1']='value1';object['key0']='value0';object;//{key1:"value1",key0:"value0"}object[20]='value20';object;//{20:"value20",key1:"value1",key0:"value0"}Object.keys(object).length;//3Object/Map最佳实践是什么上面就是“Map”和“Object”的基本区别。解决问题时,需要考虑两者的区别。当插入顺序是你解决问题时需要考虑的,而你目前需要使用除String和Symbol之外的键名,那么“Map”是最好的解决方案。如果需要遍历键值对(并且需要考虑顺序),那么我觉得还是有必要优先考虑“Map”。Map是纯哈希结构,而Object不是(它有自己的内部逻辑)。Map在键值对频繁增删改查的场景下表现更好,性能更高。因此,当你需要频繁操作数据时,也可以优先使用Map。让我再举一个实际的例子。比如有自定义字段用户操作功能,用户可以通过表单自定义字段。那么这个时候最好使用Map,因为它很有可能会破坏原来的对象此时用户定义的toString会销毁为原来的对象,“Map”键接受任何类型,无影响functionisMap(value){returnvalue.toString()==='[objectMap]';}constactorMap=newMap();actorMap.set('name','HarrisonFord');actorMap.set('toString','Actor:HarrisonFord');//Works!isMap(actorMap);//=>true当你需要处理一些属性,那么“Object”就完全有用了,尤其是当你需要处理JSON数据的时候。由于“地图”可以是任何类型,因此没有本地方法可以将其转换为JSON。varmap=newMap()map.set('key','value')JSON.stringify(map)//"{}"当你需要使用正则表达式来判断和处理一些业务逻辑时,"Map"将是你的constactions=()=>{constfunctionA=()=>{/*dosth*/}constfunctionB=()=>{/*dosth*/}constfunctionC=()=>{/*sendlog*/}的最佳解决方案returnnewMap([[/^guest_[1-4]$/,functionA],[/^guest_5$/,functionB],[/^guest_.*$/,functionC],//...])}constonButtonClick=(identity,status)=>{letaction=[...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))动作.forEach(([key,value])=>value.call(this))}利用数组循环的特点,满足正则条件的逻辑会被执行,然后公共逻辑和个别逻辑就可以执行了同时,因为有规律的存在,你可以打开你的想象力,解锁更多的玩法。更多相关的Map使用示例,可??以查看JavaScript复杂判断的更优雅写法。总结:“Object”对象通常可以很好地存储结构化数据,但也有相应的局限性。责任:接受的键名类型只能是String或Symbol。自定义键名很可能与原型继承的属性键名(如toString、constructor等)冲突。解决,并提供迭代器和简单的大小查找等好处不要使用“Map”作为普通“Object”的替代品,而是作为普通对象的补充
