美文网首页
正确、更好地使用JavaScript容器对象

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

作者: raaay0608 | 来源:发表于2017-08-09 21:33 被阅读0次

    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)

    相关文章

      网友评论

          本文标题:正确、更好地使用JavaScript容器对象

          本文链接:https://www.haomeiwen.com/subject/nldqrxtx.html