前言

最近打算好好看看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;varpush             = ArrayProto.push,slice            = ArrayProto.slice,toString         = ObjProto.toString,hasOwnProperty   = ObjProto.hasOwnProperty;varnativeIsArray      = 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 = objreturn this},name: function(name) {console.log(name)}
}
_.prototype.init.prototype = _.prototype;
var a = _([1, 2, 3])
a.name("ygy"); // ygy
复制代码

underscore中所有方法都是在_方法上面直接挂载的,并且用mixin方法将这些方法再一次挂载到了原型上面。不过,由于篇幅有限,mixin方法的实现会在后文中给大家讲解。 如果本文有错误和不足之处,希望大家指出。

转载于:https://juejin.im/post/5cdcf7bbf265da03587c2399

underscore源码剖析之整体架构相关推荐

  1. jQuery 2.0.3 源码分析core - 整体架构

    拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery ...

  2. jQuery源码分析系列 : 整体架构

    query这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! ...

  3. Nmap源码分析(整体架构)

    整体架构 功能目录 docs :相关文档 libdnet-stripped :开源网络接口库 liblinear:开源大型线性分类库 liblua:开源Lua脚本语言库 libnetutil:基本的网 ...

  4. Mongodb 源码分析:整体架构

    最近一直在学习Mongodb的源码,很希望能够搞清楚Mongodb内部的具体实现.从Mongodb中文社区和其他人的博客里面学到了很多, 因此, 开了这个博客希望把自己学到的一些分享给大家. 任何源码 ...

  5. OSChinaiOS客户端源码剖析001(架构篇)

    1.架构概要 程序启动就先来到这里,创建左侧控制器SideMenuViewController和OSCTabBarController 2.OSCTabBarController 综合标签和动弹标签的 ...

  6. python解释器源码 pdf_《python解释器源码剖析》第0章--python的架构与编译python

    本系列是以陈儒先生的<python源码剖析>为学习素材,所总结的笔记.不同的是陈儒先生的<python源码剖析>所剖析的是python2.5,本系列对应的是python3.7. ...

  7. 儒猿秒杀季!微服务限流熔断技术源码剖析与架构设计

    疯狂秒杀季:49元秒杀 原价 299元 的 <微服务限流熔断技术源码剖析与架构设计课> 今天 上午11点,仅 52 套,先到先得! === 课程背景 === 成为一名架构师几乎是每个程序员 ...

  8. 一箭双雕 刷完阿里P8架构师spring学习笔记+源码剖析,涨薪8K

    关于Spring的叙述: 我之前死磕spring的时候,刷各种资料看的我是一头雾水的,后面从阿里的P8架构师那里拿到这两份资料,从源码到案例详细的讲述了spring的各个细节,是我学Spring的启蒙 ...

  9. Spring源码剖析——Bean的配置与启动

    IOC介绍   相信大多数人在学习Spring时 IOC 和 Bean 算得上是最常听到的两个名词,IOC在学习Spring当中出现频率如此之高必然有其原因.如果我们做一个比喻的话,把Bean说成Sp ...

最新文章

  1. javascript 之 push
  2. 贪吃蛇python零基础教程_自学python-tkinter项目-贪吃蛇的程序(0基础入门学习)...
  3. 2018牛客网暑期ACM多校训练营第二场 D - money(贪心)
  4. 转:两种转换mysql数据编码的方法-latin1转utf8
  5. iPhone应用程序编程指南(窗口和视图)
  6. ORCAD CAPTURE 元件库详解
  7. bodhi linux 安装 ubuntu软件,Bodhi Linux 5.1.0 发布,基于Ubuntu的轻量级发行版
  8. MVC4中EasyUI Tree异步加载JSON数据生成树
  9. VC2005中将Picture控件显示图片保存为BMP,JPG等格式
  10. 揭秘淘宝286亿海量图片存储与处理架构,海量小文件存储的解决方案
  11. 【项目实战】Python基于RFM模型和K-Means聚类算法进行航空公司客户价值分析
  12. 数据错误循环冗余检查是什么意思_德尔西曼.交换机是一种什么设备?通过什么方式进行交换?...
  13. 我的世界服务器显示你没有权限,我的世界怎么没有权限有指令
  14. VC6.0编译出错Compiling...,Error spawning cl.exe的解决方法
  15. 【我的Android进阶之旅】Android 混淆文件资源分类整理之二:将混淆文件拆分成更小粒度的混淆文件
  16. 刷题记录:牛客NC24083Greedy Gift Takers
  17. 计算机二级小蒋是一位中学老师,解析:小蒋是一位中学教师,在教务处负责初一年级学生的成绩管理。由于学校地处偏远地区,缺乏必要的教学 - 计算机二级 - 看书网站...
  18. 获取 postman 的 token
  19. 计算机跨考什么专业不考数学,不考数学且适合跨考的考研专业,你知道哪些?...
  20. Nature Genetic | 番茄超级泛基因组的多样性和结构变异

热门文章

  1. c语言添加收支情况,C语言编写一个计算个人所得税的程序,要求输入收入金额,能够输...
  2. vuex状态持久化_Vuex持久化存储之vuex-persist
  3. orgmode导出html,含有python代码块的ORG-MODE导出为HTML时出错
  4. 决策树对鸢尾花数据两特征组合分类python代码的结果_机器学习笔记-决策树
  5. 统计5个字符串回文个数c语言,第一章 字符串 – 1.5 最长回文子串 - 编程之法:面试和算法心得...
  6. nod32 linux升级方法,打造全自动的NOD32升级服务器
  7. linux led测试程序,Linux中加入led驱动及测试程序详解
  8. jmeter jdbc mysql_jmeter获取JDBC响应做接口关联(三)
  9. 数学之美:欣赏超越数e
  10. 【BZOJ2744】【codevs2366】朋友圈,二分图最大匹配