深入解析Underscore.js源码架构

Underscore.js是很有名的一个工具库,我也经常用他来处理对象,数组等,本文会深入解析Underscore源码架构,跟大家一起学习下他源码的亮点,然后模仿他写一个简单的架子来加深理解。他的源码通读下来,我觉得他的亮点主要有如下几点:

  • 不需要new的构造函数
  • 同时支持静态方法调用和实例方法调用
  • 支持链式调用

本文的例子已经上传到GitHub,同一个repo下还有我全部的博文和例子,求个star:

https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Architecture/Underscore

外层是一个自执行函数

Underscore外层就是一个自执行函数,在自执行函数里面将_挂载到了window上。这是很多第三方库惯用的套路。如果你还不知道怎么入手看源码,不知道入口在哪里,或者看不懂他的外层结构,请看从架构入手轻松读懂框架源码:以jQuery,Zepto,Vue和lodash-es为例,这篇文章详细讲解了怎么入手看源码。本文主要讲解Underscore源码架构里面的亮点,怎么入手就不再赘述了。

不用new的构造函数

我们在使用第三方库的时候,经常需要先拿一个他们的实例,有些库需要用new来显式的调用,比如原生的Promise,有些库不需要new也可以拿到实例对象,比如jQuery。不用new就返回一个实例原生JS肯定是不支持的,有这些特性的库都是自己封装了一层的。不同的库在封装的时候也有不同的思路,下面我们来讲讲其中两种方案。

jQuery的方案

之前我在另一篇文章从架构入手轻松读懂框架源码:以jQuery,Zepto,Vue和lodash-es为例中详细讲解了jQuery是怎么实现不用new就返回一个实例的。另外还模仿jQuery的这种方案实现了我自己的一个工具库:学以致用:手把手教你撸一个工具库并打包发布,顺便解决JS小数计算不准问题。这里贴一段我工具库文章的代码简单回顾下这种方案:

// 首先创建一个fc的函数,我们最终要返回的其实就是一个fc的实例
// 但是我们又不想让用户new,那么麻烦
// 所以我们要在构造函数里面给他new好这个实例,直接返回
function FractionCalculator(numStr, denominator) {
   
  // 我们new的其实是fc.fn.init
  return new FractionCalculator.fn.init(numStr, denominator);
}

// fc.fn其实就是fc的原型,算是个简写,所有实例都会拥有这上面的方法
FractionCalculator.fn = FractionCalculator.prototype = {
   };

// 这个其实才是真正的构造函数,这个构造函数也很简单,就是将传入的参数转化为分数
// 然后将转化的分数挂载到this上,这里的this其实就是返回的实例
FractionCalculator.fn.init = function(numStr, denominator) {
   
  this.fraction = FractionCalculator.getFraction(numStr, denominator);
};

// 前面new的是init,其实返回的是init的实例
// 为了让返回的实例能够访问到fc的方法,将init的原型指向fc的原型
FractionCalculator.fn.init.prototype = FractionCalculator.fn;

// 调用的时候就不用new了,直接调用就行
FractionCalculator();

Underscore的方案

jQuery的方案是在构造函数里面new了另外一个对象,然后将这个对象的原型指向jQuery的原型,以便返回的实例能够访问jQuery的实例方法。目的是能够达到的,但是方案显得比较冗长,Underscore的方案就简洁多了:

function _(){
   
  if(!(this instanceof _)) {
   
    return new _();
  }
}

// 调用的时候直接_()就可以拿到实例对象
const instance = _();
console.log(instance);

上面代码的输出是:

image-20200318155826651

可以看到constructor指向的是_(),说明这真的是一个_的实例,我们来分析下代码执行流程:

  1. 调用_(),里面的this指向外层的作用域,我们这里是window,因为window不是_的实例,会走到if里面去。关于this指向,如果你还不是很明白,请看这篇文章。
  2. if里面会调用new _(),这会拿到一个实例对象,并将这个对象return出去。new _()也会调到_()方法,但是因为使用new调用,里面的this指向的就是new出来的实例,所以if进不去,执行结束。

Underscore巧妙应用了this的指向,通过检测this的指向来判断你是new调用的还是普通调用的,如果是普通调用就帮你new一下再返回。

同时支持静态方法和实例方法

用过Underscore的朋友应该有注意到,对于同一个方法来说,Underscore既支持作为静态方法调用,也支持作为实例方法调用,下面是官方的例子:

_.map([1, 2, 3], function(n){
    return n * 2; });   // map作为静态方法调用
_(

你可能感兴趣的:(进击的大前端,javascript)