大家好,我是前端西瓜哥。
以前我在 JS 中写字符串哈希表,是直接用对象字面量的形式来初始化的。
const map = {};
map['a'] = 1;
const key = map['a'];
然后我看了下 Vue2 源码中哈希表的写法。
const map = Object.create(null);
咋一看,觉得不太优雅,怎么这么臭长臭长的。再认真思考了一下,西瓜哥我才发现了盲点。
二者的区别涉及到了 JS 中访问对象属性的机制:原型链。
原型链
JS 中创建对象时,会给该对象添加一个指向某个对象的内置属性 [[Prototype]]
,这个被指向的对象就被称为原型对象。然后原型对象自身也有内置属性 [[Prototype]]
,层层嵌套,直到为 null 结束,形成了一个原型链。
当我们访问对象属性时,如果当前对象中不存在,就会找它的原型对象里找同名属性,如果找到就返回它;如果找不到,继续向上,直到 null 为止还是找不到,才返回 undefined。
更多原型链的解读可以看我的一篇文章:《用原型链的方式实现一个 JS 继承》
字面量对象写法({}
)的原型链是这样的:
map -> Object.prototype -> null
即使 map 为空,在访问一些特定的属性时就会拿到一些特定的 key 时(比如 toString),会因为原型链的关系,拿到 Object.prototype 的属性,导致无法得到预期的 undefined 值。
因为就只有这几个特殊的 key 值,所以不容易难命中,平时还算运行良好,但一旦出了问题,如果你不知道这个知识点,排查起来还是比较困难的。
至于 Object.create(null)
,它能够创建一个空对象,并将 [[prototype]]
属性值设置为传入对象,这里是 null。
它的原型链是长这样:
map -> null
map 对象找不到属性,去找原型对象,结果原型对象直接就是 null,没有中间商赚差价,我们就拿到预期的 undefined。
ES6 新增的 Map 数据结构
其实我们也可以用 ES6 新增的 Map 数据结构来实现哈希表,但我想说它太笨重了。
如果只是简单地使用 key 为字符串类型的哈希表,只是读写键值对,我还是更倾向于用普通对象哈希表,不为什么,代码更短一点。
// Map 对象写法
map.set(k, v);
map.get(k);
// 普通对象写法
map[k] = v;
map[k];
结尾
因为 JS 对象访问属性会追溯原型链的特性,不建议使用字面量对象的写法来实现哈希表。
正确的方式是使用 Object.assgin(null) 或者更重一点的 Map 数据结构。
我是前端西瓜哥,一名喜欢分享的前端开发,欢迎关注我。
本文使用 文章同步助手 同步
网友评论