underscore源码理会之团体架构

underscore源码理会之团体架构

近来盘算好好看看underscore源码,一个是因为自身确切程度不够,另一个是underscore源码比较简朴,比较易读。
本系列盘算对underscore1.8.3中症结函数源码举行理会,愿望做到最细致的源码理会。
今天是underscore源码理会系列第一篇,重要对underscore团体架构和基本函数举行理会。

基本模块

起首,我们先来简朴的看一下团体的代码:

// 这里是一个马上挪用函数,运用call绑定了外层的this(全局对象)
(function() {

  var root = this;
  
  // 保存当前环境中已存在的_变量(在noConflict中用到)
  var previousUnderscore = root._;
  
  // 用变量保存原生要领的援用,以防备这些要领被重写,也便于紧缩
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  var
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind,
    nativeCreate       = Object.create;

  var Ctor = function(){};
  // 内部完成省略
  var _ = function(obj) {};
  
    // 这里是种种要领的完成(省略)
    
  // 导出underscore要领,假如有exports则用exports导出,假如    没有,则将其设为全局变量
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }
  
  // 版本号
  _.VERSION = '1.8.3';
  
  // 用amd的情势导出
  if (typeof define === 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}.call(this))

全局对象

这段代码团体比较简朴,不过我看厥后的underscore版本有一些小修改,主如果将var root = this;替换为下面这句:

var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this;

这里增添了对self和global的推断,self属性能够返回对窗口自身的援用,等价于window,这里主如果为了兼容web worker,因为web worker中是没有window的,global则是为了兼容node,而且在严厉形式下,马上实行函数内部的this是undefined。

void(0) ? undefined

扫一眼源码,我们会发如今源码中并没有见到undefined的涌现,反而是用void(0)或许void 0来替代的,那末这个void究竟是什么?为何不能直接用undefined呢?
关于void的诠释,我们能够看这里:MDN
void 运算符平常只用于猎取 undefined的原始值,平常运用void(0),因为undefined不是保存字,在低版本浏览器或许部分作用域中是能够被当作变量赋值的,如许就会致使我们拿不到正确的undefined值,在许多紧缩工具中都是将undefined用void 0来替代掉了。
实在这里不仅是void 0能够拿到undefined,另有其他许多要领也能够拿到,比方0[“ygy”]、Object.__undefined__、Object.__ygy__,这些道理都是接见一个不存在的属性,所以末了一定会返回undefined

noConflict

或许有时刻我们会遇到如许一种状况,_已被当作一个变量声清楚明了,我们引入underscore后会掩盖这个变量,然则又不想这个变量被掩盖,还好underscore供应了noConflict这个要领。

_.noConflict = function() {
    root._ = previousUnderscore;
    return this;
};
var underscore = _.noConflict();

不言而喻,这里一般保存本来的_变量,并返回了underscore这个要领(this就是_要领)

_

接下来讲到了本文的重点,关于_要领的理会,在看源码之前,我们先熟习一下_的用法。
这里总结的是我一样平常的用法,假如有脱漏,愿望人人补充。
一种是直接挪用_上的要领,比方_.map([1, 2, 3]),另一种是经由历程实例接见原型上的要领,比方_([1, 2, 3]).map(),这里和jQuery的用法很像,$.extend挪用jQuery对象上的要领,而$(“body”).click()则是挪用jQuery原型上的要领。

既然_能够运用原型上面的要领,那末申明实行_函数的时刻一定会返回一个实例。

这里来看源码:

// instanceof 运算符用来测试一个对象在其原型链中是不是存在一个组织函数的 prototype 属性。
// 我这里有个不够正确但轻易明白的说法,就是搜检一个对象是不是为另一个组织函数的实例,为了更轻易明白,下面将悉数以XXX是XXX的实例的体式格局来讲。
var _ = function(obj) {
    // 假如obj是_的实例(这类状况我真的没遇到过)
    if (obj instanceof _) return obj;
    // 假如this不是_组织函数的实例,那就以obj为参数 new一个实例(相等于修改了_函数)
    if (!(this instanceof _)) return new _(obj);
    // 对应_([1,2,3])这类状况
    this._wrapped = obj;
  };

我先从源码上来诠释,这里能够看出来_是一个组织函数,我们都晓得,我既能够在组织函数上面增添要领,还能够在原型上面增添要领,前者只能经由历程组织函数自身接见到,后者因为原型链的存在,能够在组织函数的实例上面接见到。

var Person = function() {
    this.name = "ygy";
    this.age = 22;
}
Person.say = function() {
    console.log("hello")
}
Person.prototype.say = function() {
    console.log("world")
}
var ygy = new Person();
Person.say(); // hello
ygy.say(); // world

所以我们日常平凡用的_.map就是Person.say()这类用法,而_([1, 2, 3]).map则是ygy.say()这类用法。

在继续讲这个之前,我们再来温习一下原型的学问,当我们new一个实例的时刻随处发生了什么?

起首,这里会先建立一个空对象,这个空对象继续了组织函数的原型(或许明白为空对象上增添一个指向组织函数原型的指针__proto__),之后会依据实例传入的参数实行一遍组织函数,将组织函数内部的this绑定到这个新对象中,末了返回这个对象,历程和以下相似:

var ygy = {};
ygy.__proto__ = Person.prototype 
// 或许var ygy = Object.create(Person.prototype)
Person.call(ygy);

如许就很好明白了,如果想挪用原型上面的要领,必须先new一个实例出来。我们再来理会_要领的源码:
_吸收一个对象作为参数,假如这个对象是_的一个实例,那末直接返回这个对象。(这类状况我却是没见过)

假如this不是_的实例,那末就会返回一个新的实例new _(obj),这个该怎样明白?
我们须要连系例子来看这句话,在_([1, 2, 3])中,obj一定是指[1, 2, 3]这个数组,那末this是指什么呢?我以为this是指window,不信你直接实行一下上面例子中的Person()?你会发如今全局作用域中是能够拿到name和age两个属性的。

那末既然this指向window,那末this一定不是_的实例,所以this instanceof _必然会返回false,如许的话就会return一个new _([1, 2, 3]),所以_([1, 2, 3])就是new _([1, 2, 3]),从我们前面临new的诠释来看,这个历程表现以下:

var obj = {}
obj.__proto__ = _.prototype
// 此时_函数中this的是new _(obj),this instanceof _是true,所以就不会从新return一个new _(obj),如许避免了轮回挪用
_.call(obj) // 实际上做了这一步: obj._wrapped = [1, 2, 3]

如许我们就明白了为何_([1, 2, 3]).map中map是原型上的要领,因为_([1, 2, 3])是一个实例。

我这里再供应一个自身完成的_思绪,和jQuery的完成相似,这里就不作诠释了:

var _ = function(obj) {
    return new _.prototype.init(obj)
}
_.prototype = {
    init: function(obj) {
        this.__wrapped = obj
        return this
    },
    name: function(name) {
        console.log(name)
    }
}
_.prototype.init.prototype = _.prototype;
var a = _([1, 2, 3])
a.name("ygy"); // ygy

underscore中所有要领都是在_要领上面直接挂载的,而且用mixin要领将这些要领再一次挂载到了原型上面。不过,因为篇幅有限,mixin要领的完成会在后文中给人人解说。
假如本文有毛病和不足之处,愿望人人指出。

    原文作者:尹光耀
    原文地址: https://segmentfault.com/a/1190000013789060
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞