资讯专栏INFORMATION COLUMN

underscore 诞生记(一)—— 基本结构搭建

xiaolinbang / 2646人阅读

摘要:简介是一款成熟可靠的第三方开源库,正如统一了不同浏览器之间的操作的差异,让我们可以简单地对进行操作,则提供了一套完善的函数式编程的接口,让我们更方便地在中实现函数式编程。

1. 简介

underscore 是一款成熟可靠的第三方开源库,正如 jQuery 统一了不同浏览器之间的 DOM 操作的差异,让我们可以简单地对 DOM 进行操作,underscore 则提供了一套完善的函数式编程的接口,让我们更方便地在 JavaScript 中实现函数式编程。

jQuery 在加载时,会把自身绑定到唯一的全局变量 $ 上,underscore 与其类似,会把自身绑定到唯一的全局变量 _ 上,这也是为啥它的名字叫 underscore 的原因。

在搭建 underscore 之前,让我们先来了解一下什么是 “立即执行函数(IIFE)”.

2. 立即执行函数(IIFE)

立即执行函数,顾名思义,就是定义好的匿名函数立即执行,写法如下:

(function(name) {
  console.log(name);
})("suporka");

其作用是:通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。

// 函数外部拿不到内部的变量,因此不会造成变量污染,内部的变量在内部使用即可
(function() {
  var name = "suporka";
})();

console.log(name); // name is undefinded
3. 全局变量 _ 的挂载

当我们在浏览器中使用 _.map([1,2,3], function(item){console.log(item)}) 时, _ 是挂载在 Window对象上的,如果我们想在 node 环境中使用呢 ?

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

  // _ 应该是一个对象,对象内有属性函数
  var _ = {};
  root._ = _;
  _.VERSION = "1.9.1"; // 给我们的 underscore 一个版本号吧
})();
4. 函数式风格 && 面向对象风格的双重实现

首先我们实现一个倒装字符串的方法

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

  // _ 应该是一个对象,对象内有属性函数
  var _ = {};

  root._ = _;

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

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

_.reverse("suporka"); // akropus

不错,很快实现,但是这种是函数式写法,调用一个函数去实现,如果我们要实现面向对象写法呢?如 _("suporka").reverse()! underscore 是支持这种写法的,仔细观察 _("suporka") , 你会发现,_ 是一个函数啊,和我们前面定义的 var _ = {}; 不一致,那么该怎么实现呢?

实例原型

我们先测试一下:如果 _ 为函数,我们需要保存其传进来的参数 obj . new _() 生成一个实例原型对象

function _(obj) {
  this._wrapped = obj;
}
_.reverse = function(string) {
  return string
    .split("")
    .reverse()
    .join("");
};
_.reverse("suporka"); // "akropus", 函数式调用没问题

new _("suporka");

从图中我们可以看出,实例原型对象的 __proto__ (原型)的 constructor 构造函数指回了原来的 _(obj) 函数,要调用其 reverse() 方法只能 new _("suporka").constructor.reverse()多了一个层级,不符合我们原本的期望。那我们不如在_proto_ 属性下增加一个和 reverse 一样的函数,这样不就可以直接调用了吗?

let us try it !

function _(obj) {
  this._wrapped = obj;
}
_.reverse = function(string) {
  return string
    .split("")
    .reverse()
    .join("");
};
_.reverse("suporka"); // "akropus", 函数式调用没问题

_.prototype.reverse = function() {
  return this._wrapped
    .split("")
    .reverse()
    .join("");
};
new _("suporka").reverse(); // "akropus", 面向对象式调用没问题
5. 改造 _() function

new _("suporka").reverse() 有点累赘,去掉 new, 重写 function _()

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

_("suporka").reverse(); // "akropus", 面向对象式调用没问题
6. 写一个迭代函数 map()
/**
 * 数组或对象遍历方法,并返回修改后的对象或数组
 * @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;
};

_.prototype.map = function(iteratee, context) {
  var length = this._wrapped.length,
    results = Array(length);
  for (var index = 0; index < length; index++) {
    results[index] = iteratee.call(
      context,
      this._wrapped[index],
      index,
      this._wrapped
    );
  }

  return results;
};

_([1, 2, 3]).map(
  function(item) {
    console.log(item + this.value);
  },
  { value: 1 }
); // 2,3,4
_.map(
  [1, 2, 3],
  function(item) {
    console.log(item + this.value);
  },
  { value: 1 }
); // 2,3,4

嗯嗯,真好,完美实现。到这里你会发现一个问题,每次我新增一个方法,都得在 prototype 上同时写多一次这个相似函数,你会发现两者之间只是 obj 换成了 this._wrapped.有没有办法让它自动生成呢?答案肯定是有!

7. 自动创建原型方法

在这之前,我们需要先实现一个遍历方法 each(),如下:

// 最大数值
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++) {
      //   这里隐式的调用了一次 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;
};

用 each() 来遍历 _ 上挂载的所有方法函数,并给 prototype 创建相应的方法函数。那么,在此之前,我们需要知道 _ 上挂载了哪些方法名,来写个 functions() 实现它

/**
 * 判断是否为 function
 */
_.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();
};

用 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 func.apply(_, args);
  };
});
7. 当前最终代码
(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
    );
  };

  root._ = _;

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

  /**
   * 字符串倒装
   */
  _.reverse = function(string) {
    return string
      .split("")
      .reverse()
      .join("");
  };
  /**
   * 判断是否为 function
   */
  _.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();
  };
  /**
   * 数组或对象遍历方法,并返回修改后的对象或数组
   * @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;
  };

  _.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);
    };
  });
})();
未完待续,静待下篇

前端进阶小书(advanced_front_end)

前端每日一题(daily-question)

webpack4 搭建 Vue 应用(createVue)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103887.html

相关文章

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

    摘要:上篇文章讲述了的基本结构搭建,本文继续讲链式调用与混入。获得一个经包裹后的实例标识当前实例支持链式调用小试牛刀返回的为一个实例对象,后面的方法判断属性是否为为的话再调用一次方法再返回原来实例即可。 showImg(https://segmentfault.com/img/bVbrSwH?w=1374&h=773); 上篇文章讲述了 underscore 的基本结构搭建,本文继续讲链式调...

    yck 评论0 收藏0
  • 采用vue+webpack构建的单页应用——私人博客MintloG诞生

    摘要:我采用原生编写后台,因为感觉增删改查的功能很简单,就懒得用框架了其实是不会。浏览模式它也有一个,用来切换文章列表和文章详情,也就是和编辑模式它加载了作为工具栏,然后可以进行文章的撰写与修改。 介绍 项目地址:https://github.com/jrainlau/MintloG (特别乱,参考就好-_-|||)showImg(https://segmentfault.com/img/b...

    Terry_Tai 评论0 收藏0
  • 即将立秋的《课多周刊》(第2期)

    摘要:即将立秋的课多周刊第期我们的微信公众号,更多精彩内容皆在微信公众号,欢迎关注。若有帮助,请把课多周刊推荐给你的朋友,你的支持是我们最大的动力。课多周刊机器人运营中心是如何玩转起来的分享课多周刊是如何运营并坚持下来的。 即将立秋的《课多周刊》(第2期) 我们的微信公众号:fed-talk,更多精彩内容皆在微信公众号,欢迎关注。 若有帮助,请把 课多周刊 推荐给你的朋友,你的支持是我们最大...

    ruicbAndroid 评论0 收藏0
  • 即将立秋的《课多周刊》(第2期)

    摘要:即将立秋的课多周刊第期我们的微信公众号,更多精彩内容皆在微信公众号,欢迎关注。若有帮助,请把课多周刊推荐给你的朋友,你的支持是我们最大的动力。课多周刊机器人运营中心是如何玩转起来的分享课多周刊是如何运营并坚持下来的。 即将立秋的《课多周刊》(第2期) 我们的微信公众号:fed-talk,更多精彩内容皆在微信公众号,欢迎关注。 若有帮助,请把 课多周刊 推荐给你的朋友,你的支持是我们最大...

    MRZYD 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<