上篇文章报告了 underscore 的基础构造搭建,本文继承讲链式挪用与混入。
假如你还没看过第一篇文章,请点击 “underscore 诞生记(一)—— 基础构造搭建”
链式挪用
在 JQuery 中,我们常常使用到链式挪用,如:
$('.div')
.css('color', 'red')
.show();
那末在 underscore 中,是不是支撑链式挪用呢?答案是支撑的,只不过默许不开启链式挪用罢了。
想要完成链式挪用,一般我们会在支撑链式挪用的函数中返回对象自身:
let car = {
run(name) {
console.log(`${name}老司机开车啦喂!`);
return this;
},
stop() {
console.log('车停了');
},
};
car.run('奔驰').stop();
// 奔驰老司机开车啦喂!
// 车停了
那末在每一个 _
要领下都 return this
, 明显不大文雅缺少可控性!尝试着写个通用要领 chain()
开启链式挪用。
_.chain = function(obj) {
// 取得一个经underscore包裹后的实例
var instance = _(obj);
// 标识当前实例支撑链式挪用
instance._chain = true;
return instance;
};
// 小试牛刀
_.chain([1, 2, 3]);
/*
{
_chain: true,
_wrapped: [1, 2, 3]
}
*/
返回的为一个实例对象,背面的要领推断 _chain
属性是不是为 true
,为 true
的话再挪用一次 chain()
要领再返回本来实例即可。我们在之前用于给 prototype 复制要领的 each() 函数到场推断吧
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
var func = _[name];
_.prototype[name] = function() {
var args = [this._wrapped];
// args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 替代 obj 完成
push.apply(args, arguments);
return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args);
};
});
有点冗杂,将 return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args);
革新下,
// 推断是不是须要链式挪用
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
var func = _[name];
_.prototype[name] = function() {
var args = [this._wrapped];
// args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 替代 obj 完成
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
好了,碰运气结果:
_.chain([1, 2, 3])
.each(function(item) {
console.log(item);
})
.each(function(item) {
console.log(item);
});
// 1 2 3 1 2 3
// {_wrapped: [1,2,3], _chain: true}
混入(mixin)
underscore 很壮大,功用也很完全,但有时刻也不能满足一切人的需求。我们想建立一些要领,让它挂载在 _
上,如许我们全局也能够挪用到这些要领,作为一款壮大的要领库,也应当供应这类接口,让用户自定增加要领,ok, let us do it !
我们先定义一个 mixin 要领
_.mixin = function(obj) {};
// `obj` 为一个相似 `_` 的对象。传入的这个对象,也须要遍历一次,而且复制要领于 prototype 属性上。细致代码以下:
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = (_[name] = obj[name]);
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
// args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 替代 obj 完成
return chainResult(this, func.apply(_, args));
};
});
return _;
};
看到这里,你会发明,我们在要领的末了遍历赋值给_.prototype
要领,实在就是一次mixin()
的挪用.
_.each(_.functions(_), function(name) {
var func = _[name];
_.prototype[name] = function() {
var args = [this._wrapped];
// args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 替代 obj 完成
push.apply(args, arguments);
return func.apply(_, args);
};
});
// 简化为
_.mixin(_);
终究代码
(function() {
// root 为挂载对象,为 self 或 global 或 this 或 {}
var root =
(typeof self == 'object' && self.self === self && self) ||
(typeof global == 'object' && global.global === global && global) ||
this ||
{};
var _ = function(obj) {
// 假如传入的是实例后对象,返回它
if (obj instanceof _) return obj;
// 假如还没有实例化,new _(obj)
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// 最大数值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
// 推断是不是为数组
var isArrayLike = function(collection) {
var length = collection.length;
return (
typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX
);
};
// 推断是不是须要链式挪用
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
root._ = _;
_.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧
/**
* 字符串倒装
*/
_.reverse = function(string) {
return string
.split('')
.reverse()
.join('');
};
/**
* 推断是不是为 function
*/
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
// 链式挪用要领
_.chain = function(obj) {
// 取得一个经underscore包裹后的实例
var instance = _(obj);
// 标识当前实例支撑链式挪用
instance._chain = true;
return instance;
};
/**
* 猎取_的一切属性函数名
*/
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
/**
* 数组或对象遍历要领,并返回修改后的对象或数组
* @param iteratee 回调函数
* @param context 回调函数中this的指向
*/
_.map = function(obj, iteratee, context) {
var length = obj.length,
results = Array(length);
for (var index = 0; index < length; index++) {
results[index] = iteratee.call(context, obj[index], index, obj);
}
return results;
};
/**
* 数组或对象遍历要领
*/
_.each = function(obj, callback) {
var length,
i = 0;
if (isArrayLike(obj)) {
// 数组
length = obj.length;
for (; i < length; i++) {
// 这里隐式的挪用了一次 callback.call(obj[i], obj[i], i);
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
} else {
// 对象
for (i in obj) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
}
return obj;
};
/*
* 混入要领 mixin
*/
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = (_[name] = obj[name]);
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
// args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 替代 obj 完成
return chainResult(this, func.apply(_, args));
};
});
return _;
};
_.mixin(_);
})();