javascript - Eloquent JavaScript,序列接口(interface)

标签 javascript

我正在学习 Eloquent JavaScript,我对问题 #6.3(第二版)有疑问。

问题是:

Design an interface that abstracts iteration over a collection of values. An object that provides this interface represents a sequence, and the interface must somehow make it possible for code that uses such an object to iterate over the sequence, looking at the element values it is made up of and having some way to find out when the end of the sequence is reached.

When you have specified your interface, try to write a function logFive that takes a sequence object and calls console.log on its first five elements—or fewer, if the sequence has fewer than five elements.

Then implement an object type ArraySeq that wraps an array and allows iteration over the array using the interface you designed. Implement another object type RangeSeq that iterates over a range of integers (taking from and to arguments to its constructor) instead.

我写了这个解决方案:

function ArraySeq(collection) {
  this.values = collection;
}

ArraySeq.prototype.iterate = function(start, end, action) {
  var n = Math.min(this.values.length, end);
  for (var i = start; i < n; i++) {
    action(this.values[i]);
  }
};

function RangeSeq(from, to) {
  var array = [];
  for (var i = from; i <= to; i++)
    array.push(i);
  ArraySeq.call(this, array);
}

RangeSeq.prototype = Object.create(ArraySeq.prototype);

function logFive(sequenceObject) {
 sequenceObject.iterate(0, 5, console.log);
}

//This code to test how the solution works was provided by the author
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104

作者的解决方案不同,这里都是:

// I am going to use a system where a sequence object has two methods:
//
// * next(), which returns a boolean indicating whether there are more
//   elements in the sequence, and moves it forward to the next
//   element when there are.
//
// * current(), which returns the current element, and should only be
//   called after next() has returned true at least once.

function logFive(sequence) {
  for (var i = 0; i < 5; i++) {
    if (!sequence.next())
      break;
    console.log(sequence.current());
  }
}

function ArraySeq(array) {
  this.pos = -1;
  this.array = array;
}
ArraySeq.prototype.next = function() {
  if (this.pos >= this.array.length-1)
    return false;
  this.pos++;
  return true;
};
ArraySeq.prototype.current = function() {
  return this.array[this.pos];
};

function RangeSeq(from, to) {
  this.pos = from - 1;
  this.to = to;
}
RangeSeq.prototype.next = function() {
  if (this.pos >= this.to)
    return false;
  this.pos++;
  return true;
};
RangeSeq.prototype.current = function() {
  return this.pos;
};

logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104

// This alternative approach represents the empty sequence as null,
// and gives non-empty sequences two methods:
//
// * head() returns the element at the start of the sequence.
// 
// * rest() returns the rest of the sequence, or null if there are no
//   elemements left.
//
// Because a JavaScript constructor can not return null, we add a make
// function to constructors of this type of sequence, which constructs
// a sequence, or returns null if the resulting sequence would be
// empty.

function logFive2(sequence) {
  for (var i = 0; i < 5 && sequence != null; i++) {
    console.log(sequence.head());
    sequence = sequence.rest();
  }
}

function ArraySeq2(array, offset) {
  this.array = array;
  this.offset = offset;
}
ArraySeq2.prototype.rest = function() {
  return ArraySeq2.make(this.array, this.offset + 1);
};
ArraySeq2.prototype.head = function() {
  return this.array[this.offset];
};
ArraySeq2.make = function(array, offset) {
  if (offset == null) offset = 0;
  if (offset >= array.length)
    return null;
  else
    return new ArraySeq2(array, offset);
};

function RangeSeq2(from, to) {
  this.from = from;
  this.to = to;
}
RangeSeq2.prototype.rest = function() {
  return RangeSeq2.make(this.from + 1, this.to);
};
RangeSeq2.prototype.head = function() {
  return this.from;
};
RangeSeq2.make = function(from, to) {
  if (from > to)
    return null;
  else
    return new RangeSeq2(from, to);
};

logFive2(ArraySeq2.make([1, 2]));
// → 1
// → 2
logFive2(RangeSeq2.make(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104

我的问题:

  1. 我的解决方案产生的结果与作者的解决方案完全相同。我用各种测试用例测试了这三个。我在编写解决方案时确实利用了本章的 Material (本章讨论了 JavaScript 中的 OOP 技术、继承等)。 考虑到这一切,我的解决方案是否错误?

  2. 为什么他的解决方案是这样写的?我的意思是,我的方法有问题吗?不够抽象?我的方法接受参数是件坏事吗?他的方法不需要争论。 与他的解决方案相比,我的解决方案有什么缺点吗?(我必须承认,我几乎没有理解这个问题,所以我的解决方案可能反射(reflect)了我对问题的理解水平。)

    <

谢谢!

最佳答案

您的解决方案是不正确的,因为迭代器协议(protocol)的全部意义在于惰性迭代,因此如果我们只需要 RangeSeq(1, 10000000) 就不会用所有元素填满内存五个。

作者的代码还可以,但是不必要的冗长。迭代器协议(protocol)通常是这样实现的:

  • Iterable 是具有iterator() 方法的对象
  • Iterable.iterator() 返回一个Iterator 对象
  • Iterator 提供方法 .next 返回一对 (done, value)。或者,.next 可以在耗尽的迭代器上调用时抛出异常。

例子:

function RangeSeq(from, to) {
    this.iterator = function() {
        var i = from;
        return {
            next: function() { return [i >= to, i++] }
        }
    }
}

function ArraySeq(ary) {
    this.iterator = function() {
        var i = 0;
        return {
            next: function() { return [ i >= ary.length, ary[i++]] }
        }
    }
}

function logN(seq, n) {
    var it = seq.iterator();

    while (n--) {
        let [done, value] = it.next();
        if (done) break;
        console.log(value)
    }
}

logN(new RangeSeq(100, 100000000), 5);

console.log('--------------');

logN(new ArraySeq([11,22,33,44,55,66,77,88,99]), 5);

也就是说,现代 JS 引擎内置了这种东西,所以 Range 示例可以写得更简单

function RangeSeq(from, to) {
    this[Symbol.iterator] = function*() {
        for (let i = from; i < to; i++)
            yield i;
        }
}

n = 0;
rs = new RangeSeq(100, 10000000);

for (let x of rs) {
    if (n++ >= 5) break;
    console.log(x)

}

关于javascript - Eloquent JavaScript,序列接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38426110/

相关文章:

javascript - 移动鼠标时更改视频时间?

javascript - Plotly.js - 获取堆积条形图中每个 x 的 4 个 y 值

javascript - 带有 href 和 javascript 的 jQuery append 元素不起作用

javascript - 需要帮助在 QBE(示例查询)MarkLogic 中创建日期范围查询格式示例

javascript - 无法执行内联事件处理程序,因为它违反了内容安全策略指令

javascript - Vuejs:将代码分布在多个组件或所有组件中吗?

javascript - Promise.all 返回空对象

javascript - 从 FastAPI 获取文件时如何使用浏览器缓存?

javascript - 如何使用 javaScript 在图片文件 (png) 中添加文本?

javascript - jQuery cookie 破坏了我的 CSS 切换输入