专栏首页小皮咖underscore 诞生记(二)—— 链式调用与混入(mixin)

underscore 诞生记(二)—— 链式调用与混入(mixin)

上篇文章讲述了 underscore 的基本结构搭建,本文继续讲链式调用与混入。

如果你还没看过第一篇文章,请点击 “underscore 诞生记(一)—— 基本结构搭建”

链式调用

在 JQuery 中,我们经常使用到链式调用,如:

$('.div')
  .css('color', 'red')
  .show();

那么在 underscore 中,是否支持链式调用呢?答案是支持的,只不过默认不开启链式调用罢了。

想要实现链式调用,通常我们会在支持链式调用的函数中返回对象本身:

let car = {
  run(name) {
    console.log(`${name}老司机开车啦喂!`);
    return this;
  },
  stop() {
    console.log('车停了');
  },
};

car.run('奔驰').stop();

// 奔驰老司机开车啦喂!
// 车停了
复制代码

那么在每个 _ 方法下都 return this , 显然不大优雅缺乏可控性!尝试着写个通用方法 chain() 开启链式调用。

_.chain = function(obj) {
  // 获得一个经underscore包裹后的实例
  var instance = _(obj);
  // 标识当前实例支持链式调用
  instance._chain = true;
  return instance;
};

// 小试牛刀
_.chain([1, 2, 3]);
/* 
{
    _chain: true,
    _wrapped: [1, 2, 3]
}
 */
复制代码

返回的为一个实例对象,后面的方法判断 _chain 属性是否为 true,为 true 的话再调用一次 chain() 方法再返回原来实例即可。我们在之前用于给 prototype 复制方法的 each() 函数加入判断吧

var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
  var func = _[name];
  _.prototype[name] = function() {
    var args = [this._wrapped];
    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现
    push.apply(args, arguments);
    return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args);
  };
});
复制代码

有点冗长,将 return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args); 改造下,

// 判断是否需要链式调用
var chainResult = function(instance, obj) {
  return instance._chain ? _(obj).chain() : obj;
};
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
  var func = _[name];
  _.prototype[name] = function() {
    var args = [this._wrapped];
    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现
    push.apply(args, arguments);
    return chainResult(this, func.apply(_, args));
  };
});
复制代码

好了,试试看效果:

_.chain([1, 2, 3])
  .each(function(item) {
    console.log(item);
  })
  .each(function(item) {
    console.log(item);
  });
// 1 2 3 1 2 3
// {_wrapped: [1,2,3], _chain: true}
复制代码

混入(mixin)

underscore 很强大,功能也很齐全,但有时候也不能满足所有人的需求。我们想创建一些方法,让它挂载在 _ 上,这样我们全局也可以调用到这些方法,作为一款强大的方法库,也应该提供这种接口,让用户自定添加方法,ok, let us do it !

我们先定义一个 mixin 方法

_.mixin = function(obj) {};

// `obj` 为一个类似 `_` 的对象。传入的这个对象,也需要遍历一次,并且复制方法于 prototype 属性上。详细代码如下:
_.mixin = function(obj) {
  _.each(_.functions(obj), function(name) {
    var func = (_[name] = obj[name]);
    _.prototype[name] = function() {
      var args = [this._wrapped];
      push.apply(args, arguments);
      // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现
      return chainResult(this, func.apply(_, args));
    };
  });
  return _;
};
复制代码

看到这里,你会发现,我们在方法的最后遍历赋值给_.prototype方法,其实就是一次mixin() 的调用.

_.each(_.functions(_), function(name) {
  var func = _[name];
  _.prototype[name] = function() {
    var args = [this._wrapped];
    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现
    push.apply(args, arguments);
    return func.apply(_, args);
  };
});

// 简化为
_.mixin(_);
复制代码

最终代码

(function() {
  // root 为挂载对象,为 self 或 global 或 this 或 {}
  var root =
    (typeof self == 'object' && self.self === self && self) ||
    (typeof global == 'object' && global.global === global && global) ||
    this ||
    {};

  var _ = function(obj) {
    // 如果传入的是实例后对象,返回它
    if (obj instanceof _) return obj;
    // 如果还没有实例化,new _(obj)
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // 最大数值
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var ArrayProto = Array.prototype;
  var push = ArrayProto.push;
  // 判断是否为数组
  var isArrayLike = function(collection) {
    var length = collection.length;
    return (
      typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX
    );
  };

  // 判断是否需要链式调用
  var chainResult = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
  };

  root._ = _;

  _.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧

  /**
   * 字符串倒装
   */
  _.reverse = function(string) {
    return string
      .split('')
      .reverse()
      .join('');
  };

  /**
   * 判断是否为 function
   */
  _.isFunction = function(obj) {
    return typeof obj == 'function' || false;
  };
  // 链式调用方法
  _.chain = function(obj) {
    // 获得一个经underscore包裹后的实例
    var instance = _(obj);
    // 标识当前实例支持链式调用
    instance._chain = true;
    return instance;
  };
  /**
   * 获取_的所有属性函数名
   */
  _.functions = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };
  /**
   * 数组或对象遍历方法,并返回修改后的对象或数组
   * @param iteratee 回调函数
   * @param context 回调函数中this的指向
   */
  _.map = function(obj, iteratee, context) {
    var length = obj.length,
      results = Array(length);
    for (var index = 0; index < length; index++) {
      results[index] = iteratee.call(context, obj[index], index, obj);
    }

    return results;
  };

  /**
   * 数组或对象遍历方法
   */
  _.each = function(obj, callback) {
    var length,
      i = 0;

    if (isArrayLike(obj)) {
      // 数组
      length = obj.length;
      for (; i < length; i++) {
        //   这里隐式的调用了一次 callback.call(obj[i], obj[i], i);
        if (callback.call(obj[i], obj[i], i) === false) {
          break;
        }
      }
    } else {
      // 对象
      for (i in obj) {
        if (callback.call(obj[i], obj[i], i) === false) {
          break;
        }
      }
    }

    return obj;
  };
  /*
   * 混入方法 mixin
   */
  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = (_[name] = obj[name]);
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现
        return chainResult(this, func.apply(_, args));
      };
    });
    return _;
  };
  _.mixin(_);
})();
复制代码
本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:https://juejin.im/user/5af17df4518825672a02e1f5/posts复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 学习underscore源码整体架构,打造属于自己的函数式编程类库

    上一篇文章写了 jQuery整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库

    若川
  • 深入解析Underscore.js源码架构

    Underscore.js是很有名的一个工具库,我也经常用他来处理对象,数组等,本文会深入解析Underscore源码架构,跟大家一起学习下他源码的亮点,然后模...

    蒋鹏飞
  • Underscore链式调用

    回归今天分享的主题,underscore,这个函数库在业界内很出名,但是本人却从来没用过,估计只有那些算法写的多的才会用吧。underscore兼容了低版本浏览...

    wade
  • 学习 lodash 源码整体架构,打造属于自己的函数式编程类库

    这是 学习源码整体架构系列第三篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。文章学习的...

    ConardLi
  • 【Sass/SCSS】预加载器中的“轩辕剑”

    随着前端技术发展的越来越迅速,前端的样式也需要更加贴近时代的审美,那么CSS就需要承担更多的工作,(强调!这不是煽情!这是宣讲背景。?),为了给CSS怼上去,预...

    用户4268038
  • 前端VUE【实战】—— mixin混入用法总结

    单纯组件引用: 父组件 + 子组件 >>> 父组件 + 子组件 mixins: 父组件 + 子组件 >>> new父组件

    前端江太公
  • 初探Vue的Mixin混入

    在停更了近一周之后(假期已完美结束),胡哥又开启了文章的每日更新,与大家一起分享、讨论大前端的技术原理与项目实践。抽丝剥茧,让技术方案可见可闻;归纳整理,让知识...

    胡哥有话说
  • 面试官:说说你对vue的mixin的理解,有哪些应用场景?

    Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类

    @超人
  • 已阅冴羽大佬文章

    https://juejin.cn/post/6958361473953300488

    达达前端
  • Vue 之 Mixins的使用

    同名钩子函数将会混合为一个数组,都将被调用到,但是混入对象的钩子将在组件自身钩子之前调用。

    愤怒的小鸟
  • Vue3 混入

    混入 (mixins)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

    陈不成i
  • Vue.js 混入

    混入 (mixins)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

    陈不成i
  • mixin-- 混入

    mixin 是 vue 组件复用功能的技术之一 他可以把多个组件中重复出现的属性和方法进行封装方便多次调用

    CRMEB商城源码

扫码关注腾讯云开发者

领取腾讯云代金券