配合源码地址:heihei12305
node
配合gitbook小书地址 my-underscoregit
1. 将代码都挂载到
_
上,经过_.
引用
目标:es6
_.reverse('hello');
_('hello').reverse();
_.chain([1,2,3,4]).filter((num)=>!num%2).map((num)=>num*num).value();
下面咱们一点一点的说吧。
github
首先为了达到能够用
_
调用的目的,咱们须要先定义出_
,也即:编程
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global)||
this || {};
//在浏览器中,除了window属性,咱们也能够经过self属性直接访问到Window对象,同时self还能够支持Web Worker
//Node环境中全局变量为 global
//node vm(沙盒模型) 中不存在window,也不存在global变量,但咱们却能够经过this访问到全局变量
//在微信小程序中,window和global都是undefined,加上强制使用严格模式,this为undefined,就多了{}
var _ = function(obj){
if(obj instanceof _){
return obj;
}
if(!(this instanceof _)){ // 此处为了实现面向对象风格调用,能够暂时无论
return new _(obj);
}
this._wrapped = obj;
};
//exports.nodeType 防止<div id="exports"></div>产生的window.exports全局变量。
if(typeof exports != 'undefined' && !exports.nodeType){
if((typeof module != 'undefined' && !module.nodeType && module.exports)){
//在nodeJs中,exports是module.exports 的一个引用,当你使用了module.exports = function(){}
//实际上覆盖了module.exports,可是exports并未发生改变,为了不后面在修改exports而致使不能正确输出
//写成这样,将二者保持统一。
exports = module.exports = _;
}
exports._ = _;
}else{
//?不太懂部分,将_挂到全局属性_上
root._ = _;
}
复制代码
上面代码中,咱们首先定义了全局属性,考虑node,浏览器,微信小程序等不一样的环境,咱们进行了一长串的属性选择。最终得到了咱们须要的root属性。而后将_
挂到了root上。这一块能够看看讶羽大大的博客写underscore的第一二章部分小程序
有了_
,咱们就能够开始把咱们写的函数都挂到_
上了,这个简单,定义的时候这么写就行了 _.reverse = ()=>;
微信小程序
为了实现面向对象风格调用。
示例分析:_([1,2,3])
:api
var _ = function(obj){
if(!(this instanceof _)){ // 此处为了实现面向对象风格调用,能够暂时无论
return new _(obj);
}
this._wrapped = obj;
//部分
};
复制代码
this instanceof _ ,this
指向 window,window instanceof _
为 false,!操做符取反,因此执行 new _(obj)
.new _(obj)
中,this指向实例对象,window instanceof _ 为 true
,取反后,代码接着执行。this._wrapped = obj
, 函数执行结束。_([1,2,3])
返回一个对象,为{_wrapped:[1,2,3]}
,该对象原型指向_.prototype
也即咱们下一步目标:将_
上的函数也挂到_.prototype
上。
数组
先把_
上的函数获取一下吧。
浏览器
//将obj中全部函数均push进names中
_.functions = function(obj){
var names = [];
for(var key in obj){
if(_.isFunction(obj[key])){
names.push(key);
}
}
return names.sort();
}
复制代码
一个源于jQuery的通用遍历方法,可用于遍历对象和数组
回调函数拥有两个参数:第一个为对象的成员或数组的索引,第二个为对应变量或内容
并且能够退出循环
//数组遍历
$.each([0,1,2],function(i,n){
console.log('item # '+ i + ": " + n)
})
//item #0:0
//item #1:1
//item #2:2
//对象遍历
$.each({name;"John",lang:"JS"},function(i,n){
console.log('name: '+ i + ",value: " + n)
})
//item name,value:John
//name:lang,value:JS
//退出循环
$.each([0,1,2,3,4,5],function(i,n){
if(i>2){
return false;
}
console.log("item #"+i+": " + n );
});
//item #0:0
//item #1:1
//item #2:2
复制代码
_.each = function(obj,callback){
var length,i = 0;
//判断类数组对象和数组
if(_.isArrayLike(obj)){
//为数组时
length = obj.length;
for(;i<length;i++){
//绑定this到当前遍历元素上,可是call对性能有一丢丢影响
if(callback.call(obj[i],obj[i],i) === false){
//当回调函数返回false的时候,咱们就停止循环
break;
}
}
}else{
//为对象时
for( i in obj){
if(callback.call(obj[i],obj[i],i) === false){
break;
}
}
}
return obj;
}
}
复制代码
有了上面两个函数,咱们就能够来写咱们的主角 mixin()
了
回顾一下
由于_([1,2,3])
返回一个为{_wrapped:[1,2,3]}
的原型指向_.prototype
的对象
为了调用_
函数对象上的方法,咱们要把_
上的方法复制到_.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);
return chainResult(this,func.apply(_,args)); //此处为了链式调用能够暂时无论
};
});
return _;
}
复制代码
这里咱们就能够用对象风格和函数风格来调用咱们的函数库里的文件了,如今,咱们来实现链式调用。
链式调用
回顾一下:
链式调用例子 : _.chain([1,2,3,4]).filter((num)=>!num%2).map((num)=>num*num).value();
就依照上面那个函数走好了
为了实现链式调用,咱们首先须要调用_.chain()
处理如下咱们的参数
_.chain = function(obj){
var instance = _(obj);
instance._chain = true;
return instance;
}
复制代码
源码很简单,咱们把传入的obj处理了一下,结合上面的_
函数,回到顶部,传入[1,2,3,4]
,返回值会是
{
_wrapped : [1,2,3,4];
_chain : true;
}
复制代码
而后咱们须要一个函数来判断咱们刚添加的_chain
属性,也即 chainResult()
//为了判断是否有_.chain(),便是否采用链式调用
var chainResult = function (instance, obj){
return instance._chain?_(obj).chain():obj;
}
复制代码
而后,在咱们在mixin()
中调用便可:chainResult(this,func.apply(_,args));
回顾一下:
//mixin()函数部分
_.prototype[name] = function(){
var args = [this._wrapped];
push.apply(args,arguments);
return chainResult(this,func.apply(_,args));
};
复制代码
咱们在调用_.prototype
上的函数时,会跳转到mixin()
中也即咱们定义原型链函数的地方,而后return的时候会调用chainResult
函数,而后返回值就会被处理,也即被调用的函数都会返回一个带有_chain : true
的对象。
可是咱们最后的函数返回值依旧是一个对象,咱们须要的是里面的_wrapped
,这里就须要咱们的_.value()
了:
_.prototype.value = function(){
return this._wrapped;
}
复制代码
小节结语,这里,咱们完成了一个函数库最基础的布置,下面的章节咱们开始向咱们的函数库填充抽象出的可复用函数啦!
类数组之
isArrayLike()
类数组定义:拥有一个length 属性和若干索引属性的对象
例如:
a = {
1:1,
2:2,
c:3,
length:4
}
console.log(_.isArrayLike(a));//true
复制代码
_.isArrayLike = function(collection){
var length = collection.length;
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
复制代码
其中,MAX_ARRAY_INDEX
指的是最大安全数
var MAX_ARRAY_INDEX = Math.pow(2,53) - 1;
关于最大安全数,不懂的话能够看看这个JavaScript 浮点数陷阱及解法
函数判断之
isFunction()
_.isFunction = function(obj){
return typeof obj == 'function' || false;
}
复制代码
就是简单判断一个函数而已,不过能够扩展出一个知识点嘿嘿,
你是怎么看js中的&&,||
?
与其说是逻辑运算符,不如说是属性选择符。
&&
执行方式(a&&b):
||
执行方式(a || b):
'' || [1,2] //[1,2]
'' && [1,2] //""
[1] || [] //[1]
[1] && [] //[]
复制代码
而后把js中为false的属性整理一下啦:
ES5规范9.2定义了抽象操做ToBoolean,列举了布尔强制类型转换全部可能出现的结果。
从逻辑上说,假值列表之外的都应该是真值。
let a = 'false';
let b = '0';
let c = "''";
let d = Boolean(a && b && c );
d; // true
复制代码
[],{},fucntion(){}
都不在假值列表里,因此他们都是真值。let a = [];
let b = {};
let c = function(){};
let d = Boolean(a && b && c );
d; // true
复制代码
真值列表是无限长的,咱们只能根据假值列表做为参考,能够理解位假值列表之外的值都是真值。
function foo(a,b){
a = a || 'hello';
b = b || 'world';
console.log(a + ' ' + b);
};
foo(1);// '1 world'
复制代码
function foo(){
console.log(a);
};
let a = 1;
a && foo();
//当第一个操做数为真值时,&&才会选择第二个操做数做为返回值,即前面的表达式为后面的表达式'把关'
复制代码
字符反转
reverse()
_.reverse = function(string){
return string.split('').reverse().join('');
}
复制代码
这是一种取巧的作法,过程至关于
'1234'=>['1','2','3','4']=>['4','3','2','1']=>'4321'
复制代码