// underscore.js 1.5.2 // http://underscorejs.org // (c) 2009-2013 jeremy ashkenas, documentcloud and investigative reporters & editors // underscore may be freely distributed under the mit license. /* 浠g爜鏁寸悊锛氭噿浜轰箣瀹?www.lanrenzhijia.com */ (function() { // baseline setup // -------------- // establish the root object, `window` in the browser, or `exports` on the server. var root = this; // save the previous value of the `_` variable. var previousunderscore = root._; // establish the object that gets returned to break out of a loop iteration. var breaker = {}; // save bytes in the minified (but not gzipped) version: var arrayproto = array.prototype, objproto = object.prototype, funcproto = function.prototype; // create quick reference variables for speed access to core prototypes. var push = arrayproto.push, slice = arrayproto.slice, concat = arrayproto.concat, tostring = objproto.tostring, hasownproperty = objproto.hasownproperty; // all **ecmascript 5** native function implementations that we hope to use // are declared here. var nativeforeach = arrayproto.foreach, nativemap = arrayproto.map, nativereduce = arrayproto.reduce, nativereduceright = arrayproto.reduceright, nativefilter = arrayproto.filter, nativeevery = arrayproto.every, nativesome = arrayproto.some, nativeindexof = arrayproto.indexof, nativelastindexof = arrayproto.lastindexof, nativeisarray = array.isarray, nativekeys = object.keys, nativebind = funcproto.bind; // create a safe reference to the underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // export the underscore object for **node.js**, with // backwards-compatibility for the old `require()` api. if we're in // the browser, add `_` as a global object via a string identifier, // for closure compiler "advanced" mode. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // current version. _.version = '1.5.2'; // collection functions // -------------------- // the cornerstone, an `each` implementation, aka `foreach`. // handles objects with the built-in `foreach`, arrays, and raw objects. // delegates to **ecmascript 5**'s native `foreach` if available. var each = _.each = _.foreach = function(obj, iterator, context) { if (obj == null) return; if (nativeforeach && obj.foreach === nativeforeach) { obj.foreach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, length = obj.length; i < length; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; } } }; // return the results of applying the iterator to each element. // delegates to **ecmascript 5**'s native `map` if available. _.map = _.collect = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativemap && obj.map === nativemap) return obj.map(iterator, context); each(obj, function(value, index, list) { results.push(iterator.call(context, value, index, list)); }); return results; }; var reduceerror = 'reduce of empty array with no initial value'; // **reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. delegates to **ecmascript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if (obj == null) obj = []; if (nativereduce && obj.reduce === nativereduce) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } each(obj, function(value, index, list) { if (!initial) { memo = value; initial = true; } else { memo = iterator.call(context, memo, value, index, list); } }); if (!initial) throw new typeerror(reduceerror); return memo; }; // the right-associative version of reduce, also known as `foldr`. // delegates to **ecmascript 5**'s native `reduceright` if available. _.reduceright = _.foldr = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if (obj == null) obj = []; if (nativereduceright && obj.reduceright === nativereduceright) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduceright(iterator, memo) : obj.reduceright(iterator); } var length = obj.length; if (length !== +length) { var keys = _.keys(obj); length = keys.length; } each(obj, function(value, index, list) { index = keys ? keys[--length] : --length; if (!initial) { memo = obj[index]; initial = true; } else { memo = iterator.call(context, memo, obj[index], index, list); } }); if (!initial) throw new typeerror(reduceerror); return memo; }; // return the first value which passes a truth test. aliased as `detect`. _.find = _.detect = function(obj, iterator, context) { var result; any(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; return true; } }); return result; }; // return all the elements that pass a truth test. // delegates to **ecmascript 5**'s native `filter` if available. // aliased as `select`. _.filter = _.select = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativefilter && obj.filter === nativefilter) return obj.filter(iterator, context); each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) results.push(value); }); return results; }; // return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { return _.filter(obj, function(value, index, list) { return !iterator.call(context, value, index, list); }, context); }; // determine whether all of the elements match a truth test. // delegates to **ecmascript 5**'s native `every` if available. // aliased as `all`. _.every = _.all = function(obj, iterator, context) { iterator || (iterator = _.identity); var result = true; if (obj == null) return result; if (nativeevery && obj.every === nativeevery) return obj.every(iterator, context); each(obj, function(value, index, list) { if (!(result = result && iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // determine if at least one element in the object matches a truth test. // delegates to **ecmascript 5**'s native `some` if available. // aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { iterator || (iterator = _.identity); var result = false; if (obj == null) return result; if (nativesome && obj.some === nativesome) return obj.some(iterator, context); each(obj, function(value, index, list) { if (result || (result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // determine if the array or object contains a given value (using `===`). // aliased as `include`. _.contains = _.include = function(obj, target) { if (obj == null) return false; if (nativeindexof && obj.indexof === nativeindexof) return obj.indexof(target) != -1; return any(obj, function(value) { return value === target; }); }; // invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); var isfunc = _.isfunction(method); return _.map(obj, function(value) { return (isfunc ? method : value[method]).apply(value, args); }); }; // convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; // convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs, first) { if (_.isempty(attrs)) return first ? void 0 : []; return _[first ? 'find' : 'filter'](obj, function(value) { for (var key in attrs) { if (attrs[key] !== value[key]) return false; } return true; }); }; // convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findwhere = function(obj, attrs) { return _.where(obj, attrs, true); }; // return the maximum element or (element-based computation). // can't optimize arrays of integers longer than 65,535 elements. // see [webkit bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) _.max = function(obj, iterator, context) { if (!iterator && _.isarray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return math.max.apply(math, obj); } if (!iterator && _.isempty(obj)) return -infinity; var result = {computed : -infinity, value: -infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed > result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isarray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return math.min.apply(math, obj); } if (!iterator && _.isempty(obj)) return infinity; var result = {computed : infinity, value: infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // shuffle an array, using the modern version of the // [fisher-yates shuffle](http://en.wikipedia.org/wiki/fisher鈥揧ates_shuffle). _.shuffle = function(obj) { var rand; var index = 0; var shuffled = []; each(obj, function(value) { rand = _.random(index++); shuffled[index - 1] = shuffled[rand]; shuffled[rand] = value; }); return shuffled; }; // sample **n** random values from an array. // if **n** is not specified, returns a single random element from the array. // the internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (arguments.length < 2 || guard) { return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, math.max(0, n)); }; // an internal function to generate lookup iterators. var lookupiterator = function(value) { return _.isfunction(value) ? value : function(obj){ return obj[value]; }; }; // sort the object's values by a criterion produced by an iterator. _.sortby = function(obj, value, context) { var iterator = lookupiterator(value); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, criteria: iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); }; // an internal function used for aggregate "group by" operations. var group = function(behavior) { return function(obj, value, context) { var result = {}; var iterator = value == null ? _.identity : lookupiterator(value); each(obj, function(value, index) { var key = iterator.call(context, value, index, obj); behavior(result, key, value); }); return result; }; }; // groups the object's values by a criterion. pass either a string attribute // to group by, or a function that returns the criterion. _.groupby = group(function(result, key, value) { (_.has(result, key) ? result[key] : (result[key] = [])).push(value); }); // indexes the object's values by a criterion, similar to `groupby`, but for // when you know that your index values will be unique. _.indexby = group(function(result, key, value) { result[key] = value; }); // counts instances of an object that group by a certain criterion. pass // either a string attribute to count by, or a function that returns the // criterion. _.countby = group(function(result, key) { _.has(result, key) ? result[key]++ : result[key] = 1; }); // use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. uses binary search. _.sortedindex = function(array, obj, iterator, context) { iterator = iterator == null ? _.identity : lookupiterator(iterator); var value = iterator.call(context, obj); var low = 0, high = array.length; while (low < high) { var mid = (low + high) >>> 1; iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; } return low; }; // safely create a real, live array from anything iterable. _.toarray = function(obj) { if (!obj) return []; if (_.isarray(obj)) return slice.call(obj); if (obj.length === +obj.length) return _.map(obj, _.identity); return _.values(obj); }; // return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; }; // array functions // --------------- // get the first element of an array. passing **n** will return the first n // values in the array. aliased as `head` and `take`. the **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; return (n == null) || guard ? array[0] : slice.call(array, 0, n); }; // returns everything but the last entry of the array. especially useful on // the arguments object. passing **n** will return all the values in // the array, excluding the last n. the **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); }; // get the last element of an array. passing **n** will return the last n // values in the array. the **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { if (array == null) return void 0; if ((n == null) || guard) { return array[array.length - 1]; } else { return slice.call(array, math.max(array.length - n, 0)); } }; // returns everything but the first entry of the array. aliased as `tail` and `drop`. // especially useful on the arguments object. passing an **n** will return // the rest n values in the array. the **guard** // check allows it to work with `_.map`. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, (n == null) || guard ? 1 : n); }; // trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, output) { if (shallow && _.every(input, _.isarray)) { return concat.apply(output, input); } each(input, function(value) { if (_.isarray(value) || _.isarguments(value)) { shallow ? push.apply(output, value) : flatten(value, shallow, output); } else { output.push(value); } }); return output; }; // flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { return flatten(array, shallow, []); }; // return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // produce a duplicate-free version of the array. if the array has already // been sorted, you have the option of using a faster algorithm. // aliased as `unique`. _.uniq = _.unique = function(array, issorted, iterator, context) { if (_.isfunction(issorted)) { context = iterator; iterator = issorted; issorted = false; } var initial = iterator ? _.map(array, iterator, context) : array; var results = []; var seen = []; each(initial, function(value, index) { if (issorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { seen.push(value); results.push(array[index]); } }); return results; }; // produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(_.flatten(arguments, true)); }; // produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { return _.indexof(other, item) >= 0; }); }); }; // take the difference between one array and a number of other arrays. // only the elements present in just the first array will remain. _.difference = function(array) { var rest = concat.apply(arrayproto, slice.call(arguments, 1)); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { var length = _.max(_.pluck(arguments, "length").concat(0)); var results = new array(length); for (var i = 0; i < length; i++) { results[i] = _.pluck(arguments, '' + i); } return results; }; // converts lists into objects. pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { if (list == null) return {}; var result = {}; for (var i = 0, length = list.length; i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // if the browser doesn't supply us with indexof (i'm looking at you, **msie**), // we need this function. return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. // delegates to **ecmascript 5**'s native `indexof` if available. // if the array is large and already in sort order, pass `true` // for **issorted** to use binary search. _.indexof = function(array, item, issorted) { if (array == null) return -1; var i = 0, length = array.length; if (issorted) { if (typeof issorted == 'number') { i = (issorted < 0 ? math.max(0, length + issorted) : issorted); } else { i = _.sortedindex(array, item); return array[i] === item ? i : -1; } } if (nativeindexof && array.indexof === nativeindexof) return array.indexof(item, issorted); for (; i < length; i++) if (array[i] === item) return i; return -1; }; // delegates to **ecmascript 5**'s native `lastindexof` if available. _.lastindexof = function(array, item, from) { if (array == null) return -1; var hasindex = from != null; if (nativelastindexof && array.lastindexof === nativelastindexof) { return hasindex ? array.lastindexof(item, from) : array.lastindexof(item); } var i = (hasindex ? from : array.length); while (i--) if (array[i] === item) return i; return -1; }; // generate an integer array containing an arithmetic progression. a port of // the native python `range()` function. see // [the python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (arguments.length <= 1) { stop = start || 0; start = 0; } step = arguments[2] || 1; var length = math.max(math.ceil((stop - start) / step), 0); var idx = 0; var range = new array(length); while(idx < length) { range[idx++] = start; start += step; } return range; }; // function (ahem) functions // ------------------ // reusable constructor function for prototype setting. var ctor = function(){}; // create a function bound to a given object (assigning `this`, and arguments, // optionally). delegates to **ecmascript 5**'s native `function.bind` if // available. _.bind = function(func, context) { var args, bound; if (nativebind && func.bind === nativebind) return nativebind.apply(func, slice.call(arguments, 1)); if (!_.isfunction(func)) throw new typeerror; args = slice.call(arguments, 2); return bound = function() { if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; ctor.prototype = null; var result = func.apply(self, args.concat(slice.call(arguments))); if (object(result) === result) return result; return self; }; }; // partially apply a function by creating a version that has had some of its // arguments pre-filled, without changing its dynamic `this` context. _.partial = function(func) { var args = slice.call(arguments, 1); return function() { return func.apply(this, args.concat(slice.call(arguments))); }; }; // bind all of an object's methods to that object. useful for ensuring that // all callbacks defined on an object belong to it. _.bindall = function(obj) { var funcs = slice.call(arguments, 1); if (funcs.length === 0) throw new error("bindall must be passed function names"); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memo = {}; hasher || (hasher = _.identity); return function() { var key = hasher.apply(this, arguments); return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); }; }; // delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return settimeout(function(){ return func.apply(null, args); }, wait); }; // defers a function, scheduling it to run after the current call stack has // cleared. _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; // returns a function, that, when invoked, will only be triggered at most once // during a given window of time. normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. to disable execution on the trailing edge, ditto. _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; options || (options = {}); var later = function() { previous = options.leading === false ? 0 : new date; timeout = null; result = func.apply(context, args); }; return function() { var now = new date; if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { cleartimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); } else if (!timeout && options.trailing !== false) { timeout = settimeout(later, remaining); } return result; }; }; // returns a function, that, as long as it continues to be invoked, will not // be triggered. the function will be called after it stops being called for // n milliseconds. if `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; return function() { context = this; args = arguments; timestamp = new date(); var later = function() { var last = (new date()) - timestamp; if (last < wait) { timeout = settimeout(later, wait - last); } else { timeout = null; if (!immediate) result = func.apply(context, args); } }; var callnow = immediate && !timeout; if (!timeout) { timeout = settimeout(later, wait); } if (callnow) result = func.apply(context, args); return result; }; }; // returns a function that will be executed at most one time, no matter how // often you call it. useful for lazy initialization. _.once = function(func) { var ran = false, memo; return function() { if (ran) return memo; ran = true; memo = func.apply(this, arguments); func = null; return memo; }; }; // returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { var args = [func]; push.apply(args, arguments); return wrapper.apply(this, args); }; }; // returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var funcs = arguments; return function() { var args = arguments; for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } return args[0]; }; }; // returns a function that will only be executed after being called n times. _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // object functions // ---------------- // retrieve the names of an object's properties. // delegates to **ecmascript 5**'s native `object.keys` _.keys = nativekeys || function(obj) { if (obj !== object(obj)) throw new typeerror('invalid object'); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); return keys; }; // retrieve the values of an object's properties. _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; var values = new array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; // convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; var pairs = new array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } return pairs; }; // invert the keys and values of an object. the values must be serializable. _.invert = function(obj) { var result = {}; var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { result[obj[keys[i]]] = keys[i]; } return result; }; // return a sorted list of the function names available on the object. // aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isfunction(obj[key])) names.push(key); } return names.sort(); }; // extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { if (source) { for (var prop in source) { obj[prop] = source[prop]; } } }); return obj; }; // return a copy of the object only containing the whitelisted properties. _.pick = function(obj) { var copy = {}; var keys = concat.apply(arrayproto, slice.call(arguments, 1)); each(keys, function(key) { if (key in obj) copy[key] = obj[key]; }); return copy; }; // return a copy of the object without the blacklisted properties. _.omit = function(obj) { var copy = {}; var keys = concat.apply(arrayproto, slice.call(arguments, 1)); for (var key in obj) { if (!_.contains(keys, key)) copy[key] = obj[key]; } return copy; }; // fill in a given object with default properties. _.defaults = function(obj) { each(slice.call(arguments, 1), function(source) { if (source) { for (var prop in source) { if (obj[prop] === void 0) obj[prop] = source[prop]; } } }); return obj; }; // create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isobject(obj)) return obj; return _.isarray(obj) ? obj.slice() : _.extend({}, obj); }; // invokes interceptor with the obj, and then returns obj. // the primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // internal recursive comparison function for `isequal`. var eq = function(a, b, astack, bstack) { // identical objects are equal. `0 === -0`, but they aren't identical. // see the [harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a == 1 / b; // a strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // compare `[[class]]` names. var classname = tostring.call(a); if (classname != tostring.call(b)) return false; switch (classname) { // strings, numbers, dates, and booleans are compared by value. case '[object string]': // primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new string("5")`. return a == string(b); case '[object number]': // `nan`s are equivalent, but non-reflexive. an `egal` comparison is performed for // other numeric values. return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case '[object date]': case '[object boolean]': // coerce dates and booleans to numeric primitive values. dates are compared by their // millisecond representations. note that invalid dates with millisecond representations // of `nan` are not equivalent. return +a == +b; // regexps are compared by their source patterns and flags. case '[object regexp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignorecase == b.ignorecase; } if (typeof a != 'object' || typeof b != 'object') return false; // assume equality for cyclic structures. the algorithm for detecting cyclic // structures is adapted from es 5.1 section 15.12.3, abstract operation `jo`. var length = astack.length; while (length--) { // linear search. performance is inversely proportional to the number of // unique nested structures. if (astack[length] == a) return bstack[length] == b; } // objects with different constructors are not equivalent, but `object`s // from different frames are. var actor = a.constructor, bctor = b.constructor; if (actor !== bctor && !(_.isfunction(actor) && (actor instanceof actor) && _.isfunction(bctor) && (bctor instanceof bctor))) { return false; } // add the first object to the stack of traversed objects. astack.push(a); bstack.push(b); var size = 0, result = true; // recursively compare objects and arrays. if (classname == '[object array]') { // compare array lengths to determine if a deep comparison is necessary. size = a.length; result = size == b.length; if (result) { // deep compare the contents, ignoring non-numeric properties. while (size--) { if (!(result = eq(a[size], b[size], astack, bstack))) break; } } } else { // deep compare objects. for (var key in a) { if (_.has(a, key)) { // count the expected number of properties. size++; // deep compare each member. if (!(result = _.has(b, key) && eq(a[key], b[key], astack, bstack))) break; } } // ensure that both objects contain the same number of properties. if (result) { for (key in b) { if (_.has(b, key) && !(size--)) break; } result = !size; } } // remove the first object from the stack of traversed objects. astack.pop(); bstack.pop(); return result; }; // perform a deep comparison to check if two objects are equal. _.isequal = function(a, b) { return eq(a, b, [], []); }; // is a given array, string, or object empty? // an "empty" object has no enumerable own-properties. _.isempty = function(obj) { if (obj == null) return true; if (_.isarray(obj) || _.isstring(obj)) return obj.length === 0; for (var key in obj) if (_.has(obj, key)) return false; return true; }; // is a given value a dom element? _.iselement = function(obj) { return !!(obj && obj.nodetype === 1); }; // is a given value an array? // delegates to ecma5's native array.isarray _.isarray = nativeisarray || function(obj) { return tostring.call(obj) == '[object array]'; }; // is a given variable an object? _.isobject = function(obj) { return obj === object(obj); }; // add some istype methods: isarguments, isfunction, isstring, isnumber, isdate, isregexp. each(['arguments', 'function', 'string', 'number', 'date', 'regexp'], function(name) { _['is' + name] = function(obj) { return tostring.call(obj) == '[object ' + name + ']'; }; }); // define a fallback version of the method in browsers (ahem, ie), where // there isn't any inspectable "arguments" type. if (!_.isarguments(arguments)) { _.isarguments = function(obj) { return !!(obj && _.has(obj, 'callee')); }; } // optimize `isfunction` if appropriate. if (typeof (/./) !== 'function') { _.isfunction = function(obj) { return typeof obj === 'function'; }; } // is a given object a finite number? _.isfinite = function(obj) { return isfinite(obj) && !isnan(parsefloat(obj)); }; // is the given value `nan`? (nan is the only number which does not equal itself). _.isnan = function(obj) { return _.isnumber(obj) && obj != +obj; }; // is a given value a boolean? _.isboolean = function(obj) { return obj === true || obj === false || tostring.call(obj) == '[object boolean]'; }; // is a given value equal to null? _.isnull = function(obj) { return obj === null; }; // is a given variable undefined? _.isundefined = function(obj) { return obj === void 0; }; // shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return hasownproperty.call(obj, key); }; // utility functions // ----------------- // run underscore.js in *noconflict* mode, returning the `_` variable to its // previous owner. returns a reference to the underscore object. _.noconflict = function() { root._ = previousunderscore; return this; }; // keep the identity function around for default iterators. _.identity = function(value) { return value; }; // run a function **n** times. _.times = function(n, iterator, context) { var accum = array(math.max(0, n)); for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); return accum; }; // return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + math.floor(math.random() * (max - min + 1)); }; // list of html entities for escaping. var entitymap = { escape: { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } }; entitymap.unescape = _.invert(entitymap.escape); // regexes containing the keys and values listed immediately above. var entityregexes = { escape: new regexp('[' + _.keys(entitymap.escape).join('') + ']', 'g'), unescape: new regexp('(' + _.keys(entitymap.unescape).join('|') + ')', 'g') }; // functions for escaping and unescaping strings to/from html interpolation. _.each(['escape', 'unescape'], function(method) { _[method] = function(string) { if (string == null) return ''; return ('' + string).replace(entityregexes[method], function(match) { return entitymap[method][match]; }); }; }); // if the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. _.result = function(object, property) { if (object == null) return void 0; var value = object[property]; return _.isfunction(value) ? value.call(object) : value; }; // add your own custom functions to the underscore object. _.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 result.call(this, func.apply(_, args)); }; }); }; // generate a unique integer id (unique within the entire client session). // useful for temporary dom ids. var idcounter = 0; _.uniqueid = function(prefix) { var id = ++idcounter + ''; return prefix ? prefix + id : id; }; // by default, underscore uses erb-style template delimiters, change the // following template settings to use alternative delimiters. _.templatesettings = { evaluate : /<%([\s\s]+?)%>/g, interpolate : /<%=([\s\s]+?)%>/g, escape : /<%-([\s\s]+?)%>/g }; // when customizing `templatesettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var nomatch = /(.)^/; // certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; // javascript micro-templating, similar to john resig's implementation. // underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(text, data, settings) { var render; settings = _.defaults({}, settings, _.templatesettings); // combine delimiters into one regular expression via alternation. var matcher = new regexp([ (settings.escape || nomatch).source, (settings.interpolate || nomatch).source, (settings.evaluate || nomatch).source ].join('|') + '|$', 'g'); // compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset) .replace(escaper, function(match) { return '\\' + escapes[match]; }); if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } index = offset + match.length; return match; }); source += "';\n"; // if a variable is not specified, place data values in local scope. 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 { render = new function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; // provide the compiled function source as a convenience for precompilation. template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template; }; // add a "chain" function, which will delegate to the wrapper. _.chain = function(obj) { return _(obj).chain(); }; // oop // --------------- // if underscore is called as a function, it returns a wrapped object that // can be used oo-style. this wrapper holds altered versions of all the // underscore functions. wrapped objects may be chained. // helper function to continue chaining intermediate results. var result = function(obj) { return this._chain ? _(obj).chain() : obj; }; // add all of the underscore functions to the wrapper object. _.mixin(_); // add all mutator array functions to the wrapper. 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 result.call(this, obj); }; }); // add all accessor array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) { var method = arrayproto[name]; _.prototype[name] = function() { return result.call(this, method.apply(this._wrapped, arguments)); }; }); _.extend(_.prototype, { // start chaining a wrapped underscore object. chain: function() { this._chain = true; return this; }, // extracts the result from a wrapped and chained object. value: function() { return this._wrapped; } }); }).call(this); /* requestanimationframe polyfill */ 'use strict'; // adapted from https://gist.github.com/paulirish/1579671 which derived from // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestanimationframe polyfill by erik m枚ller. // fixes from paul irish, tino zijdel, andrew mao, klemen slavi膷, darius bacon // mit license if (!date.now) date.now = function() { return new date().gettime(); }; (function() { var vendors = ['webkit', 'moz']; for (var i = 0; i < vendors.length && !window.requestanimationframe; ++i) { var vp = vendors[i]; window.requestanimationframe = window[vp+'requestanimationframe']; window.cancelanimationframe = (window[vp+'cancelanimationframe'] || window[vp+'cancelrequestanimationframe']); } if (/ip(ad|hone|od).*os 6/.test(window.navigator.useragent) // ios6 is buggy || !window.requestanimationframe || !window.cancelanimationframe) { var lasttime = 0; window.requestanimationframe = function(callback) { var now = date.now(); var nexttime = math.max(lasttime + 16, now); return settimeout(function() { callback(lasttime = nexttime); }, nexttime - now); }; window.cancelanimationframe = cleartimeout; } }()); function assert(condition, message){ if (!!condition === false){ throw message || "assertion failed"; } } /* polyfill for classlist */ /*! @source http://purl.eligrey.com/github/classlist.js/blob/master/classlist.js*/ if(typeof document!=="undefined"&&!("classlist" in document.createelement("a"))){(function(j){if(!("htmlelement" in j)&&!("element" in j)){return}var a="classlist",f="prototype",m=(j.htmlelement||j.element)[f],b=object,k=string[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=array[f].indexof||function(q){var p=0,o=this.length;for(;p currenttime || seconds < currenttime) { currenttime = seconds; var callbacks = me.callbacks[seconds] || []; _.foreach(callbacks, function(cb){ cb(); }); } } })(), seek: (function(){ var animationstostart = function(me, seconds) { var tostart = []; for(var i = 0; i < me.timemodel.length; i++) { var animation = me.timemodel[i]; //stop looking, nothing else is running if (animation.startsat > seconds) { break; } if (animation.endsat > seconds) { tostart.push(animation); } } return tostart; }; /* seek function */ return function(videotime, playnow){ // 1. go through each to start //2. set the animation delay so it starts at the right place //3. start 'em up. var me = this, seconds = roundtime(videotime), tostart = animationstostart(me, seconds); // go through each animation to start _.foreach(tostart, function(animation){ //set the delay to start the animation at the right place setdelay(animation, seconds); //start it up animation.start(); /* if the move is playing right now, then let the animation * keep playing, otherwise pause the animation to wait * until the video resumes. */ if (playnow) { me.running.push(animation); } else { me.paused.push(animation); animation.pause(); } }); } })(), pauseanimations: function(){ var me = this, animation; while(animation = me.running.pop()){ animation.pause(); //keep track of paused animations so we can resume them later ... me.paused.push(animation); } }, clearanimations: function(){ var me = this, animation; /* need to be playing in order * to cause a reflow, otherwise * the offset fix in the reset method * of the animation class has no effect. */ me.resumeanimations(); while(animation = me.running.pop()){ animation.reset(); } while(animation = me.paused.pop()){ animation.reset(); } }, resumeanimations: function(){ var me = this, animation; while (animation = me.paused.pop()){ animation.resume(); me.running.push(animation); } }, bind: (function() { var createanimations = function(me, cssanimations, starttimes, callbacks){ _.foreach(_.keys(starttimes), function(name){ var keyframe = cssanimations.keyframes[name], cssrule = cssanimations.cssrules[name]; _.foreach(starttimes[name], function(starttime){ var animation = new animation( name, cssrule, keyframe, starttime.element, starttime.time); me.animations[name] = me.animations[name] || []; me.byseconds[animation.startsat] = me.byseconds[animation.startsat] || []; me.animations[name].push(animation); me.byseconds[animation.startsat].push(animation); }); }); }, createtimemodel = function(me, animations) { me.timemodel = _.sortby(animations, "endsat" ); }; /* the animationcontroller bind method */ return function(cssanimations, starttimes){ var me = this; createanimations(me, cssanimations, starttimes); var animations = _.flatten(_.values(me.animations)); createtimemodel(me, animations); me.callbacks = callbacks; } })()/* returns the bind method*/ } charlie.animationcontroller = animationcontroller; /************************************************************************ * animation */ var animation = function(name, cssrule, keyframe, element, startsat){ assert(name, "you can't create an animation without a name"); assert(cssrule, "no css rule defined for animation " + name); assert(keyframe, "no keyframe defined for animation " + name); assert(element, "no element found. animations must be bound to a dom element."); assert(startsat, "no start time provided for the animation"); this.name = name; this.element = element; this.cssrule = cssrule; this.keyframe = keyframe; this.startsat = roundtime(number(startsat)); this.duration = calculatedduration(cssrule.style); this.endsat = this.startsat + this.duration; }; animation.prototype = { name: "", element: null, cssrule: null, keyframe: null, startsat: -1, duration: -1, endsat: -1, start: function(){ var me = this; //the name of the animation is the same as the class name by convention. me.element.classlist.add(me.name); onanimationend(me.element, function(){ me.reset(); }); }, reset: function(){ this.element.classlist.remove(this.name); // cause a reflow, otherwise the animation isn't fully // removed. (let's call this a browser bug). this.element.offsetwidth = this.element.offsetwidth; //reset any calculated animation delays. setdelay(this, 0); }, pause: function(){ this.element.style.webkitanimationplaystate = "paused"; this.element.style.mozanimationplaystate = "paused"; this.element.style.oanimationplaystate = "paused"; this.element.style.animationplaystate = "paused"; }, resume: function(){ this.element.style.webkitanimationplaystate = "running"; this.element.style.mozanimationplaystate = "running"; this.element.style.oanimationplaystate = "running"; this.element.style.animationplaystate = "running"; } } charlie.animation = animation; /************************************************************************ * bigloop */ var bigloop = function(controller){ assert(controller, "can't create a bigloop without an animationcontroller"); this.controller = controller; }; bigloop.prototype = { controller: null, video: null, running: false, frameid: -1, bind: function(video){ //start and stop the loop when the video //starts and stops this.video = video; video.addeventlistener("play", this.start.bind(this), false); video.addeventlistener("ended", this.ended.bind(this), false); video.addeventlistener("pause", this.stop.bind(this), false); video.addeventlistener("seeked", this.seeked.bind(this), false); }, ended: function(){ this.controller.clearanimations(); }, seeked: function(){ this.controller.clearanimations(); this.controller.seek(video.currenttime, !this.video.paused); }, tick: function(time){ if (this.running){ this.frameid = requestanimationframe(this.tick.bind(this)); this.controller.startanimations(time, this.video.currenttime); this.controller.executecallbacks(time, this.video.currenttime); } }, start: function() { this.running = true; this.tick(); }, stop: function(){ if (this.frameid){ cancelanimationframe(this.frameid); this.frameid = -1; } this.running = false; this.controller.pauseanimations(); } } var callbacks = {}; charlie.setup = function(video){ var cssanimations = cssanimations.create(), animationdata = scrapeanimationdata(), controller = new animationcontroller(), loop = new bigloop(controller); controller.bind(cssanimations, animationdata, callbacks); loop.bind(video); } charlie.addcallback = function(callback, time){ time = roundtime(time); var cbs = callbacks[time] || []; cbs.push(callback); callbacks[time] = cbs; } /* 浠g爜鏁寸悊锛氭噿浜轰箣瀹?www.lanrenzhijia.com */ })();