当前位置: 首页 > news >正文

Underscore 整体架构浅析

前言

终于,楼主的「Underscore 源码解读系列」underscore-analysis 即将进入尾声,关注下 timeline 会发现楼主最近加快了解读速度。十一月,多事之秋,最近好多事情搞的楼主心力憔悴,身心俱疲,也想尽快把这个系列完结掉,也好了却一件心事。

本文预计是解读系列的倒数第二篇,最后一篇那么显然就是大总结了。楼主的 Underscore 系列解读完整版地址 https://github.com/hanzichi/underscore-analysis

常规调用

之前写的文章,关注点大多在具体的方法,具体的知识细节,也有读者留言建议楼主讲讲整体架构,这是必须会讲的,只是楼主把它安排在了最后,也就是本文,因为楼主觉得不掌握整体架构对于具体方法的理解也是没有大的问题的。

Underscore 大多数时候的调用形式为 _.funcName(xx, xx),这也是 文档中 的调用方式。

_.each([1, 2, 3], alert);

最简单的实现方式,我们可以把 _ 看做一个简单的对象:

var _ = {};
_.each = function() {
  // ...
};

在 JavaScript 中,一切皆对象,实际上,源码中的 _ 变量是一个方法:

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

为什么会是方法?我们接下去看。

OOP

Underscore 支持 OOP 形式的调用:

_([1, 2, 3]).each(alert);

这其实是非常经典的「无 new 构造」,_ 其实就是一个 构造函数_([1, 2, 3]) 的结果就是一个对象实例,该实例有个 _wrapped 属性,属性值是 [1, 2, 3]。实例要调用 each 方法,其本身没有这个方法,那么应该来自原型链,也就是说 _.prototype 上应该有这个方法,那么,方法是如何挂载上去的呢?

方法挂载

现在我们已经明确以下两点:

  1. _ 是一个函数(支持无 new 调用的构造函数)
  2. _ 的属性有很多方法,比如 _.each_.template 等等

我们的目标是让 _ 的构造实例也能调用这些方法。仔细想想,其实也不难,我们可以遍历 _ 上的属性,如果属性值类型是函数,那么就将函数挂到 _ 的原型链上去。

源码中用来完成这件事的是 _.mixin 方法:

// Add your own custom functions to the Underscore object.
// 可向 underscore 函数库扩展自己的方法
// obj 参数必须是一个对象(JavaScript 中一切皆对象)
// 且自己的方法定义在 obj 的属性上
// 如 obj.myFunc = function() {...}
// 形如 {myFunc: function(){}}
// 之后便可使用如下: _.myFunc(..) 或者 OOP _(..).myFunc(..)
_.mixin = function(obj) {
  // 遍历 obj 的 key,将方法挂载到 Underscore 上
  // 其实是将方法浅拷贝到 _.prototype 上
  _.each(_.functions(obj), function(name) {
    // 直接把方法挂载到 _[name] 上
    // 调用类似 _.myFunc([1, 2, 3], ..)
    var func = _[name] = obj[name];

    // 浅拷贝
    // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用
    _.prototype[name] = function() {
      // 第一个参数
      var args = [this._wrapped];

      // arguments 为 name 方法需要的其他参数
      push.apply(args, arguments);
      // 执行 func 方法
      // 支持链式操作
      return result(this, func.apply(_, args));
    };
  });
};

// Add all of the Underscore functions to the wrapper object.
// 将前面定义的 underscore 方法添加给包装过的对象
// 即添加到 _.prototype 中
// 使 underscore 支持面向对象形式的调用
_.mixin(_);

_.mixin 方法可以向 Underscore 库增加自己定义的方法:

_.mixin({
  capitalize: function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"

同时,Underscore 也加入了一些 Array 原生的方法:

// Add all mutator Array functions to the wrapper.
// 将 Array 原型链上有的方法都添加到 underscore 中
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    var obj = this._wrapped;
    method.apply(obj, arguments);

    if ((name === 'shift' || name === 'splice') && obj.length === 0)
      delete obj[0];

    // 支持链式操作
    return result(this, obj);
  };
});

// Add all accessor Array functions to the wrapper.
// 添加 concat、join、slice 等数组原生方法给 Underscore
_.each(['concat', 'join', 'slice'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    return result(this, method.apply(this._wrapped, arguments));
  };
});

链式调用

Underscore 也支持链式调用:

// 非 OOP 链式调用
_.chain([1, 2, 3])
  .map(function(a) {return a * 2;})
  .reverse()
  .value(); // [6, 4, 2]

// OOP 链式调用
_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

乍一看似乎有 OOP 和非 OOP 两种链式调用形式,其实只是一种,_.chain([1, 2, 3])_([1, 2, 3]).chain() 的结果是一样的。如何实现的?我们深入 chain 方法看下。

_.chain = function(obj) {
  // 无论是否 OOP 调用,都会转为 OOP 形式
  // 并且给新的构造对象添加了一个 _chain 属性
  var instance = _(obj);

  // 标记是否使用链式操作
  instance._chain = true;

  // 返回 OOP 对象
  // 可以看到该 instance 对象除了多了个 _chain 属性
  // 其他的和直接 _(obj) 的结果一样
  return instance;
};

我们看下 _.chain([1, 2, 3]) 的结果,将参数代入函数中,其实就是对参数进行无 new 构造,然后返回实例,只是实例多了个 _chain 属性,其他的和直接 _([1, 2, 3]) 一模一样。再来看 _([1, 2, 3]).chain()_([1, 2, 3]) 返回构造实例,该实例有 chain 方法,调用方法,为实例添加 _chain 属性,返回该实例对象。所以,这两者效果是一致的,结果都是转为了 OOP 的形式。

说了这么多,似乎还没讲到正题上,它是如何「链」下去的?我们以如下代码为例:

_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

当调用 map 方法的时候,实际上可能会有返回值。我们看下 _.mixin 源码:

// 执行 func 方法
// 支持链式操作
return result(this, func.apply(_, args));

result 是一个重要的内部帮助函数(Helper function ):

// Helper function to continue chaining intermediate results.
// 一个帮助方法(Helper function)
var result = function(instance, obj) {
  // 如果需要链式操作,则对 obj 运行 chain 方法,使得可以继续后续的链式操作
  // 如果不需要,直接返回 obj
  return instance._chain ? _(obj).chain() : obj;
};

如果需要链式操作(实例会有带有 _chain 属性),则对运算结果调用 chain 函数,使之可以继续链式调用。

小结

Underscore 整体架构,或者说是基础实现大概就是这个样子,代码部分就讲到这了,接下去系列解读最后一篇,讲讲这段时间(几乎也是历时半年了)的一些心得体会吧,没钱的就捧个人场吧!

相关文章:

  • BizTalk 2013R2 WCF-LOB Oracle Adapter安装配置/问题解决方法
  • 关于AWS的备份策略
  • Objective-C三方库: ZXEasyCoding
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • JSON字符串转对象
  • iOS开发笔记--导航栏解决方案(透明、隐藏)
  • 投票系统之防止重复投票
  • python redis使用心得
  • Tools - Markdown
  • 深入理解ajax系列第一篇——XHR对象
  • 如果图片链接找不到地址出错自动显示默认图片(头像)
  • 现代化敏捷简介
  • HTML link标签media参数
  • mongodb中数据类型的坑
  • Velocity初探小结--Velocity在spring中的配置和使用
  • #Java异常处理
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 【mysql】环境安装、服务启动、密码设置
  • 【译】理解JavaScript:new 关键字
  • Akka系列(七):Actor持久化之Akka persistence
  • HashMap ConcurrentHashMap
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • JDK 6和JDK 7中的substring()方法
  • js
  • Just for fun——迅速写完快速排序
  • k8s如何管理Pod
  • Otto开发初探——微服务依赖管理新利器
  • Spring声明式事务管理之一:五大属性分析
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 成为一名优秀的Developer的书单
  • 如何利用MongoDB打造TOP榜小程序
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 我看到的前端
  • 小程序 setData 学问多
  • 小试R空间处理新库sf
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • ​油烟净化器电源安全,保障健康餐饮生活
  • (2.2w字)前端单元测试之Jest详解篇
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (七)Knockout 创建自定义绑定
  • (五)IO流之ByteArrayInput/OutputStream
  • (转载)Linux 多线程条件变量同步
  • .NET Micro Framework初体验(二)
  • .NET/C# 使窗口永不获得焦点
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • /var/log/cvslog 太大
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [Android]How to use FFmpeg to decode Android f...
  • [C]整形提升(转载)
  • [EFI]MSI GF63 Thin 9SCXR电脑 Hackintosh 黑苹果efi引导文件
  • [GN] DP学习笔记板子
  • [GN] Vue3.2 快速上手 ---- 核心语法2
  • [JavaEE系列] Thread类的基本用法