1. 通过 underscore 的链式调用带来的思考

74 阅读3分钟

通过 underscore 的链式调用带来的思考

_([1, 2, 3]).reverse()  // [3, 2, 1]
const res = _.chain([4, 5, 6]).push(7, 8, 9).reverse().pop().value() // [9, 8, 7, 6, 5, 4]
console.log(res)

1. _()_.chain() 的比较得出什么?

  1. _() 我们可以看成是 _函数,执行后的结果, 那么带有一个叫做 reverse的方法 js function _ () { return { reverse() {} } }
  2. _.chain()表示函数_上面有个属性叫做chain, 并且chain是一个函数, 因为函数是一个一等公民,所以我们可以在_上添加属性 js function _() { return { reverse() {} } } _.chain = function () {}
  3. 但是chian之后又有push, 那么这样才合理
    function _() {
      return {
        reverse() {}
      }
    }
    _.chain = function () {
      return {
        push() {}
      }
    }
    
  4. 所谓链式调用,是方法后面接方法,chain后边可能加上push方法,也有可能在chain加上reverse..., 那么这样才合理
    function _() {
      return {
        reverse() {}
      }
    }
    _.chain = function () {
      return {
        push() {
          return {
            reverse () {},
            slice() {}
          }
        },
        reverse() {},
        slice() {}
      }
    }
    
    那么这样下去,就是一个无穷的死循环, 所以我们换个思路思考. 如果我们把push, reverse, slice 都作为实例上的方法,并且在每个方法执行之后,就把当前的实例返回

2. 换个姿势再来

  1. _当成构造函数

    function _(obj) {
      // 如果 obj 是 _ 的实例,则直接返回
      if (obj instanceof _) return obj
      // 保证如果用函数调用的方式,返回的依然是 _ 的实例
      if (!(this instanceof _)) return new _(obj)
      // 内置属性来存储
      this._wrapped = obj
    }
    _.prototype.reverse = function () {}
    _.chain = function () {}
    
    
  2. reverse 方法实现一下

    _.prototype.reverse = function () {
      return this._wrapped.reverse()
    }
    
  3. value 方法实现一下

    _.prototype.value = function() {
      return this._wrapped
    }
    
  4. 跑一下代码, 发现例子1通过, 可是 2 该怎么搞呢?

3. 实现例子2

  1. _.chain也是应该返回_的实例
    _.chain = function (obj) {
      return _(obj)
    }
    
  2. 这样下来发现_.chain_()创建的没什么区别,但是_.chain()创建出来的支持链式调用?这该怎么搞? 加一个 _chain的标志位来进行区分吧
    _.chain = function (obj) {
      const instance =  _(obj)
      instance._chain = true
      return instance
    }
    
  3. 那么我们先将push, slice的占槽搞定
      _.prototype.push = function () {}
      _.prototype.pop = function () {}
    
  4. 实现reverse
    _.prototype.reverse = function () {
      // 需要对 _wrapped 进行reverse 处理
      const obj = this._wrapped
      Array.prototype.reverse.apply(obj, arguments)
      // 将当前实例返回
      return this
    }
    
  5. 跑一下
    const res = _.chain([4, 5, 6]).push(7, 8, 9).reverse().pop().value()
    console.log(res) // [6, 5, 4]
    
  6. 发现一切尽在掌握之中
  7. case 1中的返回值不对了
    console.log(_([1, 2, 3]).reverse()) // 返回了 _ 的实例
    

4. 在 reverse, push, pop 的返回值需要区分是不是被chain包裹的

  1. 那么我们的标志位_chain就要起作用了

    function chainResult(instance, obj) {
      return instance._chain ? instance : obj
    }
    _.prototype.reverse = function () {
      // 需要对 _wrapped 进行reverse 处理
      const obj = this._wrapped
      Array.prototype.reverse.apply(obj, arguments)
      return chainResult(this, obj)
    }
    
  2. 同理pushpop也需要这样的操作

    function chainResult(instance, obj) {
      return instance._chain ? instance : obj
    }
    _.prototype.reverse = function () {
      // 需要对 _wrapped 进行reverse 处理
      const obj = this._wrapped
      Array.prototype.reverse.apply(obj, arguments)
      return chainResult(this, obj)
    }
    _.prototype.push = function () {
      const obj = this._wrapped
      Array.prototype.push.apply(obj, arguments)
      return chainResult(this, obj)
    }
    _.prototype.pop = function () {
      const obj = this._wrapped
      Array.prototype.pop.apply(obj, arguments)
      return chainResult(this, obj)
    }
    
  3. 跑一下case 2, 木问题啦

  4. 如果我们添加一个shiftunshift函数呢

  5. 我们直接按照reverse模板进行

    _.prototype.shift = function () {
      const obj = this._wrapped
      Array.prototype.shift.apply(obj, arguments)
      return chainResult(this, obj)
    }
    _.prototype.unshift = function () {
      const obj = this._wrapped
      Array.prototype.unshift.apply(obj, arguments)
      return chainResult(this, obj)
    }
    
  6. 测试下面例子

    const res = _.chain([4, 5, 6]).push(7, 8, 9).reverse().pop().shift().unshift(10, 11, 12)
    console.log(res.value())
    
  7. 也木问题啦

6. 总结

  1. 如果遇到链式调用,疯狂往OOP上靠
  2. 所有的操作(reverse, push, pop, shift, unshift)都要在当前实例上
  3. 在实例中搞出来一个属性(_wrapped)用来记录每次操作的结果值

7. 存在的问题

  1. reverse, push, pop, shift, unshift 的写法重复很多,需要提取出来
  2. 看看underscore的实现