Jamey's Jamey's
首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于

Jamey

首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
  • 深入系列

  • 专题系列

  • underscore系列

    • underscore 系列之如何写自己的 underscore
    • underscore 系列之链式调用
      • 📖. 前言
      • 一. jQuery
      • 二. _.chain
      • 三. _.value
      • 四. 最终代码
    • underscore 系列之内部函数 cb 和 optimizeCb
    • underscore 系列之内部函数 restArgs
    • underscore 系列之防冲突与 Utility Functions
    • underscore 系列之实现一个模板引擎(上)
    • underscore 系列之实现一个模板引擎(下)
    • underscore 系列之字符实体与 _.escape
    • underscore 的源码该如何阅读?
  • ES6系列

  • 模块化

  • 正则表达式

  • 单元测试

  • 微前端

  • 实用函数

  • Rollup

  • 解决方案

  • 《JavaScript》笔记
  • underscore系列
Jamey
2021-11-11
目录

underscore 系列之链式调用

# underscore 系列之链式调用

# 📖. 前言

本文接着上篇 《underscore 系列之如何写自己的 underscore》,阅读本篇前,希望你已经阅读了上一篇。

# 一. jQuery

我们都知道 jQuery 可以链式调用,比如:

$("div").eq(0).css("width", "200px").show();
1

我们写个简单的 demo 模拟链式调用:

function JQuery(selector) {
  this.elements = [];
  var nodeLists = document.getElementsByTagName(selector);
  for (var i = 0; i < nodeLists.length; i++) {
    this.elements.push(nodeLists[i]);
  }
  return this;
}

JQuery.prototype = {
  eq: function(num){
    this.elements = [this.elements[num]];
    return this;
  },
  css: function(prop, val) {
    this.elements.forEach(function(el){
      el.style[prop] = val;
    })
    return this;
  },
  show: function() {
    this.css('display', 'block');
    return this;
  }
}

window.$ = function(selector){
  return new JQuery(selector)
}

$("div").eq(0).css("width", "200px").show();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

jQuery 之所以能实现链式调用,关键就在于通过 return this,返回调用对象。再精简下 demo 就是:

var jQuery = {
  eq: function(){
    console.log('调用 eq 方法');
    return this;
  },
  show: function(){
    console.log('调用 show 方法');
    return this;
  }
}

jQuery.eq().show();
1
2
3
4
5
6
7
8
9
10
11
12

# 二. _.chain

在 underscore 中,默认不使用链式调用,但是如果你想使用链式调用,你可以通过 _.chain 函数实现:

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

我们看看 _.chain 这个方法都做了什么:

_.chain = function (obj) {
  var instance = _(obj);
  instance._chain = true;
  return instance;
};
1
2
3
4
5

我们以 [1, 2, 3] 为例,_.chain([1, 2, 3]) 会返回一个对象:

{
  _chain: true,
  _wrapped: [1, 2, 3]
}
1
2
3
4

该对象的原型上有着 underscore 的各种方法,我们可以直接调用这些方法。

但是问题在于原型上的这些方法并没有像 jQuery 一样,返回 this ,所以如果你调用了一次方法,就无法接着调用其他方法了……

但是试想下,我们将函数的返回值作为参数再传入 _.chain 函数中,不就可以接着调用其他方法了?

写一个精简的 Demo:

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

_.chain = function (obj) {
  var instance = _(obj);
  instance._chain = true;
  return instance;
};

_.prototype.push = function(num) {
  this._wrapped.push(num);
  return this._wrapped;
}

_.prototype.shift = function(num) {
  this._wrapped.shift()
  return this._wrapped
}

var res = _.chain([1, 2, 3]).push(4);
// 将上一个函数的返回值,传入 _.chain,然后再继续调用其他函数
var res2 = _.chain(res).shift();

console.log(res2); // [2, 3, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

然而这也太复杂了吧,难道 chain 这个过程不能是自动化的吗?如果我是开发者,我肯定希望直接写成:

_.chain([1, 2, 3]).push(4).shift();
1

所以我们再优化一下实现方式:

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

var chainResult = function (instance, obj) {
  return instance._chain ? _.chain(obj) : obj;
};

_.chain = function (obj) {
  var instance = _(obj);
  instance._chain = true;
  return instance;
};

_.prototype.push = function(num) {
  this._wrapped.push(num);
  return chainResult(this, this._wrapped)
}

_.prototype.shift = function() {
  this._wrapped.shift();
  return chainResult(this, this._wrapped);
}

var res = _.chain([1, 2, 3]).push(4).shift();

console.log(res._wrapped);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

我们在每个函数中,都用 chainResult 将函数的返回值包裹一遍,再生成一个类似以下这种形式的对象:

{
  _wrapped: some value, 
  _chain: true
}
1
2
3
4

该对象的原型上有各种函数,而这些函数的返回值作为参数传入了 chainResult,该函数又会返回这样一个对象,函数的返回值就保存在 _wrapped 中,这样就实现了链式调用。

_.chain 链式调用原理就是这样,可是这样的话,我们需要对每个函数都进行修改呀……

幸运的是,在 underscore 中,所有的函数是挂载到 _ 函数对象中的,_.prototype 上的函数是通过 _.mixin 函数将 _ 函数对象中的所有函数复制到 _.prototype 中的。

所以为了实现链式调用,我们还需要对上一篇 《underscore 系列之如何写自己的 underscore》 中的 _.mixin 方法进行一定修改:

// 修改前
var ArrayProto = Array.prototype;
var push = ArrayProto.push;

_.mixin = function(obj) {
  _.each(_.functions(obj), function(name) {
    var func = _[name] = obj[name];
    _.prototype[name] = function() {
      var args = [this._wrapped];
      push.apply(args, arguments);
      return func.apply(_, args);
    };
  });
  return _;
};

_.mixin(_);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 修改后
var ArrayProto = Array.prototype;
var push = ArrayProto.push;

var chainResult = function (instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
};

_.mixin = function(obj) {
  _.each(_.functions(obj), function(name) {
    var func = _[name] = obj[name];
    _.prototype[name] = function() {
      var args = [this._wrapped];
      push.apply(args, arguments);
      return chainResult(this, func.apply(_, args));
    };
  });
  return _;
};

_.mixin(_);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 三. _.value

根据上面的分析过程,我们知道如果我们打印:

console.log(_.chain([1, 2, 3]).push(4).shift());
1

其实会打印一个对象 {_chain: true, _wrapped: [2, 3, 4] }

可是我希望获得值是 [2, 3, 4] 呀!

所以,我们还需要提供一个 value 方法,当执行 value 方法的时候,就返回当前 _wrapped 的值。

_.prototype.value = function() {
  return this._wrapped;
};
1
2
3

此时调用方式为:

var arr = _.chain([1, 2, 3]).push(4).shift().value();
console.log(arr); // [2, 3, 4]
1
2

# 四. 最终代码

结合 上一篇文章,最终的 underscore 代码组织结构如下:

(function() {

  var root = (typeof self == 'object' && self.self == self && self) ||
      (typeof global == 'object' && global.global == global && global) ||
      this || {};

  var ArrayProto = Array.prototype;

  var push = ArrayProto.push;

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

  if (typeof exports != 'undefined' && !exports.nodeType) {
    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  _.VERSION = '0.2';

  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

  var isArrayLike = function(collection) {
      var length = collection.length;
      return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

  _.each = function(obj, callback) {
    var length, i = 0;

    if (isArrayLike(obj)) {
      length = obj.length;
      for (; i < length; 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;
  }

  _.isFunction = function(obj) {
    return typeof obj == 'function' || false;
  };

  _.functions = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  /**
   * 在 _.mixin(_) 前添加自己定义的方法
   */
  _.reverse = function(string){
    return string.split('').reverse().join('');
  }

  _.chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
  };

  var chainResult = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
  };

  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return chainResult(this, func.apply(_, args));
      };
    });
    return _;
  };

  _.mixin(_);

  _.prototype.value = function () {
    return this._wrapped;
  };

})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#JavaScript underscore
上次更新: 2022/07/01, 17:34:19
underscore 系列之如何写自己的 underscore
underscore 系列之内部函数 cb 和 optimizeCb

← underscore 系列之如何写自己的 underscore underscore 系列之内部函数 cb 和 optimizeCb→

Theme by Vdoing | Copyright © 2017-2023 Jamey | blog 闽ICP备19022664号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式