Skip to content

你不知道的 _ 调用方式 #1

@ilcherry

Description

@ilcherry

underscore.js 三种调用方式

函数式调用

// index.js

const { map, filter } = require('underscore');

map([1, 2, 3], v => v + 1); // [2, 3, 4]
filter([1, 2, 3], v => v >= 2); //[2, 3]

下面我们来看看 underscore.js v1.9.2 源码中, 关于 _ 函数部分

// underscore.js v1.9.2
// ...
var _ = function(obj) {
  if (obj instanceof _) return obj;
  if (!(this instanceof _)) return new _(obj);
  this._wrapped = obj;
};

_.each = _.forEach = () => { /* some code */};

_.map = () => {/* some code */}

//...

通过这段代码,我们可以知道,underscore.js 源码里声明了一个名称为 _ 的函数,_.each、_.forEach 及 _.map 等等方法是 _ 函数(本质上也是对象)的实例属性,可以用普通对象描述,加以理解:

// underscore.js

const _ = {
  each: () => {},
  forEach: () => {},
  map: () => {}
  // ...
};

module.exports = _;
// index.js

const { map, filter } = require('underscore.js');

map([1, 2, 3], v => v + 1); // [2, 3, 4]
filter([1, 2, 3], v => v >= 2); //[2, 3]

通常情况下,我们使用 underscore.js 里的方法,都是按照以上的方式进行使用。

原生 js 调用

// underscore.js v1.9.2

const _ = require('underscore');

const result = _([1,2,3]).map(v => v + 1) // [2, 3, 4]
// 经过 map 方法返回的结果是原生数组,则只能使用 js 语法中的数组方法进行。
const filterResult = _([1,2,3]).map(v => v + 1).filter(v => v >= 3) // [3, 4]

_([1, 2, 3]).map() 是面向对象式的调用方式。在源码里声明了一个 _ 函数,当使用 new _(), 该 _ 函数作为构造函数。在 underscore 源码中:

// underscore.js v1.9.2

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

_(obj) 这样的调用形式中,即执行 new _(obj) ,创建了一个 _ 的实例, this 指向创建的实例对象,同时也创建它的原型对象(_.prototype)。

// underscore.js v1.9.2

_.mixin = function(obj) {
  /* 
    _.functions(obj) 返回一个 _ 实例上的所有方法名组成的数组
  */
  _.each(_.functions(obj), function(name) {
    var func = (_[name] = obj[name]);
    /* 
      将 _ 函数(对象)上的方法全部添加到 _.prototype 对象上
    */
    _.prototype[name] = function() {
      var args = [this._wrapped];
      push.apply(args, arguments);
      /* 
        最后都要返回 _ 实例,才能启用链式调用
        this 指向 _.prototype
      */
      return chainResult(this, func.apply(_, args));
    };
  });

  /* 
    _ 是一个类
  */
  return _;
};

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

_.mixin(_) 的作用是把 _ 函数(本质上是对象)上的方法全部添加到 _.prototype 对象上。再来看看 chainResult 函数。

// underscore.js v1.9.2

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

chainResult(this, func.apply(_, args)) 中,this 指向的是 _ 类的实例,根据 chainResult 函数的定义可知, 如果 _ 实例的 _chain 属性为 false, 则直接返回 func.apply(_, args) 的运行结果,其结果一定是原生 js 数据类型。_ 实例的 _chain 属性为 true, 则返回 _{_wrapped: Array(length), _chain: true}。

// underscore.js v1.9.2

_.chain = function(obj) {
    /* 
      这里的 _(obj) === new _(obj)
    */
    var instance = _(obj);
    // 设置 _chain 属性为 true
    instance._chain = true;
    // 返回创建的实例
    return instance;
  };

只要执行 _(obj).chain() 一次,_chain 的值就会被永久设置成 true。

以 _([1,2,3]).map(v => v + 1) 形式使用,返回的结果是原生数组,则只能使用 js 语法中的数组方法。

// underscore.js v1.9.2

const _ = require('underscore');
// 经过 map 方法返回的结果是原生数组,则只能使用 js 语法中的数组方法进行。
const filterResult = _([1,2,3]).map(v => v + 1).filter(v => v >= 3) // [3, 4]
// 诸如 compact、where、reject 等方法接着以链式使用

使用 underscore 所有方法的链式调用

// underscore.js v1.9.2

const _ = require('underscore');

_chain([1, 2, 3]).map(v => v + 1).filter(v => v >= 3) // _{_wrapped: Array(2), _chain: true}

_([1, 2, 3]).chain().map(v => v + 1).filter(v => v >=3) // _{_wrapped: Array(2), _chain: true}

原生 js 调用 部分我讲到,只要调用一次 chain () 方法,就会将它的 _chain 设置为 true,之后每一次调用方法,返回值均为 _ 类的实例,所以它能以链式方式调用 underscore.js 里提供的所有方法。

// underscore.js v1.9.2

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

_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

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

因为这四个方法没有返回 _ 类的实例,所以以链式方式调用这四个方法时,会终止链式。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions