Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入学习JavaScript之二:数组遍历 #4

Open
shhdgit opened this issue Apr 30, 2017 · 0 comments
Open

深入学习JavaScript之二:数组遍历 #4

shhdgit opened this issue Apr 30, 2017 · 0 comments

Comments

@shhdgit
Copy link
Owner

shhdgit commented Apr 30, 2017

在编写代码时,数组遍历可以说是我们最常用的操作。今天就根据es5-shim中数组遍历部分的代码进行解读。先实现一个forEach函数。

目标

  • forEach第一个参数为回调函数,回调函数参数为currentValue, index, array
  • forEach第二个参数为回调函数中this的指向
  • 遍历数组中元素并调用回调函数

思路

  • forEach实现起来比较简单,就直接把代码全部贴上来了
// Monkey patch
Array.prototype.forEach = Array.prototype.forEach || forEach

function forEach (callback, thisArg) {
  var object = new Object(this)
  // 如果this是字符串则转换为数组
  var self = isString(this) ? this.split('') : object
  var i = -1

  // robust检查
  if (!self.length) throw new TypeError(self + 'isn\'t an array.')
  if (!isFn(callback)) throw new TypeError('Array.prototype.forEach callback must be a function')

  while (++i < self.length) {
    // 如果this的属性不为number或该index的属性不存在,则不遍历(确保为类数组遍历)
    if (i in self) {
      // 若存在thisArg,增加调用时回调函数this指向
      if (thisArg) {
        callback.call(thisArg, self[i], i, object)
      } else {
        callback(self[i], i, object)
      }
    }
  }
}

function isString (str) {
  return Object.prototype.toString.call(str) === '[object String]'
}
function isFn (fn) {
  return Object.prototype.toString.call(str) === '[object Function]'
}
  • 其他的一些数组遍历函数大体上跟forEach函数差不多,map的过程在遍历函数内记录了一个新的数组,遍历完毕之后返回即可:
function map (callback, thisArg) {
  var object = new Object(this)
  var self = isString(this) ? this.split('') : object
  var i = -1
  // 生成的新数组
  var result = []

  if (!self.length) throw new TypeError(self + 'isn\'t an array.')
  if (!isFn(callback)) throw new TypeError('Array.prototype.forEach callback must be a function')

  while (++i < self.length) {
    if (i in self) {
      if (thisArg) {
        // 赋值
        result[i] = callback.call(thisArg, self[i], i, object)
      } else {
        result[i] = callback(self[i], i, object)
      }
    }
  }

  // 返回新数组
  return result
}
  • 剩下的如every, some, filter等遍历类型的操作,都是在while的内部做改动,其他部分大同小异。

深入

前面说到map, every, some, filter与forEach的结构其实大同小异,主要是while内部的逻辑有所不同。其实这里实现的forEach, map等都属于内部迭代器,forEach函数内部定义了迭代规则,外部只需一次初始调用即可。同时,由于是内部迭代器,每需要一种不同的迭代功能,就得写一份类似的代码,较为复杂的迭代需求就不方便通过写好的迭代函数来实现了,大部分情况下我们会重新封装一个带for () {}语句的函数以复用。

这时候我们来看看外部迭代器能替我们解决什么问题。外部迭代器相当于将迭代的功能装在了数据结构上,增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,减少了结构性的代码,基本写出来的都是需求的逻辑。

下面就是一个外部迭代器的实现,代码来自《JavaScript设计模式与开发实践》P105:

var Iterator = function (obj) {
  var current = 0
  var next = function () {
    current += 1
  }
  var isDone = function () {
    return current >= obj.length
  }
  var getCurrItem = function {
    return obj[current]
  }

  return {
    next: next,
    isDone: isDone,
    getCurrItem: getCurrItem,
  }
}

基于Class版:

class Iterator {
  constructor (obj) {
    this._current = 0
    this._data = obj
  }

  next () {
    this._current += 1
  }

  isDone () {
    return this._current >= this._data.length
  }

  getCurrItem () {
    return this._data[this._current]
  }
}
  • 实现一个compare函数:
function compare (iterator1, iterator2) {
  while (!iterator1.isDone() && !iterator2.isDone()) {
    if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {
      throw new Error('iterator1和iterator2不相等')
    }
    iterator1.next()
    iterator2.next()
  }

  alert('iterator1和iterator2相等')
}

var iterator1 = new Iterator([1, 2, 3])
var iterator2 = new Iterator([1, 2, 3])
compare(iterator1, iterator2)  // 输出:iterator1和iterator2相等

总结

那么使用外部迭代器和直接使用循环语句写逻辑有什么不同呢,很大程度上,使用外部迭代器减少了循环的嵌套,使代码更清晰,这在多重迭代上体现的尤为明显。如果需求只有一层循环,那大可直接使用循环语句。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant