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...

相关推荐
qq. 28040339843 小时前
CSS层叠顺序
前端·css
喝拿铁写前端3 小时前
SmartField AI:让每个字段都找到归属!
前端·算法
猫猫不是喵喵.3 小时前
vue 路由
前端·javascript·vue.js
烛阴3 小时前
JavaScript Import/Export:告别混乱,拥抱模块化!
前端·javascript
bin91534 小时前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例12,TableView16_12 拖拽动画示例
前端·javascript·vue.js·ecmascript·deepseek
GISer_Jing4 小时前
[Html]overflow: auto 失效原因,flex 1却未设置min-height &overflow的几个属性以及应用场景
前端·html
程序员黄同学4 小时前
解释 Webpack 中的模块打包机制,如何配置 Webpack 进行项目构建?
前端·webpack·node.js
拉不动的猪4 小时前
vue自定义“权限控制”指令
前端·javascript·vue.js
再学一点就睡4 小时前
浏览器页面渲染机制深度解析:从构建 DOM 到 transform 高效渲染的底层逻辑
前端·css
拉不动的猪5 小时前
刷刷题48 (setState常规问答)
前端·react.js·面试