Underscore源码学习之如何实现链式调用

前言

underscore的版本是1.13.7

underscore的链式调用代码分析

让我们看下几个官方例子, 先调用chain函数,然后就可以链式调用数组的一些方法来处理数据,最后通过value()来拿到输出值。对于数组格式的数据处理非常方便,咋们只需要关心数据的输入和输出。

scss 复制代码
_.chain([1, 2, 3]).reverse().value();
// [3, 2, 1]

_.chain([1,2,3,200])
  .filter(function(num) { return num % 2 == 0; })
  .map(function(num) { return num * num })
  .value();
// [4, 40000]

_.chain(lyrics)
  .map(function(line) { return line.words.split(' '); })
  .flatten()
  .reduce(function(counts, word) {
    counts[word] = (counts[word] || 0) + 1;
    return counts;
  }, {})
  .value();
  // {lumberjack: 2, all: 4, night: 2 ... }

源码解读

按照chain的使用一步一步的解读源码

1. chain.js文件源码解读

主要目的是导出一个实例对象,{ _chain: true, _wrapped: [1, 2, 3], ...}这种结构

javascript 复制代码
// 从underscore.js文件中导入underscore函数
import _ from './underscore.js';

// Start chaining a wrapped Underscore object.
export default function chain(obj) {
// 执行该函数,获得一个实例对象
  var instance = _(obj);
  // 该实例对象上挂载_chain私有属性
  instance._chain = true;
  // 返回该实例对象,
  return instance;
}

2. underscore.js文件源码解读

声明了一个函数,并在该函数的原型对象上处理了value、toString等方法,最后返回把自己作为构造函数new的一个实例对象

javascript 复制代码
import { VERSION } from './_setup.js';

// 导出一个函数
export default function _(obj) {
   // 如果obj就是当前函数实例化的对象,直接返回
  if (obj instanceof _) return obj;
  // 自己调用自己实例化一个对象返回
  if (!(this instanceof _)) return new _(obj);
  // 将obj传参挂载到_wrapped私有属性上
  this._wrapped = obj;
}

// 记录_函数的版本
_.VERSION = VERSION;

// 在_函数的原型上挂载value方法,返回的值就是this._wrapped
_.prototype.value = function() {
  return this._wrapped;
};

// 原型上valueOf和toJSON方法都指向value方法
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

// 重写了toString方法,返回this._wrapped到字符串格式
_.prototype.toString = function() {
  return String(this._wrapped);
};

3. underscore-array-methods.js文件源码解读

在"_函数"原型上挂载数组原型的一些方法

javascript 复制代码
// 导入上文2中的underscore.js中的函数
import _ from './underscore.js';
// each是一个便利函数,类似于forEach
import each from './each.js';
import chainResult from './_chainResult.js';

// 数组的原型对象
var ArrayProto = Array.prototype

// Add all mutator `Array` functions to the wrapper.
// 在"_函数"原型上挂载数组原型的一些方法
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

  // 引用数组原型的方法  
  var method = ArrayProto[name];
  
  // 在_函数的原型上声明这些方法
  _.prototype[name] = function() {
    var obj = this._wrapped;
    if (obj != null) {
    
      // 注意obj此时是一个数组,apply的第一传参是obj
      // 例如执行代码let a = [1,2,3], Array.prototype.push.apply(a, [4])
      // 执行之后a的值就变成[1,2,3,4]了
      method.apply(obj, arguments);
     
     // 这里大概意思就是兼容IE低版本的写法,具体参照引用文档讲解
      if ((name === 'shift' || name === 'splice') && obj.length === 0) {
        delete obj[0];
      }
    }
    // 最后调用了chainResult函数,代码解读看下一个小点
    return chainResult(this, obj);
  };
});

// 这里为啥要把'concat', 'join', 'slice'单独提出来呢 ?
each(['concat', 'join', 'slice'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    var obj = this._wrapped;
    // 'concat'、'slice'返回新的数组,join返回新的字符串,obj不会变,只好赋值了
    if (obj != null) obj = method.apply(obj, arguments);
    return chainResult(this, obj);
  };
});

export default _;

4. _chainResult.js代码解读

如果instance上有_chain属性,构造一个新的实例返回,否则返回obj, 此时就有问题了,为啥不直接返回this,而要返回_(obj).chain()呢?因为obj的值不一定和instance._wrapped相同了!

javascript 复制代码
import _ from './underscore.js';

// Helper function to continue chaining intermediate results.
export default function chainResult(instance, obj) {
  // 用obj重新创建一个实例,是instance的更新版,
  return instance._chain ? _(obj).chain() : obj;
}

如何自己封装一个链式调用函数?

仿照上文简单的封装一个MyChain对象

javascript 复制代码
class MyChain {
  constructor(arg = []) {
      this.arg = arg
      this.addProp()
  }
  get value() {
      return this.arg
  }
  addProp() {
      ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'].forEach((funName) => {
          this[funName] = function () {
              Array.prototype[funName].apply(this.arg, arguments)
              return this
          }
      })

      const propList = ['concat', 'slice']
      propList.forEach(funName => {
          this[funName] = function () {
              this.arg = Array.prototype[funName].apply(this.arg, arguments)
              return this
          }
      })
  }
}

const chain = new MyChain()
console.log(chain.push(1, 2, 3).reverse().value) // [3,2,1]

总结

加深了链式调用的封装理解,如有问题,欢迎指出,Thanks

参考

juejin.cn/post/684490...

相关推荐
@大迁世界14 小时前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路14 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug14 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213814 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中14 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路14 小时前
GDAL 实现矢量合并
前端
hxjhnct14 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子15 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗15 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常15 小时前
我学习到的AG-UI的概念
前端