正确、更好地使用JavaScript容器对象

1. 简介

针对JavaScript内置的容器,尝试写出更好的代码。

此篇文中的语法、特性可能需要较新的ES版本来支持。

2. JavaScript中的遍历语法

2.1 传统for循环

仅适用于支持数字下标访问的容器,C、Java形式的循环。

let a = ['a', 'b', 'c'];

for (let i = 0; i < a.length; i++) {
    console.log(`${i}: ${a[i]}`);
}

输出为

0: a
1: b
2: c

稳定易用少坑,只是看起来稍显啰嗦。

使用let i = 0,保证i的有效范围在for循环内,如果使用var i = 0,那么在循环过后仍然能够访问到i的值。如果运行环境不支持let关键字,那么在循环外独立声明i,意思会更明确一些。

var a = ['a', 'b', 'c'],
    i = 0;

for (i = 0; i < a.length; i++) {
    console.log(`${i}: ${a[i]}`);
}

2.2. for-in

重要的话放在前边,for...in有坑。
for...in语法是用来遍历任何可迭代对象的。

let myObj = {
        name: "Ming",
        age: "20",
    };

for (let prop in myObj) {
    console.log(`${prop}: ${myObj[prop]}`);
}

输出为

name: Ming
age: 20

坑在于,使用for...in循环,会遍历对象的所有可遍历属性,包括对象继承来的属性、prototype的属性等。表现可能与预期不同。
这里引用阮一峰《JavaScript 标准参考教程(alpha)》中的例子:

// name 是 Person 本身的属性
function Person(name) {
  this.name = name;
}

// describe是Person.prototype的属性
Person.prototype.describe = function () {
  return 'Name: '+this.name;
};

var person = new Person('Jane');

// for...in循环会遍历实例自身的属性(name),
// 以及继承的属性(describe)
for (var key in person) {
  console.log(key);
}

输出为

name
describe

可以使用hasOwnProperty方法,判断某个属性是不是自身的属性。

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}

此时输出只有一个name

注意一般情况下应避免对Array使用for...in循环。假设有这样的数组:

var a = [];
var a[2] = 1;    // [undefined, undefined, 1]
a.extraProp = "this is a test extra props";   // 可以给数组或数组的ProtoType加一个属性

如果用传统i++式的循环,能够得到[undefined, undefined, 1]这样的结果,接下来我们试试使用for...in循环:

for (let val in a) {
    console.log(val);
}

得到结果为

2
extraProp

丢失了两个undefined,多了一个不能通过下标访问的属性extraProp。对数组进行遍历时,我们通常希望以此输出数组中每一个数字下标对应的元素,而这与我们一般的需求不符。

2.3. for...of

for...of是ES6中新出现的语法。for...of需要所遍历的对象实现了可迭代协议;并使用对象指定的迭代器进行遍历。换句话说,for...of会输出什么,是由对象自己决定的,不同于for...in那种,像是把对象直接肢解遍历。某种程度上说,JavaScript中的for...of最为接近Python中的for...in

使用和for..in同样的例子:

var a = [];
var a[2] = 1;    // [undefined, undefined, 1]
a.extraProp = "this is a test extra props";   // 可以给数组或数组的ProtoType加一个属性

for (let val of a) {
    console.log(val);
}

得到结果:

undefined
undefined
1

与使用传统i++遍历方式得到的结果完全一致。
在允许ES6的情况下,应当优先考虑使用for...of方式遍历元素。

3. 数组Array

3.1. 对Array进行for...of遍历

对于数组,前边已经提到了可以使用for...of语法写出简洁的代码进行遍历:

let a = ['a', 'b', 'c'];
for (let val of a) {
    console.log(val);
}

3.2. Array的keys与entries

如果在使用for...of进行遍历时,希望拿到当前元素的下标引用(类似于Python中的enumerate),可以使用Array.prototype.keys以及 Array.prototype.entries方法。

keys返回一个新的Array Iterator,对其遍历得到数组中的索引,用于仅需要索引,而不需要元素本身的情况:

let a = ['a', 'b', 'c'];
for (let val of a.keys()) {
    console.log(val);
}

输出结果为

0
1
2

entries同样返回一个新的Array Iterator,对其遍历可以得到数组中的键值对:

let a = ['a', 'b', 'c'];
for (let [idx, val] of a.entries()) {
    console.log(`${idx}: ${val}`);
}

输出结果为

0: a
1: b
2: c

3.3. Array的forEach

Array.prototype.forEach()是ES5开始Array类的一个遍历方法。其语法为:

arr.forEach(function callback(currentValue, index, array) {
    //your iterator
}[, thisArg]);

即接受一个回掉函数作为参数,这样会对数组中的每一个元素执行此函数:

let a = ['a', 'b', 'c'];

a.forEach(function(element) {
    console.log(element);
});

输出结果为

a
b
c

forEach仅会对能够通过下标访问的元素进行遍历,为数组或数组的prototype增加属性不会影响遍历,但是如果某个索引对应的元素为空,那么会跳过这个元素:

let a = ['a', 'b', 'c'];
let a[4] = 'e';    // ['a', 'b', 'c', undefined, 'e'];
a.forEach(function(element) {
    console.log(element);
});

输出结果为

a
b
c
e

另一个限制为,使用forEach难于实现for循环中的break逻辑。

4. 使用Map和Set

Mapset是ES2015中加入的键访问容器(Keyed Collection),用于实现映射表和集合的功能。

在ES2015之前,Map的功能通常可以用一个JavaScript对象来实现,但JavaScript对象有限制,包括key必须为字符串,对象的原型包含默认的键,可能在使用中导致冲突。

Set的功能在以前通常使用Array代替,并手动处理一些Set应有的特性(例如元素不可重复等),使用Array实现Set的功能运行效率十分低下。

顺便,在JavaScript中的MapSet有一个隐藏小福利:对Map或者Set进行遍历的时候,元素的访问顺序就是元素插入的顺序。相比之下Python中需要有序字典需要使用collections.OrderedDict,且Python的内置库中目前还没有找到专门的有序集合的实现。

对于Map常用操作如下:

Map.prototype.size
Map.prototype.set(key, value)
Map.prototype.get(key, value)
Map.prototype.has(key)
Map.prototype.delete(key)
Map.prototype.clear()
Map.prototype.keys()             // 只包含key的迭代器
Map.prototype.values()           // 只包含value的迭代器
Map.prototype.entries()          // [key, value] 形式的迭代器
Map.prototype.forEach(callbackFn[, thisArg])

对于Set常用操作如下:

Set.prototype.size
Set.prototype.add(value)
Set.prototype.has(value)
Set.prototype.delete(value)
Set.prototype.clear()
Set.prototype.values()           // 包含value的迭代器
Set.prototype.keys()             // 包含value的迭代器,和Set.prototype.values()相同
Set.prototype.entries()          // [value, value] 形式的迭代器
Set.prototype.forEach(callbackFn[, thisArg])

我踩过的一个坑是in运算符(大概是由于Python的惯性...)。在JavaScript中in操作符可以用于判断一个对象是否含有某个属性(var o = {name: "Ming"},那么name in o会返回True),文档中也提到可以用于判断数组的某个下标是否在其长度以内(多数情况下,如果n < arr.lengthn >= 0,那么n in arr会返回True,但也有例外,在2.2中提到,此处不该依赖in操作符)。

实际上in操作符对于MapSet类型没有什么意义。要查找某个键是否在MapSet中,请使用Map.prototype.has(key)Set.prototype.has(value)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Javascript有很多数组的方法,有的人有W3C的API,还可以去MDN上去找,但是我觉得API上说的不全,M...
    顽皮的雪狐七七阅读 4,060评论 0 6
  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock阅读 3,370评论 2 36
  • 臻悉阅读 192评论 3 1
  • 当自己在网上投了很多份简历还没得到回应时,一度觉得电脑系统出问题了,从没想过是自己不足(一个不知名的大专院校毕业生...
    迷路的妞儿阅读 170评论 0 0
  • 当一个人丢失了诚信,也就丢失了生命中最为珍贵的东西——人品和尊严。现实生活之中,无论友情还是爱性,都容不得半点儿欺...
    zzl阿丽阅读 252评论 0 0