underscore做为开发中比较经常使用的一个javascript工具库,提供了一套丰富的函数式编程功能,该库并无拓展原有的javascript原生对象,而是在自定义的
_
对象上,提供了100多个方法函数。在这个系列中,将从uderscore源码角度, 打造一个本身的underscore框架javascript
现代js 库的框架设计,通常都是以自执行函数的形式,自执行函数通常有两种形式前端
(function(){
// 形式一
}())
复制代码
(function(){
// 形式二
})()
复制代码
咱们知道,函数声明的形式会挂载到window对象做为方法存在,而函数表达式的形式则会挂载在window对象做为属性存在,这都会形成变量污染,而自执行函数的好处在于能够防止变量的污染,函数执行完后便马上销毁掉。java
underscore有两种风格形式可使用,一种是面向对象类型,另外一种是函数类型。node
// 例子
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
复制代码
所以,在定义underscore类的时候须要考虑对象和函数两种场景。当以函数的形式调用时须要把 _ 看成一个构造函数并返回他的实例化。代码以下算法
(function(root){
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
}
root._ = _
}(this))
复制代码
如今前端开发重视模块化,以node服务端而论, 咱们有commonjs规范,以客户端而论,咱们有AMD 和 CMD规范,对应的模块加载器为 requirejs 和 seajs。目前通行的javascript模块规范主要集中在commonjs 和 AMD,所以为了让定义的underscore库可以适用于各类规范。在框架的定义时需检测使用环境并兼容各类规范。编程
define('**', [], function(){ return '***' })
暴露模块(function (root) {
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
}
// commonjs 规范 检测 module.exports 是否存在
if ((typeof module !== 'undefined' && module.exports)) {
module.exports = {
_: _
}
} else {
root._ = _;// window 对象暴露方法
}
// amd 规范,检测 define.amd 是否存在
if (typeof define == 'function' && define.amd) {
define('underscore', [], function () {
return _;
});
}
}(this))
复制代码
// commonjs
const _ = require('./underscore.js')
console.log(_)
复制代码
// AMD
require(['underscore'], function (underscore) {
console.log(underscore)
})
复制代码
underscore的调用,既能够经过_.unique()
,也能够经过 _().unique()
,两种方法效果相同却须要在框架设计时定义两套方法,一套是定义 _ 对象的静态方法,另外一套是扩展 _对象原型链上的方法。api
_.uniqe = function() {}
_.prototype.unique = function() {}
复制代码
为了不冗余代码,能够将定义好的静态方法复制一份成为原型链上的方法数组
(function(root){
···
_.mixins = function() {
// 复制静态方法到原型上
}
_.mixins() // 执行方法
}(this))
复制代码
mixins 方法的实现,须要遍历 underscore 对象上全部的静态方法,所以须要先完成对 遍历方法 _.each 的实现bash
_.each(list, iteratee, [context]) Alias: forEach 遍历list中的全部元素,按顺序用每一个元素当作参数调用 iteratee 函数。若是传递了context参数,则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数:(element, index, list)。若是list是个JavaScript对象,iteratee的参数是 (value, key, list))。返回list以方便链式调用。app
each 的第一个参数按照文档能够支持 数组,类数组,对象三种类型,数组类数组和对象在遍历时的处理方式不一样。前者回调函数处理的是 值和下标,后者处理的是 值和属性。
// 判断数组,类数组方法
(function(root) {
···
_.each = function (list, callback, context) {
// context 存在会改变callback 中this 的指向
var i = 0;
var key;
if (isArrayLikeLike(list)) { // 数组,类数组
for (var i = 0; i < list.length; i++) {
context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)
}
} else { // 对象
for (key in list) {
context ? callback.call(context, list[key], key) : callback(list[key], key)
}
}
}
var isArrayLike = function (collection) {
// 返回参数 collection 的 length 属性值
var length = collection.length;
// length是数值,非负,且小于等于MAX_ARRAY_INDEX
// MAX_ARRAY_INDEX = Math.pow(2, 53) - 1
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
}(this))
复制代码
mixin方法的设计,目的是为了在underscore原型对象上扩展更多的方法,它既能够用来扩展用户自定义的方法,好比
_.mixin({
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_("fabio").capitalize();
=> "Fabio"
复制代码
固然也能够用来内部拷贝静态方法到原型链的方法上。
(function(root){
···
var push = Array.prototype.push
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
this.wrap = obj // 存储实例对象传过来的参数
}
_.mixins = function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
return value.apply(this, args)
}
})
}
_.mixins(_)
}(this))
复制代码
其中关注点在arguments 的处理上,静态方法须要传递目标源做为方法的参数 即_.unique(目标源, 回调函数)
,而实例方法的目标源存储在构造对象的属性中 ,即_(目标源).unique(回调函数)
,所以定义实例方法时须要合并属性和回调函数。即Array.prorotype.push.apply([this.wrap], arguments)
,以后将他做为参数传递给静态方法并返回处理结果。
将类数组转成数组的方法
Array.prototype.slice.call(类数组)
var a = []; Array.prototype.push.apply(a, 类数组); console.log(a);
var a = []; Array.prototype.concat.apply(a, 类数组); console.log(a);
Array.from(类数组)
var args = [...类数组]
返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象自己, 直道 value 方法调用为止。
underscore中方法的调用返回的是处理后的值,所以没法支持方法的链式调用。若是须要链式调用,须要使用chain()方法,chain的使用会使每次调用方法后返回underscore的实例对象,直到 调用value方法才中止返回。
(function(root){
···
// chain方法会返回 _ 实例,而且标注该实例是否容许链式调用的
_.chain = function(obj) {
var instance = _(obj);
instance.chain = true;
return instance
}
// 增长是否支持链式调用的判断,若是支持,则返回该实例,不支持则直接返回结果,
var chainResult = function (instance, obj) {
return instance.chain ? _(obj).chain() : obj
}
_.mixins = function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
return chainResult(this, value.apply(this, args)) // 修改实例方法的返回值,返回值经过chainResult 包装,根据chainResult的判断结果改变返回值
}
})
}
}(this))
复制代码
由于链式调用会使underscore的方法返回他的实例对象,因此当须要结束这一调用行为时,须要使用value()。 value()方法会返回调用的结果。
(function(root){
···
_.value = function(instance) {
return instance.wrap
}
}(this))
复制代码