文享日志

Underscore源码

JavaScript Underscore

发表于2017年09月12日21:14:36

0条评论 320次阅读

//此源码分析自参考前端网,老姚笔记
//http://www.qdfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370/page/2.html
//在老姚分析基础上添加了一些个人觉得应当掌握的一些要点。
//若有兴趣的话,建议将本篇与老姚笔记结合一起来看

(function(){

//绑定this,浏览器为window,node为exports
	var root = this;

//如果本框架外有用到'_'这个变量,就先将这个变量保存到一个内部变量,最后调用完本框架,再用_.noConflict恢复。
	var previousUnderscore = root._;


 	var ArrayProto = Array.prototype, 
 		ObjProto = Object.prototype, 
 		FuncProto = Function.prototype;

	var
	    push             = ArrayProto.push,
	    slice            = ArrayProto.slice,
	    toString         = ObjProto.toString,
	    hasOwnProperty   = ObjProto.hasOwnProperty;

	var
		nativeIsArray      = Array.isArray,
		nativeKeys         = Object.keys,
		nativeBind         = FuncProto.bind,
		nativeCreate       = Object.create;


//var a = new Array();
//var a = Array();
	var _ = function(obj){
		if(obj instanceof _) return obj;
		if(!(this instanceof _)) return new _(obj);
		this._wrapped = obj;
	};

//this在创建出实例后,指向_q
	// var _q = new _({name:'han'});
	// console.log(_q);
	
	

// 因为有一些地方需要空函数(比如接口,等着被覆盖),所以提出来,让需要空函数的地方指向这里,节省空间
	_.noop = function(){};

// var a ;
// console.log( undefined ===_.noop())      true


//求min到max之间的随机整数,当参数为一个时,求0到该参之间的随机数
	_.random = function(min,max){
		if(max == null){
			max = min;
			min = 0;
		}
		return min + Math.floor(Math.random()*(max - min + 1));
	};

//返回一串时间数字,获取时间戳,可以用来代替id当标识
	_.now = Date.now || function() {
	    return new Date().getTime();
	  };


//为对象指定id,没指定时,自己累加
	var idCounter = 0;
	_.uniqueId = function(prefix){
		var id = ++idCounter + '';//转为字符串格式
		return prefix ? prefix + id : id ;
	};


//避免与全局变量冲突
	//var previousUnderscore = root._;  位置在源码的开头
	_.noConflict = function(){
		root._ = previousUnderscore;
		return this;
	};


//返回自身值,作用是放在if条件处,判断当前返回的value能转成true还是false
//转为false的参数 '' , false , NaN , null , undefined , 0
	_.identity = function(value){
		return value;
	};

//返回一个返回自身值的函数
	_.constant = function(value){
		return function(){
			return value;
		};
	};

//注意:判断一个数为undefined最好用void 0
	_.isUndefined = function(obj){
		return obj === void 0;
	};

	_.isNull = function(obj){
		return obj ===null;
	};


//如果支持原生isArray方法就用原生方法,否则自己判断
	_.isArray = nativeIsArray || function(obj){
		return toString.call(obj) === '[object Array]';
	};

//绑定执行环境函数,指定参数个数,返回个函数
	var optimizeCb = function(func, context, argCount) {
		if (context === void 0) return func;
		switch (argCount == null ? 3 : argCount) {
		  case 1: return function(value) {
		    return func.call(context, value);
		  };
		  case 2: return function(value, other) {
		    return func.call(context, value, other);
		  };
		  case 3: return function(value, index, collection) {
		    return func.call(context, value, index, collection);
		  };
		  case 4: return function(accumulator, value, index, collection) {
		    return func.call(context, accumulator, value, index, collection);
		  };
		}
		return function() {
		  return func.apply(context, arguments);
		};
	};

//判定像数组的函数,比如arguments,nodes(文档对象集)
//这个函数有个bug(已改写)
//console.log(isArrayLike({name:'han'}))返回true
	var MAX_ARRAY_INDEX = Math.pow(2,53)-1;
	var isArrayLike = function(collection){
		if(!collection) return false ;
		var length = collection.length ;
		//下面这句是原来的,上面的是改写的
		//var lenght = collection != null && collection.length;
		//这里传入对象,length会为0,下面return会返回true
		return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
	};

//toString那里判断的是刚new出来的bool对象
	_.isBoolean = function(obj){
		return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
	};

//这里注意 NaN !== NaN,第二行的+不知是为了什么
	_.isNaN = function(obj){
		return _.isNumber(obj) && obj !==+obj;
	};


//第二行的isFinite是语言自带的判断数是否无穷大函数,finite是‘有限’的意思
	_.isFinite = function(obj){
		return isFinite(obj) && !isNaN(parseFloat(obj));
	};

//判断是不是对象。注意!null为true,&&优先级高于||
	_.isObject = function(obj){
		var type = typeof obj;
		return type === 'function' || type === 'object' && !!obj;
	};


//从Dom中获取的元素nodeType类型为‘1’,因为当obj为null或者undefined时,会直接返回(因为两个bool值为false,所以没必要判断后半句),所以添加两个!!,转化为布尔值。
	_.isElement = function(obj){
		return !!(obj && obj.nodeType === 1);
	};


//判断当前对象是否包含某个属性(不包含原型中的),hasOwnProperty原生方法
	var ObjProto = Object.prototype;
	var hasOwnProperty = ObjProto.hasOwnProperty;
	_.has = function(obj, key){
		return obj != null && hasOwnProperty.call(obj, key);
	};


//判断是否有枚举bug(ie低版本得到的true),PropertyIsEnumerable 是检测属性是否可用 for...in 枚举,所有自定义属性默认是可枚举的。详细可查看https://www.zhihu.com/question/21907133
var hasEnumBug = !{toString : null}.propertyIsEnumerable('toString');

//不让枚举的属性有哪些
var nonEnumerableProps = ['valueOf','isPrototypeof','toString','propertyIsEnumerable','hasOwnProperty','toLocalString'];

//给keys添加本该枚举的属性
function collectNonEnumProps(obj,keys){

	//不可枚举属性长度
	var nonEnumIdx = nonEnumerableProps.length;
	//constructor是类(构造函数)原型中属性,obj.constructor指向类
	var constructor = obj.constructor;
	
	//obj的原型
	var proto = (_.isFunction(constructor) && constructor.prototype)||ObjProto;
	
	//因为constructor跟其他如toString的处理逻辑不一样,这里单独处理??这里为什么逻辑不一样??
	var prop = 'constructor';
	//如果对象有constructor属性但keys数组中没有,就添加进来
	if(_.has(obj,prop)&&!_.contains(keys,prop)) keys.push(prop);

//添加自定义的规范规定的不可枚举属性	
	while(nonEnumIdx--){
		prop = nonEnumerableProps[nonEnumIdx];
		//比如toString在obj中
		//并且obj的toString并不是原型的toString,并且keys里面没有,添加进来
		if(prop in obj && obj[prop] !== proto[prop] && !_.contains(keys,prop)){
			keys.push(prop);
		}
	}
}

//找出对象的除了原型中的键,支持原生keys方法调用原生方法,不支持则遍历,注意in会查找原型中自己定义的属性。此外,如果对一个数组添加属性,in除了会查找数组中对象外,还会查找添加的属性
	var nativeKeys = Object.keys;
	_.keys = function(obj){
		if(!_.isObject(obj)) return [];
		if(nativeKeys) return nativeKeys(obj);
		var keys = [];
		//if条件排除掉原型中的属性
		for(var key in obj) if(_.has(obj,key)) keys.push(key);
		//ie9以下兼用
		if(hasEnumBug) collectNonEnumProps(obj,keys);
		return keys;
	};

//返回所有obj中的属性,包括原型中的
	_.allKeys = function(obj){
		if(!_.isObject(obj)) return [];
		var keys = [];
		for(var key in obj) keys.push(key);		
		if(hasEnumBug) collectNonEnumProps(obj,keys);
		return keys;
	};

//重写forEach函数,
// console.log(_.each({one: 1, two: 2, three: 3}, alert))
	_.each = _.forEach = function(obj, iteratee, context) {
		//将iteratee函数绑定在context对象上
		iteratee = optimizeCb(iteratee, context);
		var i, length;
		if(isArrayLike(obj)){
		  for (i = 0, length = obj.length; i < length; i++) {
		    iteratee(obj[i], i, obj);
		  }
		} else {
		  var keys = _.keys(obj);
		  for (i = 0, length = keys.length; i < length; i++) {
// alert(1,'one',{one: 1, two: 2, three: 3});这里注意只返回1		  	
		    iteratee(obj[keys[i]], keys[i], obj);
		  }
		}
		return obj;
	};


//批量为个对象构建判断函数,仔细看前两个函数和这个,能看懂
	_.each(['Arguments','Function','String','Number','Date','RegExp','Error'],function(name){
		_['is' + name] = function(obj){
			return toString.call(obj) ==='[object '+ name +']';
		};
	});

//该函数传入一个函数和控制条件,返回一个函数
	var createAssigner = function(keysFunc, undefinedOnly){
		return function(obj){
			var length = arguments.length;
			//一个参数或者obj为空,直接返回obj
			if(length < 2 || obj == null) return obj;
			
			//从第二个参数开始(第一个为dst,第二个以后都是src)
			for(var index = 1; index < length; index++){
			
				//取出一个src
				var source = arguments[index],
					//取出对应的keys。因为keysFunc,三个api中只是_.keys和_.allKeys
					keys = keysFunc(source),
					l = keys.length;
					
					for(var i = 0; i < l; i++){
						var key = keys[i];
						//拷贝条件,这个条件在_.default函数有点用
						//!undefined为true,所以在_.extend和_.extendOwn函数这里控制条件没什么用
						if(!undefinedOnly || obj[key] === void 0)
							obj[key] = source[key];
					}
					//下面这个return是自己加的,用于验证
					return obj
			}
		};
	}

//将第二个及以后的对象的属性(包括原型中的)拷贝进第一个对象中
	_.extend = createAssigner(_.allKeys);

//将第二个及以后的对象的属性(不包括原型中的)拷贝进第一个对象中
	_.extendOwn = _.assign = createAssigner(_.keys);

//判断一个对象attrs属性及值是否在另一个对象object属性(包括该对象的原型链)中.
//_.isMatch({name:'han',age:11},{name:'han'})
	_.isMatch = function(object, attrs){
		var keys = _.keys(attrs),
			length = keys.length;
		if(object == null) return !length;
		var obj = Object(object);
		for(var i = 0; i < length; i++){
			var key = keys[i];
			
			//value不等或者不在obj中,就返回false
			if(attrs[key]!==obj[key]||!(key in obj)) return false;
		}
		return true;
	};


//返回一个匹配函数
// console.log(_.matches({name:'han'})({name:'han',age:22}))
	_.matcher = _.matches = function(attrs){
		attrs = _.extendOwn({},attrs);
		return function(obj){
			return _.isMatch(obj, attrs);
		};
	};

//输出一个输出对象为某个属性值的函数
//console.log(_.property('name')({name:'han'}))
	_.property = function(key){
		return function(obj){
			return obj == null ? void 0 : obj[key];
		};
	};
 	var getLength = _.property('length');


//绑定,返回函数
	var cb = function(value, context, argCount){
		//_.identity是一个返回自身参数值的函数
		if(value == null) return _.identity;
		//将value函数绑定在context对象上。返回绑定对象后的函数
		if(_.isFunction(value))
			return optimizeCb(value, context, argCount);		
		//value为对象,返回一个是否匹配属性的函数
		if(_.isObject(value)) return _.matcher(value);
		return _.property(value);
	};




//获取对象每个键的值
	_.values = function(obj) {
	    var keys = _.keys(obj);
	    var length = keys.length;
	    var values = Array(length);
	    for (var i = 0; i < length; i++) {
	      values[i] = obj[keys[i]];
	    }
	    return values;
	};


//二分法查找,返回找到的值的下标.
// var stooges = [{name: 'moe', age: 40}, {name: 'curly', age: 60}];
// console.log(_.sortedIndex(stooges, {name: 'larry', age: 50}, 'age'));
	_.sortedIndex = function(array, obj, iteratee, context){
		//iteratee参数为空,返回一个返回自身参数的函数
		//为其他(除了对象,函数),返回_.property(value),这里需要看看原函数
		//ƒ (obj){
		// 	return obj == null ? void 0 : obj[key];
		//}
		iteratee = cb(iteratee, context, 1);
		var value = iteratee(obj);
		var low = 0, high = array.length;
		while(low0? 0 : length-1;
			for(;index >=0 && index < length; index +=dir){
				//他原来是这样写的
				//if(predicate[index],index,array) return index;
				if(predicate(array[index])) return index;	 
			}
		};
	}

	_.findIndex = createIndexFinder(1);
	_.findLastIndex = createIndexFinder(-1);

//上边这两个函数的用法
// var users = [{'id': 1, 'name': 'Bob'},
//              {'id': 2, 'name': 'Ted'},
//              {'id': 3, 'name': 'Frank'},
//              {'id': 4, 'name': 'Ted'}];
// console.log(_.findIndex(users,{name: 'Frank'}));
// console.log(_.findLastIndex(users, function(value){
//         return /^T/.test(value.name);
// }))


//查找array的下标值(正向)。
// console.log(_.indexOf([1, 2, 3], 5 ,2));
	_.indexOf = function(array, item, isSorted){
		var i = 0,length = array && array.length;
		//isSorted传入数字(下标),指定从哪里开始查找
		if(typeof isSorted == 'number'){
			i = isSorted<0 ? Math.max(0,length + isSorted) : isSorted;
		}else if(isSorted && length){
			// isSorted传入true,调用二分法快速查找,没找到返回-1;
			console.log(11);
			i = _.sortedIndex(array, item);
			return array[i] === item ? i : -1;
		}
		//NaN != NaN
		//在数组中查找NaN
		if(item !== item){
			return _.findIndex(slice.call(array, i), _.isNaN);
		}
		for(;i= 0) if (array[idx] === item) return idx;
	    return -1;
	};


//判断数组,类数组或对象是否包含指定值
	_.contains = _.includes = _.include = function(obj,target,fromIndex){
		if(!isArrayLike(obj)) obj = _.values(obj);
		return _.indexOf(obj,target,typeof fromIndex == 'number'&&fromIndex)>=0;
	}


//把一个对象转变为一个[key, value]形式的数组
	_.pairs = function(obj){
		var keys = _.keys(obj);
		var length = keys.length;
		var pairs = Array(length);
		for(var i = 0;i < length; i++){
			pairs[i] = [keys[i],obj[keys[i]]];
		}
		return pairs;
	};

//将对象键与值进行翻转
	_.invert = function(obj){
		var result = {};
		var keys = _.keys(obj);
		for(var i = 0,length = keys.length;ib || a === void 0) return 1;
				if(a=0 && index0 ? 0 : length -1;
			if(arguments.length < 3){
				//获取数组或者对象中的项
				memo = obj[keys ? keys[index] : index];
				index += dir;
			}
			//[],function,0,0,3;
			// console.log(obj,iteratee,memo,index,length);
			return iterator(obj, iteratee,memo,keys,index,length);
		}
	}


// 模拟数组的原生reduce方法。对数组元素迭代,最后拿到一个值。
// console.log(_.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0));
// console.log(_.reduceRight([[0, 1], [2, 3], [4, 5]], function(a, b) { return a.concat(b); }, []));
	_.reduce = _.foldl = _.inject = createReduce(1);
	_.reduceRight = _.foldr = createReduce(-1);


	var FuncProto = Function.prototype ;
	var nativeBind  = FuncProto.bind ;

	var executeBound = function(sourceFunc,boundFunc,context,callingContext,args){
		//当new时,this指向新创建的对象,if条件为false
		if(!(callingContext instanceof boundFunc))
			return sourceFunc.apply(context, args);
		//构建new出来的对象self
		//在self的__proto__属性上添加constructor指向sourceFunc
		var self = baseCreate(sourceFunc.prototype);
		var result = sourceFunc.apply(self, args);
		//这里返回的是有sourceFunc函数的空对象
		return self;
	}

//为对象绑定作用域
	_.bind = function(func, context){
		//如果有原生bind,使用原生bind
		if(nativeBind && func.bind === nativeBind){
			//原生bind函数只有一个对象参数
			// return func.bind(slice.call(arguments,1));
			return nativeBind.apply(func, slice.call(arguments, 1));
		}
			
		//如果func不是函数,抛异常
		if(!_.isFunction(func)) throw new TypeError('Bind must be called on a function');

		//这种方式能实现,但_.bind后的函数,使用new创建对象,this指针这里有问题
		//具体:http://www.qdfuns.com/notes/17398/777d8f249dd4c0e9d2043faf00a91ba1.html

        // return function(){
        // 	//传递给func的参数来自两部分,
        // 	//一部分是调用_.bind生效的
        // 	//一部分是调用_.bind函数返回的函数生效的
        // 	return func.apply(context,args.concat(slice.call(arguments)));
        // };

		
		//取出要传给func的参数
		var args = slice.call(arguments, 2);
		var bound = function(){
			//这里this会随着绑定函数被new后改变方向
			//未new时,执向bound,new之后,指向new出的对象。
			return executeBound(func,bound,context,this,args.concat(slice.call(arguments)));
		};
		return bound;
	};

// 原生bind的两种调用方式,所以可以在_.bind函数中参数传递用的args.concat(slice.call(arguments)。
// var f = function(name,sex){
//         console.log(name + this.name);
//         console.log(sex + this.age);
// }
// var fn1 = f.bind({name:"han",age:20});
// fn1('myname','age');

// var fn2 = f.bind({name:'han',age:22},'myname');
// fn2('age');
// http://www.qdfuns.com/notes/17398/777d8f249dd4c0e9d2043faf00a91ba1.html



// 局部应用一个函数填充在任意个数的 arguments,不改变其动态this值
	_.partial = function(func){
		//获取要传入func的参数数组
		var boundArgs = slice.call(arguments,1);
		var bound = function(){
			var position = 0,length = boundArgs.length;
			var args = Array(length);
			for(var i = 0; i < length; i++){
				//如果要传入func的参数为_占位符,则取bound参数,否则取boundArgs参数
				args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
			}
			while(position < arguments.length)
				args.push(arguments[position++]);
			return executeBound(func, bound, this, this, args);
		};
		return bound;
	};

// var fun = function(x1,x2,x3,x4,x5,x6){
//         var str = "x1=" + x1;
//         str += ",x2=" + x2;
//         str += ",x3=" + x3;
//         str += ",x4=" + x4;
//         str += ",x5=" + x5;
//         str += ",x6=" + x6;
//         return str;
// };
//这里的_是对象,做占位符
// var f = _.partial(fun,1,_,3,_);
// console.log(f(2,4,5,6));

//将obj绑定到所有函数上
	_.bindAll = function(obj){
		var i, length = arguments.length,key;
		//如果没传function names,抛异常
		if(length <= 1)
			throw new Error('bindAll must be passed function names');
		
		for(i = 1; i < length; i++){
			key = arguments[i];
			//把对象的方法的this绑定到obj上
			obj[key] = _.bind(obj[key], obj);
		}
		return obj;
	};


//缓存函数。缓存某函数的计算结果。
//原理闭包。对象中保存运算结果
	_.memoize = function(func, hasher){
		var memoize = function(key){
			var cache = memoize.cache;
			var address = '' +(hasher ? hasher.apply(this, arguments) : key);
			if(!_.has(cache, address))
				cache[address] = func.apply(this, arguments);
			return cache[address];
		};
		memoize.cache = {};
		return memoize;
	};
// var fibonacci = _.memoize(function(n) {
//   return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
// });
// console.log(fibonacci(1111));

//延时执行函数
	_.delay = function(func, wait){
		var args = slice.call(arguments,2);
		return setTimeout(function(){
			return func.apply(null, args);
		},wait)
	}


//延迟调用function直到当前调用栈清空为止.
//类似使用延时为0的setTimeout方法。
//对于执行开销大的计算和无阻塞UI线程的HTML渲染时候非常有用.
	_.defer = _.partial(_.delay, _, 1);



//节流函数(重点就是时间的把握),这里要画图
//注意remaining,和timeout
	_.throttle = function(func, wait, options){
		var context, args, result;
		var timeout = null;
		var previous = 0;
		if(!options) options = {};
		
		var later = function(){
			previous = options.leading === false?0:_.now();
			timeout = null;
			result = func.apply(context, args);
			if(!timeout) context = args = null;
		}
		
		return function(){
			//当前时间
			var now = _.now();
			if(!previous && options.leading === false)
				//previous记录上一次函数执行时间
				previous = now;
			//判断当前函数执行与上一次函数执行时间差与wait的关系
			var remaining = wait - (now - previous);
			// console.log(remaining);
			//这里注意remaining在下面setTimeout函数中做参数
			context = this;
			args = arguments;
			//刚调用函数时,执行if条件里的。
			//执行传入的函数,清除定时器(若有),作用域,参数置空
			if(remaining <= 0 || remaining > wait){
				if(timeout){
					clearTimeout(timeout);
					timeout = null;
				}
				previous = now;
				result = func.apply(context, args);
				if(!timeout) context = args = null;
				//注意这里条件,当timeout不存在是才设置定时器
			}else if(!timeout && options.trailing !== false){
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	};
// console.log("start")
// var f = function(){console.log("f run")};
// var tf = _.throttle(f,3000);
// setInterval(function(){
//         console.log("tf run")
//         tf();
// },1000);



//返回 function 函数的防反跳版本,将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后.对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。 例如: 渲染一个Markdown格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.
//最好也画一个图
	_.debounce = function(func, wait, immediate){
		var timeout, args, context, timestamp, result;
		var later = function(){
			var last = _.now() - timestamp;
			if(last < wait && last >= 0){
				timeout = setTimeout(later, wait -last);
			}else{
				timeout = null;
				if(!immediate){
					result = func.apply(context, args);
					if(!timeout) context = args = null;
				}
			}
		};		
		return function(){
			context = this;
			args = arguments;
			//注意时间戳具体在哪里
			timestamp = _.now();
			var callNow = immediate && !timeout;
			if(!timeout) timeout = setTimeout(later, wait);
			if(callNow){
				result = func.apply(context, args);
				context = args = null;
			}
			return result;
		};
	}

// console.log("start")
// var f = function(){console.log("f run")};
// var tf = _.debounce(f,2200);
// var num = 0;
// var timer = setInterval(function(){
//         console.log("tf run")
//         tf();   
//         num ++;
//         if(num == 4){
//                 clearInterval(timer);
//         }
// },1000);


//将第一个函数 function 封装到函数 wrapper 里面, 并把函数 function 作为第一个参数传给 wrapper. 
	_.wrap = function(func, wrapper){
		return _.partial(wrapper,func);
	};

// var hello = function(name) {
// 				return "hello: " + name; 
// 			};
// hello = _.wrap(hello, function(func) {
// 			return "before, " + func("moe") + ", after";
// 		});
// console.log(hello());



	_.compose = function(){
		var args = arguments;
		var start = args.length -1;
		return function(){
			var i = start;
			var result = args[start].apply(this, arguments);
			//从后往前一次次调用
			while(i--) result = args[i].call(this, result);
			return result;
		};
	};

// var greet    = function(name){ return "hi: " + name; };
// var exclaim  = function(statement){ return statement.toUpperCase() + "!"; };
// var welcome = _.compose(greet, exclaim);
// console.log(welcome('moe'))	;


//创建一个函数, 只有在运行了 count 次之后才有效果. 
//在处理同组异步请求返回结果时, 如果你要确保同组里所有异步请求完成之后才执行这个函数, 这将非常有用。
//类似nodejs里的eventproxy模块吧,功能差不多
	_.after = function(times, func){
		return function(){
			if(--times < 1){
				return func.apply(this, arguments);
			}
		};
	};


//创建一个函数,调用不超过times次。 当times已经达到时,最后一个函数调用的结果将被记住并返回。
	_.before = function(times, func){
		var memo;
		return function(){
			if(--times > 0){
				memo = func.apply(this, arguments);
			}
			if(times <= 1) func = null;
			return memo;
		};
	};
// var be = _.before(3,function(name){return name;});
// be("xxx");	xxx
// be("yyy");	yyy
// be("zzz");	yyy
// be("www");	yyy


//创建一个只能调用一次的函数。重复调用改进的方法也没有效果,只会返回第一次执行时的结果
	_.once = _.partial(_.before, 2);
	//_.before(2,func);


//调用n次函数,返回结果数组
	_.times = function(n, iteratee, context){
		var accum = Array(Math.max(0,n));
		iteratee = optimizeCb(iteratee, context, 1);
		for(var i = 0; i < n; i++) accum[i] = iteratee(i);
		return accum;
	};
// console.log(_.times(3,function(i){return i}));


	var escapeMap = {
	    '&': '&',
	    '<': '<',
	    '>': '>',
	    '"': '"',
	    "'": ''',
	    '`': '`'
	};
	var createEscaper = function(map){
		var escaper = function(match){
			return map[match];
		};
		//(?:)表示排除引用.....这个??
		var source = '(?:' + _.keys(map).join('|') + ')';
		var testRegexp = RegExp(source);
		var replaceRegexp = RegExp(source, 'g');
		return function(string){
			string = string == null ? '' : '' + string;
			//replace将在replaceRegexp中查找到的字符串传入escaper函数
			// console.log(string.replace(replaceRegexp,escaper));
			return testRegexp.test(string) ? string.replace(replaceRegexp,escaper) : string;
		};
	};

//替换字符串中特殊字符
	 _.escape = createEscaper(escapeMap);
	 // console.log(_.escape('hans < jak'))


//注意这里的_.invert将键和值位置互换
	var unescapeMap = _.invert(escapeMap);

//功能恰好与_.escape相反
	_.unescape = createEscaper(unescapeMap);


//查找object中的proerty键,没找到执行fallback函数
	_.result = function(object, property, fallback){
		var value = object == null ? void 0 : object[property];
		if(value === void 0){
			value = fallback;
		}
		return _.isFunction(value) ? value.call(object):value;
	}
	// _.result({name:'han'},'nam',function(){
	// 	console.log("Not Found");
	// });


//模板

//[\s\S]表示任意字符。\s是表示空白,\S表示非空白,+表示至少一个,?表示不贪婪匹配
	_.templateSettings = {
		evaluate    : /<%([\s\S]+?)%>/g,
		interpolate : /<%=([\s\S]+?)%>/g,
		escape      : /<%-([\s\S]+?)%>/g
	};

	var noMatch = /(.)^/;

	var escapes = {
		"'":      "'",
		'\\':     '\\',
		'\r':     'r',
		'\n':     'n',
		'\u2028': 'u2028',
		'\u2029': 'u2029'
	};

	var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

	var escapeChar = function(match) {
		return '\\' + escapes[match];
	};

	_.template = function(text, settings, oldSettings) {
		//兼容旧版underscores
		if(!settings && oldSettings) settings = oldSettings;
		//未传入setting则使用默认,这里要看看_.defaults用法
		settings = _.defaults({}, settings, _.templateSettings);
		//构建正则,$存在的意义是后面代码replace中的函数能多执行一次。
		var matcher = RegExp([
			(settings.escape || noMatch).source,
			(settings.interpolate || noMatch).source,
			(settings.evaluate || noMatch).source
		].join('|') + '|$','g');
		var index = 0;
		var source = "_p+='";
		//函数主要作用就是拼接字符串中的js代码。
		//text中匹配到几次就调用几次函数,就拼接几次
		//escape,interpolate,evalute保存分组匹配的结果,保存()里的内容
		//[\s\S]+?外边有()包着。
		text.replace(matcher, function(match, escape, interpolate, evaluate, offset){
			source += text.slice(index, offset).replace(escaper, escapeChar);
			index = offset + match.length;
			if(escape) {
				source += "'+\n((_t=(" + escape +"))==null?'':_.escape(_t))+\n'";
			} else if (interpolate){
				source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
			} else if (evaluate) {
				source += "';\n" + evaluate + "\n__p+='";
			}
			return match;
		});
		source += "';\n";
		//with(对象){操作}
		if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
	    source = "var __t,__p='',__j=Array.prototype.join," +
	      "print=function(){__p+=__j.call(arguments,'');};\n" +
	      source + 'return __p;\n';
		try {
			//new Function(函数参数,函数主体)
			var render = new Function(settings.variable || 'obj', '_', source);
	    } catch (e) {
			e.source = source;
			throw e;
	    }
	    //为新创建好的函数绑定当前作用域,传入参数
	    var template = function(data) {
			return render.call(this, data, _);
	    };
	    var argument = settings.variable || 'obj';
	    template.source = 'function(' + argument + '){\n' + source + '}';
	    return template;
	};

// var string = "
  • <%= n%>
  • <%= a%>
  • <%if(s==0){%>male<%}else{%>female<%}%>
  • <%= w%>
" // var template = _.template(string); // var source = template.source; // console.log(source); //面向对象部分。自我粗浅理解,就是调用原型函数吧。。 //标记当前调用是否为链式调用,若要链式调用,先调用该函数标记一下 _.chain = function(obj) { //这里要看看_构造函数。。_(obj)实际为new _(obj)。是一个_的实例。 var instance = _(obj); instance._chain = true; return instance; }; //当前对象为链式调用,则继续链式调用(有点模糊) var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; //将所有_对象上的属性函数添加到_对象的原型上 _.mixin = function(obj) { //_.functions获取_对象的所有函数 _.each(_.functions(obj), function(name) { //这是为变量赋值。 var func = _[name] = obj[name]; _.prototype[name] = function() { //this.wrapped获取到的是对象本身 var args = [this._wrapped]; //将调用的函数参数压入首项为对象本身的数组中 push.apply(args, arguments); //这里调用result是为了判断该函数是否链式调用 //若是,则接着链式调用 //这里注意apply第二个参数为数组或者类数组 //上面几乎所有函数第一个参数为obj,所以这里数组第一项为obj return result(this, func.apply(_, args)); }; }); }; _.mixin(_); //返回对象本身名字的函数 _.prototype.value = function() { return this._wrapped; }; //这些函数功能与Array中对应函数功能一致 _.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); //修复IEbug。?? //https://github.com/jashkenas/underscore/issues/397 if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); //这里要注意这个与上面这个函数的差别。 //pop,push等函数操作数组后,会改变原数组 //而concat,join等函数操作的是原数组副本,不会改变原数组 _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return '' + this._wrapped; }; }).call(this);


👍 0  👎 0
共有0条评论

发表新评论

提交