学习underscore源码整体架构,打造属于自己的函数式编程类库

前言

上一篇文章写了 jQuery整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库

虽然看过挺多 underscore.js分析类的文章,但总感觉少点什么。这也许就是纸上得来终觉浅,绝知此事要躬行吧。于是决定自己写一篇学习 underscore.js整体架构的文章。

本文章学习的版本是 v1.9.1unpkg.com源码地址:https://unpkg.com/underscore@1.9.1/underscore.js

虽然很多人都没用过 underscore.js,但看下官方文档都应该知道如何使用。

从一个官方文档 _.chain简单例子看起:

_.chain([1, 2, 3]).reverse().value();
// => [3, 2, 1]

看例子中可以看出,这是支持链式调用。

读者也可以顺着文章思路,自行打开下载源码进行调试,这样印象更加深刻。

链式调用

_.chain 函数源码:

_.chain = function(obj) {var instance = _(obj);instance._chain = true;return instance;
};

这个函数比较简单,就是传递 obj调用 _()。但返回值变量竟然是 instance实例对象。添加属性 _chain赋值为 true,并返回 intance对象。但再看例子,实例对象竟然可以调用 reverse方法,再调用 value方法。猜测支持 OOP(面向对象)调用。

带着问题,笔者看了下定义 _ 函数对象的代码。

_ 函数对象 支持 OOP

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

如果参数 obj已经是 _的实例了,则返回 obj。如果 this不是 _的实例,则手动 new_(obj); 再次 new调用时,把 obj对象赋值给 _wrapped这个属性。也就是说最后得到的实例对象是这样的结构 {_wrapped:'参数obj',}它的原型 _(obj).__proto___.prototype;

如果对这块不熟悉的读者,可以看下以下这张图(之前写面试官问:JS的继承画的图)。

继续分析官方的 _.chain例子。这个例子拆开,写成三步。

var part1 = _.chain([1, 2, 3]);
var part2 = part1.reverse();
var part3 = part2.value();
// 没有后续part1.reverse()操作的情况下
console.log(part1); // {__wrapped: [1, 2, 3], _chain: true}
console.log(part2); // {__wrapped: [3, 2, 1], _chain: true}
console.log(part3); // [3, 2, 1]

思考问题:reverse本是 Array.prototype上的方法呀。为啥支持链式调用呢。搜索 reverse,可以看到如下这段代码:

并将例子代入这段代码可得(怎么有种高中做数学题的既视感^_^):

_.chain([1,2,3]).reverse().value()s
var ArrayProto = Array.prototype;
// 遍历 数组 Array.prototype 的这些方法,赋值到 _.prototype 上
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {// 这里的`method`是 reverse 函数var method = ArrayProto[name];_.prototype[name] = function() {// 这里的obj 就是数组 [1, 2, 3]var obj = this._wrapped;// arguments  是参数集合,指定reverse 的this指向为obj,参数为arguments, 并执行这个函数函数。执行后 obj 则是 [3, 2, 1]method.apply(obj, arguments);if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];// 重点在于这里 chainResult 函数。return chainResult(this, obj);};
});
// Helper function to continue chaining intermediate results.
var chainResult = function(instance, obj) {// 如果实例中有_chain 为 true 这个属性,则返回实例 支持链式调用的实例对象  { _chain: true, this._wrapped: [3, 2, 1] },否则直接返回这个对象[3, 2, 1]。return instance._chain ? _(obj).chain() : obj;
};

if((name==='shift'||name==='splice')&&obj.length===0)deleteobj[0];提一下上面源码中的这一句,看到这句是百思不得其解。于是赶紧在 github中搜索这句加上 ""双引号。表示全部搜索。

搜索到两个在官方库中的 ISSUE,大概意思就是兼容IE低版本的写法。有兴趣的可以点击去看看。

I don't understand the meaning of this sentence.

why delete obj[0]

基于流的编程

至此就算是分析完了链式调用 _.chain()_ 函数对象。这种把数据存储在实例对象 {_wrapped:'',_chain:true} 中, _chain判断是否支持链式调用,来传递给下一个函数处理。这种做法叫做 基于流的编程

最后数据处理完,要返回这个数据怎么办呢。underscore提供了一个 value的方法。

_.prototype.value = function(){return this._wrapped;
}

顺便提供了几个别名。toJSONvalueOf。_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

还提供了 toString的方法。

_.prototype.toString = function() {return String(this._wrapped);
};

这里的 String()newString() 效果是一样的。可以猜测内部实现和 _函数对象类似。

var String = function(){if(!(this instanceOf String)) return new String(obj);
}
var chainResult = function(instance, obj) {return instance._chain ? _(obj).chain() : obj;
};

细心的读者会发现 chainResult函数中的 _(obj).chain(),是怎么实现实现链式调用的呢。

_(obj)是返回的实例对象 {_wrapped:obj}呀。怎么会有 chain()方法,肯定有地方挂载了这个方法到 _.prototype上或者其他操作,这就是 _.mixin()

_.mixin 挂载所有的静态方法到 _.prototype, 也可以挂载自定义的方法

_.mixin 混入。但侵入性太强,经常容易出现覆盖之类的问题。记得之前 Reactmixin功能, Vue也有 mixin功能。但版本迭代更新后基本都是慢慢的都不推荐或者不支持 mixin

_.mixin = function(obj) {// 遍历对象上的所有方法_.each(_.functions(obj), function(name) {// 比如 chain, obj['chain'] 函数,自定义的,则赋值到_[name] 上,func 就是该函数。也就是说自定义的方法,不仅_函数对象上有,而且`_.prototype`上也有var func = _[name] = obj[name];_.prototype[name] = function() {// 处理的数据对象var args = [this._wrapped];// 处理的数据对象 和 arguments 结合push.apply(args, arguments);// 链式调用  chain.apply(_, args) 参数又被加上了 _chain属性,支持链式调用。// _.chain = function(obj) {//  var instance = _(obj);//  instance._chain = true;//  return instance;};return chainResult(this, func.apply(_, args));};});// 最终返回 _ 函数对象。return _;
};
_.mixin(_);

_mixin(_) 把静态方法挂载到了 _.prototype上,也就是 _.prototype.chain方法 也就是 _.chain方法。

所以 _.chain(obj)_(obj).chain()效果一样,都能实现链式调用。

关于上述的链式调用,笔者画了一张图,所谓一图胜千言。

_.mixin 挂载自定义方法

挂载自定义方法:举个例子:

_.mixin({log: function(){console.log('哎呀,我被调用了');}
})
_.log() // 哎呀,我被调用了
_().log() // 哎呀,我被调用了

_.functions(obj)

_.functions = _.methods = function(obj) {var names = [];for (var key in obj) {if (_.isFunction(obj[key])) names.push(key);}return names.sort();
};

_.functions_.methods 两个方法,遍历对象上的方法,放入一个数组,并且排序。返回排序后的数组。

underscore.js 究竟在 _和 _.prototype挂载了多少方法和属性

再来看下 underscore.js究竟挂载在 _函数对象上有多少静态方法和属性,和挂载 _.prototype上有多少方法和属性。

使用 forin循环一试遍知。看如下代码:

var staticMethods = [];
var staticProperty = [];
for(var name in _){if(typeof _[name] === 'function'){staticMethods.push(name);}else{staticProperty.push(name);}
}
console.log(staticProperty); // ["VERSION", "templateSettings"] 两个
console.log(staticMethods); // ["after", "all", "allKeys", "any", "assign", ...] 138个
var prototypeMethods = [];
var prototypeProperty = [];
for(var name in _.prototype){if(typeof _.prototype[name] === 'function'){prototypeMethods.push(name);}else{prototypeProperty.push(name);}
}
console.log(prototypeProperty); // []
console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 152个

根据这些,笔者又画了一张图 underscore.js 原型关系图,毕竟一图胜千言。

整体架构概览

匿名函数自执行

(function(){
}());

这样保证不污染外界环境,同时隔离外界环境,不是外界影响内部环境。

外界访问不到里面的变量和函数,里面可以访问到外界的变量,但里面定义了自己的变量,则不会访问外界的变量。匿名函数将代码包裹在里面,防止与其他代码冲突和污染全局环境。关于自执行函数不是很了解的读者可以参看这篇文章。[译] JavaScript:立即执行函数表达式(IIFE)

root 处理

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

支持 浏览器环境nodeWebWorkernode vm微信小程序

导出

if (typeof exports != 'undefined' && !exports.nodeType) {if (typeof module != 'undefined' && !module.nodeType && module.exports) {exports = module.exports = _;}exports._ = _;
} else {root._ = _;
}

关于 root处理导出的这两段代码的解释,推荐看这篇文章冴羽:underscore 系列之如何写自己的 underscore,讲得真的太好了。笔者在此就不赘述了。总之, underscore.js作者对这些处理也不是一蹴而就的,也是慢慢积累,和其他人提 ISSUE之后不断改进的。

支持 amd 模块化规范

if (typeof define == 'function' && define.amd) {define('underscore', [], function() {return _;});
}

_.noConflict 防冲突函数

源码:

// 暂存在 root 上, 执行noConflict时再赋值回来
var previousUnderscore = root._;
_.noConflict = function() {root._ = previousUnderscore;return this;
};

使用:

<script>
var _ = '我就是我,不一样的烟火,其他可不要覆盖我呀';
</script>
<script src="https://unpkg.com/underscore@1.9.1/underscore.js">
</script>
<script>
var underscore = _.noConflict();
console.log(_); // '我就是我,不一样的烟火,其他可不要覆盖我呀'
underscore.isArray([]) // true
</script>

总结

全文根据官网提供的链式调用的例子, _.chain([1,2,3]).reverse().value();较为深入的调试和追踪代码,分析链式调用( _.chain()_(obj

).chain())、 OOP、基于流式编程、和 _.mixin(_)_.prototype挂载方法,最后整体架构分析。学习 underscore.js整体架构,利于打造属于自己的函数式编程类库。

文章分析的源码整体结构。

(function() {var root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};var previousUnderscore = root._;var _ = function(obj) {if (obj instanceof _) return obj;if (!(this instanceof _)) return new _(obj);this._wrapped = obj;};if (typeof exports != 'undefined' && !exports.nodeType) {if (typeof module != 'undefined' && !module.nodeType && module.exports) {exports = module.exports = _;}exports._ = _;} else {root._ = _;}_.VERSION = '1.9.1';_.chain = function(obj) {var instance = _(obj);instance._chain = true;return instance;};var chainResult = function(instance, obj) {return instance._chain ? _(obj).chain() : obj;};_.mixin = function(obj) {_.each(_.functions(obj), function(name) {var func = _[name] = obj[name];_.prototype[name] = function() {var args = [this._wrapped];push.apply(args, arguments);return chainResult(this, func.apply(_, args));};});return _;};_.mixin(_);_.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 chainResult(this, obj);};});_.each(['concat', 'join', 'slice'], function(name) {var method = ArrayProto[name];_.prototype[name] = function() {return chainResult(this, method.apply(this._wrapped, arguments));};});_.prototype.value = function() {return this._wrapped;};_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;_.prototype.toString = function() {return String(this._wrapped);};if (typeof define == 'function' && define.amd) {define('underscore', [], function() {return _;});}
}());

下一篇文章可能是学习 lodash的源码整体架构。

读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。

关于

作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
个人博客 http://lxchuan12.cn
https://github.com/lxchuan12/blog,相关源码和资源都放在这里,求个 star^_^~

微信交流群,加我微信lxchuan12,注明来源,拉您进前端视野交流群

下图是公众号二维码:若川视野,一个可能比较有趣的前端开发类公众号

往期文章

工作一年后,我有些感悟(写于2017年)

高考七年后、工作三年后的感悟

学习 jQuery 源码整体架构,打造属于自己的 js 类库

由于公众号限制外链,点击阅读原文,或许阅读体验更佳

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276520.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拓扑目的 1.Pc9通过van3访问pc10 2.Pc9通过Vlan1\Vlan2访问pc11

1拓扑图2设置路由器R12的接口的IPint g0/0/0ip address 192.168.20.254 24undo shutdown int g0/0/01ip address 192.168.1.1 24undo shutdownint g2/0/00ip address 192.168.3.1 24undo shutdown 3设置路由器R10的接口的IPint g0/0/0ip address 192.168.2.1 24undo shutdownin…

学习 lodash 源码整体架构,打造属于自己的函数式编程类库

前言这是 学习源码整体架构系列第三篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码&#xff0c;不是实际仓库中的拆分的代码。上上篇…

推荐一个快速反射调用的类

使用传统的.net反射机制&#xff0c;调用类的方法时&#xff0c;在调用频率大的情况下&#xff0c;会感觉速度很慢。最近浏览卢彦的博客时&#xff0c;找到一个他改进后的反射调用类。试用以后感觉效率明显提高&#xff0c;特推荐给大家。作者重新实现了&#xff0c;反射调用方…

面试官问:JS的继承

原文作者若川&#xff0c;掘金链接&#xff1a;https://juejin.im/post/5c433e216fb9a049c15f841b写于2019年2月20日&#xff0c;现在发到公众号声明原创&#xff0c;之前被《前端大全》公众号等转载阅读量超1w&#xff0c;知乎掘金等累计阅读量超过1w。导读&#xff1a;文章主…

qt 快速按行读取文件_这是知识点之Linux下分割文件并保留文件头

点击上方"开发者的花花世界"&#xff0c;选择"设为星标"技术干货不定时送达&#xff01;这是一个知识点方便快捷的给结构化数据文件分割大小并保留文件的表头&#xff0c;几十个G的结构化文件不仅阅读编辑麻烦&#xff0c;而且使用受限&#xff0c;因此高效…

mono 调用windows webService

1. 实现linux mono Develop中调用windows 中的webService l linux 与 windows 在一个局域网的网段中 l windows 的IIs中发布webService 2. windows 中的设置 l webService 的代码 using System; using System.Collections.Generic; using System.Linq; using S…

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK

前言这是学习源码整体架构第四篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码&#xff0c;不是实际仓库中的拆分的代码。其余三篇分…

绑定dictionary 给定关键字不再字典中_VBA代码集锦-利用字典做两列数据的对比并对齐...

源数据&#xff1a;代码&#xff1a;Sub 对比()Dim arr, brr, crrDim i, j, n, lastrowA, lastrowB As Integer建立字典对象Set d CreateObject("scripting.dictionary")获取数据区域最后一行的行数lastrowA Sheets("对比对齐两列数据").Cells(Rows.Coun…

前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并

前端也可以爬虫&#xff0c;写于2018年08月29日&#xff0c;现在发布到微信公众号申明原创。掘金若川 本文章链接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 库puppetee…

信息安全管理与评估_计算机工程学院教师参加“信息安全管理与评估赛项”说明会...

看了就要关注我&#xff0c;喵呜~2019年3月15日下午&#xff0c;2019年陕西省高等职业院校技能大赛“信息安全管理与评估赛项说明会”在咸阳职业技术学院举行。出席本次会仪的有咸阳职业技术学院教务处长杨新宇、神州数码范永强经理、神州数码信息安全工程师高峰和各院校指导教…

haproxy概念和负载均衡

https://pan.baidu.com/s/1Sq2aJ35zrW2Xn7Th9j7oOA //软件百度网盘连接 在80.100虚拟机上 systemctl stop firewalld //关闭防火墙 setenforce 0 //关闭监控 yum install lrz* -y //安装上传软件 tar xf haproxy-1.5.15.tar.gz -C /opt/ //解压压缩包到/opt/ cd /op…

知乎问答:一年内的前端看不懂前端框架源码怎么办?

知乎问答&#xff1a;一年内的前端看不懂前端框架源码怎么办&#xff1f;以下是我的回答&#xff0c;阅读量 1000。现在转载到微信公众号中。链接&#xff1a;https://www.zhihu.com/question/350289336/answer/910970733其他回答的已经很好了。刚好最近在写学习源码整体架构系…

冷启动问题:如何构建你的机器学习组合?

作为即将告别大学的机器学习毕业狗的你&#xff0c;会不会有种迷茫的感觉&#xff1f;你知道 HR 最看重的是什么吗&#xff1f;在求职季到来之前&#xff0c;毕业狗要怎么做&#xff0c;才能受到 HR 的青睐、拿到心仪的 Offer 呢&#xff1f;负责帮助应届生找到机器学习工作的 …

JavaScript 对象所有API解析【2020版】

写于 2017年08月20日&#xff0c;虽然是2017年写的文章&#xff0c;但现在即将2020年依旧不过时&#xff0c;现在补充了2019年新增的ES10 Object.fromEntries()。发到公众号申明原创。若川顺便在此提前祝大家&#xff1a;2020年更上一层楼。近日发现有挺多人对对象基础API不熟悉…

PHP生成各种验证码和Ajax验证

转载链接&#xff1a;http://www.helloweba.com/view-blog-191.html 验证码在WEB应用中非常重要&#xff0c;通常用来防止用户恶意提交表单&#xff0c;如恶意注册和登录、论坛恶意灌水等。本文将通过实例讲解使用PHP生成各种常见的验证码包括数字验证码、数字字母验证码、中文…

若川的2019年度总结,波澜不惊

从2014年开始写年度总结至今已经六个年头了。正如孔子所说&#xff1a;逝者如斯夫&#xff0c;不舍昼夜。2019年的年度总结写得比较晚&#xff0c;都快农历新年了&#xff0c;此刻在家里继续写完这篇文章。往年基本是元旦之后几天就写完了。我的年度总结尽量写得非技术人员也能…

如何正确选择仓储物流供应商?

如何正确选择仓储物流供应商&#xff1f; 以前有做电商的朋友向我咨询过怎么去选择优质的仓储物流供应商&#xff1f;有哪些能做作为关键问题进行参考。作为一个优秀的合作伙伴是可以为客户提供超乎预期的服务的&#xff0c;上海维佳供应链服务专业提供物流外包解决仓储物流供应…

Realtime Ray Tracing RenderMan Point Cloud

这里演示的是演示的是光线与包围盒测试。在装备Winfast 8800GT 512M的台式机上可以进行每秒4.6亿次点到射线的距离计算计算&#xff0c;用于判断点是否真正的与射线相交。外部数据的填充与准备延迟依旧是GPGPU应用的一个巨大门槛。白色是命中的包围盒&#xff0c;绿色的就是射线…

如何制定有价值的目标

写于2017年07月09日23:29现在修改发布到公众号声明原创公司会制定一系列目标&#xff0c;个人也可以制定一些目标&#xff0c;有利于自我学习成长。那么看我这篇文章可以告诉你如何制定有价值的目标。会制定有价值的目标&#xff0c;绝对超越很多人。SMART原则王健林之前说定个…

清除dns缓存命令行_怎么防止移动dns劫持,防止移动dns劫持要先了解什么是dns劫持...

本人以网络技术出身&#xff0c;近两年接触CDN网络&#xff0c;处理了一些CDN方面的网络问题&#xff0c;大多数以运营商丢包&#xff0c;延迟抖动为主&#xff0c;也处理一些硬件故障&#xff0c;比如机械硬盘的读写io测试&#xff0c;内存条兼容性测试&#xff0c;服务器IPMI…