所有分类
  • 所有分类
  • 实时新闻

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

前言

你好,我是若川。这是学习源码整体架构系列第二篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。

学习源码整体架构系列文章如下:

1.若川:学习 jQuery 源码整体架构,打造属于自己的 js 类库2.若川:学习underscore源码整体架构,打造属于自己的函数式编程类库3.若川:学习 lodash 源码整体架构,打造属于自己的函数式编程类库4.若川:学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK5.若川:学习 vuex 源码整体架构,打造属于自己的状态管理库6.若川:学习 axios 源码整体架构,打造属于自己的请求库7.若川:学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理

感兴趣的读者可以点击阅读。 其他源码计划中的有:expressvue-routerreduxreact-redux等源码,不知何时能写完(哭泣),欢迎持续关注我(若川)。

源码类文章,一般阅读量不高。已经有能力看懂的,自己就看了。不想看,不敢看的就不会去看源码。所以我的文章,尽量写得让想看源码又不知道怎么看的读者能看懂。

虽然看过挺多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;

如果对这块不熟悉的读者,可以看下以下这张图(之前写ef=”https://juejin.im/post/5c433e216fb9a049c15f841b“>面试官问:JS的继承画的图)。

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

继续分析官方的_.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) delete obj[0];提一下上面源码中的这一句,看到这句是百思不得其解。于是赶紧在github中搜索这句加上""双引号。表示全部搜索。

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

I dont 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()new String()效果是一样的。 可以猜测内部实现和_函数对象类似。

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()效果一样,都能实现链式调用。

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

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

_.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上有多少方法和属性。

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

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原型关系图,毕竟一图胜千言。

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

整体架构概览

匿名函数自执行

(function(){

}());

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

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

root 处理

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

支持浏览器环境nodeWeb Workernode 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;
};

使用:



总结

全文根据官网提供的链式调用的例子,_.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的源码整体架构。

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

推荐阅读

underscorejs.org 官网undersercore-analysisunderscore 系列之如何写自己的 underscore

笔者往期文章

学习 jQuery 源码整体架构,打造属于自己的 js 类库面试官问:JS的继承面试官问:JS的this指向面试官问:能否模拟实现JS的call和apply方法面试官问:能否模拟实现JS的bind方法面试官问:能否模拟实现JS的new操作符前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并

关于

作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客掘金专栏,欢迎关注~segmentfault前端视野专栏,开通了前端视野专栏,欢迎关注~知乎前端视野专栏,开通了前端视野专栏,欢迎关注~github blog,相关源码和资源都放在这里,求个star^_^~

微信公众号 若川视野

可能比较有趣的微信公众号,长按扫码关注。也可以加微信lxchuan12,注明来源,拉您进【前端视野交流群】。

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

原文链接:https://www.w1ym.com/83137/,转载请注明出处~~~
0

评论0

请先

站点公告

【温馨提示】 本站不建议您对本站支付任何费用或开通任何会员本站99%资源为免费资源只提供共享不提供技术支持,本站资源主要以学习开发为主,本站是为个人资源记录学习研究等情况而建立,如特殊原因下载,需在24小时删除相关资源。本站资源均来自互联网收集或网友分享,若有侵权,请联系站长删除,谢谢。立即查看
显示验证码
没有账号?注册  忘记密码?