underscore 架构解析

使用方式

JQ类似,underscore也占用了一个全局对象;
但是underscore为用户提供了两种使用方式

_.unique();		// 通过构造函数的静态方法调用
_().unique();	// 通过实例化对象调用其原型链上方法

架构

我们由浅入深,一步步完善

初始架构

首先我们先支持两种调用方式

(function (root) {
  // _ 是一个构造函数
  var _ = function () {
    if(!(this instanceof _)){
      return new _();
    }
  }

  _.unique = function(){
    console.log(1);
  }

  _.prototype.unique = function(){
    console.log(1);
  }

  // 挂载到全局
  root._ = _;
})(this);

备注:

  • 构造函数的 return new _()_.prototype.fn 是为第二种方法服务的
  • 单纯的用构造函数的静态方法调用其实与直接调用一个用 function 声明的函数无异

通过以上代码我们可以保证两种使用方法都能正常使用

但是这样写引出了新的问题——同一个方法要在静态方法中写一遍,又要在原型链上写一遍,这样做是非常愚蠢的,当里面的方法有上百个的时候整个框架就会很臃肿

我们可以用 混入 来解决这个问题


混入

首先来看看 mixin 这个方法的逻辑

_.mixin = function(obj){
    _.each(arr, callback(){
    	xxx
    })
}

解析:

  1. mixin接收一个参数,这个参数是指要被混入的对象
  2. each 用传入的回调函数来对传入数组的每一项进行操作
  3. 在混入这个方法中each的第一个参数arr应该是被混入对象的静态方法的名字,第二个参数是将具体静态方法混入到_原型链上的操作
  4. each这个方法还可以在外部于其他用途被调用,所以第二点和第三点不是讲的完全一样的东西
(function (root) {
    var _ = function () {
        if(!(this instanceof _)){
            return new _();
        }
    }

    _.unique = function(){
        console.log(1);
    }

    // _.prototype.unique = function(){
    //   console.log(1);
    // }

    // 对数组的每一项进行操作
    _.each = function(arr, cb){
        for (var i = 0; i < arr.length; i++) {
            cb(arr[i]);
        }
    }

    // 用于将一个对象的静态方法名用数组存储起来并将这个数组返回
    _.functions = function(obj){
        var resultArr = [];
        for( var key in obj ){
            resultArr.push(key);
        }
        return resultArr;
    }

	// 这里的混入其实就是将一个对象上的静态方法混进 _ 的原型链中
    _.mixin = function(obj){
        _.each(_.functions(obj), function (key) {
            var fn = obj[key];
            _.prototype[key] = function () {
                fn();
            };
        });
    }

    _.mixin(_); // 将本构造函数的静态方法混入到原型链上
    // 挂载到全局
    root._ = _;
})(this);

现在之前的问题解决了。。


添加数据源

以上代码只是成功让两种使用方法跑起来,但是我们并没有传入数据进行处理

通常在开发中用户肯定是要处理数据才会使用这个框架,就像这样

var arr = [1,2,3,4,5,5,6];

_.unique(arr);
_(arr).unique();

第一种使用方法调用构造函数静态方法的直接在函数上设置形参就行了

值得注意的是第二种使用方法,这种方法生成了一个_实例,所以我们需要在实例化的时候添加一个属性用以存储数据源

然后在混入的时候在原型链上绑定对应的方法

(function (root) {
  var _ = function (val) {
    if(!(this instanceof _)){
      return new _(val);
    }
    this.val = val;
  }


  // 静态方法 (可在外部调用)
  _.unique = function(arr, cb){
    var resultArr = [];
    var item;
    for (var i = 0; i < arr.length; i++) {
      item = cb ? cb(arr[i]) : arr[i];
      if( resultArr.indexOf(item) === -1 ){
        resultArr.push(item);
      }
    }
    return resultArr;
  }

  // 用传入的回调函数对数组每个成员进行操作
  _.each = function(arr, cb){
    for (var i = 0; i < arr.length; i++) {
      cb(arr[i]);
    }
  }

  // 用于将一个对象的静态方法名用数组存储起来并将这个数组返回
  _.functions = function(obj){
    var resultArr = [];
    for( var key in obj ){
      resultArr.push(key);
    }
    return resultArr;
  }

  // 混入
  _.mixin = function(obj){
    _.each(_.functions(obj), function (key) {
      var fn = obj[key];
        
      // 使用实例化对象的方法来调用相关函数时会指向以下函数
      _.prototype[key] = function () {
        // 在这个函数中,用户可能会传入其他参数,我们要对这些参数进行处理
        var args = [this.val];  // 首先要有传入的数据源
        Array.prototype.push.apply(args, arguments);  // 再将传入参数和数据源拼接在一起
        return fn.apply(this, args);
      };
    });
  }

  // 将本构造函数的静态方法混入到原型链上
  _.mixin(_);
  // 挂载到全局
  root._ = _;
})(this);

实际效果如下

var arr = [1,2,3,4,5,5,6];

_.unique(arr);		// [1, 2, 3, 4, 5, 6]
_(arr).unique();	// [1, 2, 3, 4, 5, 6]
var arr = [1,2,3,4,5,5,6,'a','A'];
function toLowerCase(item){
    return typeof item === 'string' ? item.toLowerCase() : item;
}

console.log(_.unique(arr, toLowerCase)); // [1, 2, 3, 4, 5, 6, "a"]
console.log(_(arr).unique(toLowerCase)); // [1, 2, 3, 4, 5, 6, "a"]

就很棒!

然而问题又来了,到目前为止,我们只能使用一次函数而不能进行链式调用


链式调用

如果要进行链式调用,那么我们可以使用一个中间站来决定是否进行链式调用;如果是则后面继续点调用其他函数,如果不是,则直接返回结果

(function (root) {
  var _ = function (val) {
    if(!(this instanceof _)){
      return new _(val);
    }
    this.val = val;
  }
  /* 私有方法 */
  // 判断当前函数执行完毕后的返回结果
  // 如果之前开启了链式调用模式,则后面会一直使用链式调用
  var useChain = function(instance, val){
    if(instance._chain){
      instance.val = val;
      return instance;
    }
    return val;
  }

  /* 静态方法 (可在外部调用) */
  // 链式调用标记 如果决定链式调用,则利用上次函数返回结果进行再包装
  _.chain = function(val){
    var instance = _(val);
    instance._chain = true;
    return instance;
  }

  // 数组去重
  _.unique = function(arr, cb){
    var resultArr = [];
    var item;
    for (var i = 0; i < arr.length; i++) {
      item = cb ? cb(arr[i]) : arr[i];
      if( resultArr.indexOf(item) === -1 ){
        resultArr.push(item);
      }
    }
    return resultArr;
  }

  // 取数组最大值
  _.max = function(arr){
    var result = arr[0] ? arr[0] : false;
    for (var i = 1; i < arr.length; i++) {
      if( arr[i] > result ) result = arr[i];
    }
    return result;
  }

  // 用传入的回调函数对数组每个成员进行操作
  _.each = function(arr, cb){
    for (var i = 0; i < arr.length; i++) {
      cb(arr[i]);
    }
  }

  // 用于将一个对象的静态方法名用数组存储起来并将这个数组返回
  _.functions = function(obj){
    var resultArr = [];
    for( var key in obj ){
      resultArr.push(key);
    }
    return resultArr;
  }

  // 混入
  _.mixin = function(obj){
    _.each(_.functions(obj), function (key) {
      var fn = obj[key];
      // 使用实例化对象的方法来调用相关函数时会指向以下函数
      _.prototype[key] = function () {
        // 在这个函数中,可能用户会传入其他参数,我们要对这些参数进行处理
        var args = [this.val];  // 首先要有传入的数据源
        Array.prototype.push.apply(args, arguments);  // 将传入参数和数据源拼接在一起
        return useChain(this, fn.apply(this, args));
      };
    });
  }

  // 将本构造函数的静态方法混入到原型链上
  _.mixin(_);
  // 挂载到全局
  root._ = _;
})(this);

解析:

  • 这段代码相比之前的新增了一个私有方法 useChain 和一个静态方法chain
  • 注意这里的mixin函数,返回的时候使用 useChain 包装了一下,useChain的作用是判断当前实例是否要进行链式调用,如果是则返回一个underscore实例,如果否则直接把当前值返回出去
  • 这里的静态方法chain用来标记当前实例是否要进行链式调用

实际效果如下

var arr = [1, 2, 3, 4, 5, 5, 6, 'a', 'A'];

function toLowerCase(item) {
  return typeof item === 'string' ? item.toLowerCase() : item;
}

console.log(_.unique(arr, toLowerCase));
// [1, 2, 3, 4, 5, 6, "a"]
console.log(_.chain(arr).unique(toLowerCase).max().val);
// 6
console.log(_(arr).chain().unique(toLowerCase));
// {val: Array(7), _chain: true}

按照以上方式搭建了初始模型之后,后面需要什么新的方法就直接添加静态方法就可以了!


当然underscore不止以上所述,有兴趣的同学可以到官网看看~~

有问题的话欢迎评论区或私信留言~

如果本文对你有帮助,点个赞再走吧!♪(・ω・)ノ

你可能感兴趣的:(javaScript)