Underscore整体架构浅析
前言
终于,楼主的「Underscore 源码解读系列」underscore-analysis 即将进入尾声,关注下 timeline 会发现楼主最近加快了解读速度。十一月,多事之秋,最近好多事情搞的楼主心力憔悴,身心俱疲,也想尽快把这个系列完结掉,也好了却一件心事。
本文预计是解读系列的倒数第二篇,最后一篇那么显然就是大总结了。楼主的 Underscore 系列解读完整版地址https://github.com/hanzichi/u...
常规调用
之前写的文章,关注点大多在具体的方法,具体的知识细节,也有读者留言建议楼主讲讲整体架构,这是必须会讲的,只是楼主把它安排在了最后,也就是本文,因为楼主觉得不掌握整体架构对于具体方法的理解也是没有大的问题的。
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 上应该有这个方法,那么,方法是如何挂载上去的呢?
方法挂载
现在我们已经明确以下两点:
_ 是一个函数(支持无 new 调用的构造函数) _ 的属性有很多方法,比如 _.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) {
_.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 函数,使之可以继续链式调用。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...
