├── README.md └── underscore-1.8.3 └── underscore.js /README.md: -------------------------------------------------------------------------------- 1 | # underscore 源码阅读 2 | 3 | 希望通过阅读 underscore 这一工具库的源码,巩固自己的 javascript 功力,如果觉得我写得好, 欢迎给我一个 star。你的关注是我持续写作的最大动力。 4 | 5 | ## Source Code 6 | 7 | [添加注释的 underscore 源码 -- 1.8.3](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/blob/master/underscore-1.8.3/underscore.js) 8 | 9 | ## Article List 10 | 11 | [root 对象初始化](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/1) 12 | 13 | [underscore 中的变量缓存与继承](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/2) 14 | 15 | [硬绑定代码性能优化](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/3) 16 | 17 | [解读 cb 函数](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/4) 18 | 19 | [模拟 ES6 rest 运算符](https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/5) 20 | 21 | [Javascript 中的数据类型判断](https://github.com/zhangxiang958/underscore-analysis/issues/6) 22 | 23 | ## Licence 24 | MIT License 25 | 26 | Copyright (c) 2017 Shawn Cheung 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 29 | 30 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | 35 | ## Concat 36 | Mail [958033967@qq.com](mailto 958033967@qq.com) 37 | 38 | Blog zhangxiang958.github.io 39 | -------------------------------------------------------------------------------- /underscore-1.8.3/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.8.3 2 | // http://underscorejs.org 3 | // (c) 2009-2017 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // 基础设置 9 | // -------------- 10 | 11 | // Establish the root object, `window` (`self`) in the browser, `global` 12 | // on the server, or `this` in some virtual machines. We use `self` 13 | // instead of `window` for `WebWorker` support. 14 | // self 是 window.self, self 属性是对窗口的自身的只读引用, 15 | // 经常会使用 self 来代替 window 对象. 在防 HTTP 劫持的时候就会使用 window.top 是不是等于 window.self 来判断是不是被劫持了. 16 | // 第一行的意思就是判断代码是不是在客户端下运行也就是 window 对象里面, 如果 self 是一个对象而且 self.self === self 的话那么就说明是在浏览器端 17 | // global 是 node 环境下的全局对象的引用, 类似于 self, global.global === global 的话就说明是 node 环境下 18 | // 如果都不是那么就是在其他的一些 js 环境里面比如 web worker, 你可以在浏览器里面试试 19 | // 详细请看: https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/1 20 | var root = typeof self == 'object' && self.self === self && self || 21 | typeof global == 'object' && global.global === global && global || 22 | this || 23 | {}; 24 | 25 | // Save the previous value of the `_` variable. 26 | // 将 underscore 对象缓存起来 27 | var previousUnderscore = root._; 28 | 29 | // Save bytes in the minified (but not gzipped) version: 30 | // 31 | var ArrayProto = Array.prototype, ObjProto = Object.prototype; 32 | var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; 33 | 34 | // Create quick reference variables for speed access to core prototypes. 35 | // 将常用的数组或对象方法缓存起来, 提高读取速度. 36 | var push = ArrayProto.push, 37 | slice = ArrayProto.slice, 38 | toString = ObjProto.toString, 39 | hasOwnProperty = ObjProto.hasOwnProperty; 40 | 41 | // All **ECMAScript 5** native function implementations that we hope to use 42 | // are declared here. 43 | // 将一些常用的原生方法缓存起来, 可以方便压缩和提高读取速度. 44 | // Array.isArray 方法其实就是我们常用判断是不是数组的方法, 不过我自己常用的是 Object.prototype.toString.call() 这个方法 判断值是不是 [object Array], 如果是就是数组对象, 实际上 Array.isArray 的 polyfill 也是这么做的. 45 | // Object.keys 是返回对象的所有键名数组, 这样就可以直接时候迭代器, 而不需要 for in 循环了. 46 | // Object.create 方法是常用于原型继承的, 它返回一个新的对象, 这个对象和输入的对象已经进行了 原型链中的原型指针的连接(proto). 47 | var nativeIsArray = Array.isArray, 48 | nativeKeys = Object.keys, 49 | nativeCreate = Object.create; 50 | 51 | // Naked function reference for surrogate-prototype-swapping. 52 | // 创建一个 "干净" 的构造函数用于实现优化后的组合继承. 53 | var Ctor = function(){}; 54 | 55 | // Create a safe reference to the Underscore object for use below. 56 | // 这里是创建了 underscore 对象.这个函数的目的其实就是为了创建 underscore 对象, 如果在 new 命令的时候有对象传进来, 那么就将这个对象传给新建对象的 _wrapped 属性存储起来. 57 | var _ = function(obj) { 58 | if (obj instanceof _) return obj; 59 | if (!(this instanceof _)) return new _(obj); 60 | this._wrapped = obj; 61 | }; 62 | 63 | // Export the Underscore object for **Node.js**, with 64 | // backwards-compatibility for their old module API. If we're in 65 | // the browser, add `_` as a global object. 66 | // (`nodeType` is checked to ensure that `module` 67 | // and `exports` are not HTML elements.) 68 | // 这段代码如何理解呢? 69 | // 这个是前端与 node.js 通用的模块封装模式, 70 | // 我们知道 exports 和 module.exports 是 node 模块化的显著标志, 71 | // 第一个先判断 exports 是否存在, 还有就是判断 exports 变量是否有 nodeType 属性, 72 | // 如果有, 说明 exports 变量是一个 html 元素, 所以不使用 node 方式模块加载, 73 | // 同理 module 变量也是这样.这里不再细讲 exports 和 module.exports 的区别, 74 | // 简单总结就是 exports 看作是 module.exports 的一个快照. 75 | if (typeof exports != 'undefined' && !exports.nodeType) { 76 | if (typeof module != 'undefined' && !module.nodeType && module.exports) { 77 | exports = module.exports = _; 78 | } 79 | exports._ = _; 80 | } else { 81 | root._ = _; 82 | } 83 | 84 | // Current version. 85 | // 当前版本为 1.8.3 86 | _.VERSION = '1.8.3'; 87 | 88 | // Internal function that returns an efficient (for current engines) version 89 | // of the passed-in callback, to be repeatedly applied in other Underscore 90 | // functions. 91 | // 一个内部动态实现绑定 this 的函数. 92 | // 这个函数是什么意思?从整体上看并没有太大的意思,我们知道 this 的指向是动态的, 93 | // 所以在实际开发中肯定免不了对函数的 this 值进行硬绑定的做法,但是 bind 函数会有兼容性问题, 94 | // 所以会倾向于使用 call 方法和 apply 方法,这两个原生函数在使用的时候区别就在于 call 后面是可以跟随 n 的参数, 95 | // 而 apply 后面是跟随数组形式的参数的, 那为什么 underscore 源码需要将这两种方法区分呢? 96 | // 可以看到 optimizeCb 函数会将传递参数少于或等于 4 个的采用 call 方法来绑定 this, 97 | // 而对于多于 4 个参数的方法则会采用 apply 方法来进行绑定,其实这是代码性能优化的手段, 98 | // apply 方法在执行的时候其实是比 call 方法要慢得多, apply 方法在执行的时候需要对传进来的参数数组 进行深拷贝:apply 内部执行伪代码 99 | // 请参考文章: https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/3 100 | var optimizeCb = function(func, context, argCount) { 101 | if (context === void 0) return func; 102 | switch (argCount) { 103 | case 1: return function(value) { 104 | return func.call(context, value); 105 | }; 106 | // The 2-parameter case has been omitted only because no current consumers 107 | // made use of it. 108 | case null: 109 | case 3: return function(value, index, collection) { 110 | return func.call(context, value, index, collection); 111 | }; 112 | case 4: return function(accumulator, value, index, collection) { 113 | return func.call(context, accumulator, value, index, collection); 114 | }; 115 | } 116 | return function() { 117 | return func.apply(context, arguments); 118 | }; 119 | }; 120 | 121 | var builtinIteratee; 122 | 123 | // An internal function to generate callbacks that can be applied to each 124 | // element in a collection, returning the desired result — either `identity`, 125 | // an arbitrary callback, a property matcher, or a property accessor. 126 | // cb 函数到底是做什么的? cb 函数是一个内部方法, 一下子可能看不出来它的功能, 但是我们可以借助 _.iteratee 函数来看, 127 | var cb = function(value, context, argCount) { 128 | if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); 129 | if (value == null) return _.identity; 130 | if (_.isFunction(value)) return optimizeCb(value, context, argCount); 131 | if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); 132 | return _.property(value); 133 | }; 134 | 135 | // External wrapper for our callback generator. Users may customize 136 | // `_.iteratee` if they want additional predicate/iteratee shorthand styles. 137 | // This abstraction hides the internal-only argCount argument. 138 | // 根据文档 "_.iteratee 函数是用来生成可以应用到集合中每个元素的回调的, 返回想要的结果", 139 | // 而 _.iteratee 函数主要是用在了 _.each, _.map, _.find, _.filter 等等的这些需要回调的函数的. 140 | _.iteratee = builtinIteratee = function(value, context) { 141 | return cb(value, context, Infinity); 142 | }; 143 | 144 | // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html) 145 | // This accumulates the arguments passed into an array, after a given index. 146 | // 这个函数是用来模仿 ES6 语法中的拓展运算符的. 147 | // items 代表的就是除了第一个定义的形参 array 之外, 剩余的其他参数都变成了 items 的一部分, 148 | // 而 items 会将剩余的所有参数集中起来, 放入一个数组里面, 所以 items 本身 就是一个数组, 149 | // 里面按顺序存放了除 array 之外的传进来的参数. 那么我们在没有 ES6 语法的情况下, 就需要使用 arguments 对象, 150 | // 将传进来的没有对应形参名的参数放入到一个数组里面, 所以我们当然需要知道函数本来已经定义了多少个已经命名了的形参的数量, 151 | // 假如原来函数已经定义了 2 个参数, 那么我们就从 arguments 的第三个参数也就是 arguments 转化后得到的数组的下标为 2 的元素开始放入到 rest 数组中. 152 | // 这也就是 startIndex 的意义. 然后通过一个闭包, 缓存起 startIndex 的值, 然后将模拟 rest 数组的生成: 153 | // https://github.com/zhangxiang958/underscore-sourcecodeAnalysis/issues/5 154 | // 为什么要使用这样一个 restArg 函数呢? 因为如果不使用这样的函数模拟 rest 运算符, 而是在每个函数调用 [].slice.call(arguments); 的话, 这样代码就耦合了 155 | // 而且代码复用性不高. 156 | var restArgs = function(func, startIndex) { 157 | startIndex = startIndex == null ? func.length - 1 : +startIndex; 158 | // 闭包缓存 159 | return function() { 160 | var length = Math.max(arguments.length - startIndex, 0), 161 | rest = Array(length), 162 | index = 0; 163 | for (; index < length; index++) { 164 | rest[index] = arguments[index + startIndex]; 165 | } 166 | // 下面这里就是针对参数个数的多少进行 apply 和 call 的优化. 167 | switch (startIndex) { 168 | case 0: return func.call(this, rest); 169 | case 1: return func.call(this, arguments[0], rest); 170 | case 2: return func.call(this, arguments[0], arguments[1], rest); 171 | } 172 | var args = Array(startIndex + 1); 173 | for (index = 0; index < startIndex; index++) { 174 | args[index] = arguments[index]; 175 | } 176 | args[startIndex] = rest; 177 | return func.apply(this, args); 178 | }; 179 | }; 180 | 181 | // An internal function for creating a new object that inherits from another. 182 | // 内部函数, 用于实现继承(组合继承) 183 | var baseCreate = function(prototype) { 184 | if (!_.isObject(prototype)) return {}; 185 | if (nativeCreate) return nativeCreate(prototype); 186 | Ctor.prototype = prototype; 187 | var result = new Ctor; 188 | Ctor.prototype = null; 189 | return result; 190 | }; 191 | 192 | 193 | // shallowProperty 函数是用来获取对象对应键名的值的, 194 | var shallowProperty = function(key) { 195 | return function(obj) { 196 | return obj == null ? void 0 : obj[key]; 197 | }; 198 | }; 199 | // deepGet 函数则主要是在 property 或者 propertyOf 这两个根据键名获取键值的函数中, 200 | // 当输入的键名值为数组类型的时候, 采用遍历的方式 取值, 它是一个内部的工具函数. 201 | var deepGet = function(obj, path) { 202 | var length = path.length; 203 | for (var i = 0; i < length; i++) { 204 | if (obj == null) return void 0; 205 | obj = obj[path[i]]; 206 | } 207 | return length ? obj : void 0; 208 | }; 209 | 210 | // Helper for collection methods to determine whether a collection 211 | // should be iterated as an array or as an object. 212 | // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength 213 | // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 214 | // 下面这里都是一些集合类函数的辅助函数. 215 | // getLength 是一个获取对象 length 属性的工具方法, 而 MAX_ARRAY_INDEX 为 2 的 53 次方减 1, 216 | // 而这个是 javascript 中能表示的最大整数值. 217 | var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 218 | var getLength = shallowProperty('length'); 219 | // isArrayLike 是为了判断一个对象是不是类数组对象 220 | // 什么是类数组对象? 像我们常见的 arguments 对象, DOM 中的 NodeList 对象等等这些都是类数组对象, 221 | // 他们有 length 属性并且表示的是对象 拥有的元素的数量, 但是却没有数组的某些方法像 forEach, map 等等. 222 | // 这也就是所谓的鸭子模型, 所以我们在使用 underscore 集合函数的时候要避免传入拥有 length 属性的对象, 否则会被当作数组使用. 223 | var isArrayLike = function(collection) { 224 | var length = getLength(collection); 225 | return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; 226 | }; 227 | 228 | // Collection Functions 229 | // -------------------- 230 | 231 | // The cornerstone, an `each` implementation, aka `forEach`. 232 | // Handles raw objects in addition to array-likes. Treats all 233 | // sparse array-likes as if they were dense. 234 | // each 函数中 obj 就是你需要遍历的那个对象, 235 | // iteratee 是你定义的处理数据回调函数, context 是执行上下文, 回调函数的 this 值将会是 context. 236 | // 而对于第一句 iteratee = optimizeCb(iteratee, context);, 就是之前所说的视命名参数数量而使用 call 或 apply 动态生成回调的函数, 这里函数式编程的优势就体现了, 每个函数只负责单一的功能, 组合起来威力很大. 237 | // 然后对传进来的对象进行类型判断, 如果是类数组类型(有 length 属性, 并且是一个大于 1 小于 2 的 23 次方减 1 的数), 238 | // 那么就使用下标的形式也就是方括号的形式来遍历对象. 239 | // 如果不是, 那么就使用 _.keys 函数, 将对象的键名提取出来, 并返回键名数组, 然后根据键名数组中的键名, 逐个提取 obj 对象中的键值, 进行遍历. 240 | _.each = _.forEach = function(obj, iteratee, context) { 241 | iteratee = optimizeCb(iteratee, context); 242 | var i, length; 243 | if (isArrayLike(obj)) { 244 | for (i = 0, length = obj.length; i < length; i++) { 245 | iteratee(obj[i], i, obj); 246 | } 247 | } else { 248 | // 不知道大家在对象遍历的时候有没有疑问, 我在看的时候, 我觉得 _.keys 非常奇怪, 249 | // 为什么需要先提取键名数组, 使用 for...in 循环然后使用 hasOwnProperty 来判断对象是否拥有该键不可以吗? 250 | // 其实这里作者非常巧妙, 第一将获取键名的函数独立出去, 有利于其他函数的复用, 而且这样显得非常简洁. 251 | // 第二 keys 数组如果能用原生的 Object.keys 当然最好, 但是会有兼容性问题, 所以需要使用 for...in 循环来兼容, 252 | // 所以不将 for...in 循环不写在 each 函数里面可以减低耦合度, 优化代码性能(for..in 不是必需执行的循环). 253 | // 最后一点在于 IE9 的兼容问题, 在低于 IE9 的浏览器版本里面, 当对象中含有像 toString, valueOf, isPropertyOf, propertyIsEnumerable, hasOwnProperty, toLocaleString 这些属性名的键值对时, 254 | // 这些属性是无法使用 for...in 循环遍历出来的. 也就是 collectNonEnumProps 函数里面所做的内容. 255 | var keys = _.keys(obj); 256 | for (i = 0, length = keys.length; i < length; i++) { 257 | iteratee(obj[keys[i]], keys[i], obj); 258 | } 259 | } 260 | return obj; 261 | }; 262 | 263 | // Return the results of applying the iteratee to each element. 264 | // _.each 和 _.map 类似的, 只不过 _.map 会返回一个使用回调函数处理过的集合数据.这里有点奇怪, 265 | // 明明 _.each 处理逻辑与 _.map 处理逻辑类似的, 为什么 _.map 的代码写得这么简洁? 266 | // 这一点还不知道作者是出于什么考虑, 如果是我肯定是两个都是第二种写法. 还有一点疑问就是为什么 _.each 函数和 _.map 函数在生成回调的时候, 267 | // 一个采用 optimizeCb, 一个采用 cb 函数? 268 | _.map = _.collect = function(obj, iteratee, context) { 269 | iteratee = cb(iteratee, context); 270 | var keys = !isArrayLike(obj) && _.keys(obj), 271 | length = (keys || obj).length, 272 | results = Array(length); 273 | for (var index = 0; index < length; index++) { 274 | var currentKey = keys ? keys[index] : index; 275 | results[index] = iteratee(obj[currentKey], currentKey, obj); 276 | } 277 | return results; 278 | }; 279 | 280 | // Create a reducing function iterating left or right. 281 | // 如果是正常的 reduce 的话, 那么就是将输入的集合从左到右集合成一个数字, 282 | // 而对于 reduceRight 就是从右边开始, 从字面来看, 这两个 API 会有很大的相似点, 而在实际编写的时候也确实是这样的. 283 | // 作者先用一个 reducer 来构造 reduce 函数, 然后通过一个 flag 值来确定合并的方向, 也就是使用 dir, 284 | // 当 dir 为 1, 那么就是从左边开始, 当 dir 为 -1, 那么就是从右边开始. 285 | // 对于 createReduce 这个函数来说, 其实所做的时候就是通过闭包, 确定集合的方向, 然后还有调用 reducer 这个核心的方法. 286 | var createReduce = function(dir) { 287 | // Wrap code that reassigns argument variables in a separate function than 288 | // the one that accesses `arguments.length` to avoid a perf hit. (#1991) 289 | var reducer = function(obj, iteratee, memo, initial) { 290 | var keys = !isArrayLike(obj) && _.keys(obj), 291 | length = (keys || obj).length, 292 | index = dir > 0 ? 0 : length - 1; 293 | if (!initial) { 294 | memo = obj[keys ? keys[index] : index]; 295 | index += dir; 296 | } 297 | for (; index >= 0 && index < length; index += dir) { 298 | var currentKey = keys ? keys[index] : index; 299 | memo = iteratee(memo, obj[currentKey], currentKey, obj); 300 | } 301 | return memo; 302 | }; 303 | 304 | return function(obj, iteratee, memo, context) { 305 | // 先用 initial 判断形参有多少个, 306 | var initial = arguments.length >= 3; 307 | // 然后进行 optimizeCb 回调函数包装优化. 回调是有 4 个参数的, 分别是 memo, value, index(key), list. memo 就是初始值, 308 | // list 里面的 item 值就会逐个相加到 memo 里面. 对于 reducer 这个函数: 309 | return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); 310 | }; 311 | }; 312 | 313 | // **Reduce** builds up a single result from a list of values, aka `inject`, 314 | // or `foldl`. 315 | // 从左边开始将列表中的数集合为一个单一的数 316 | _.reduce = _.foldl = _.inject = createReduce(1); 317 | 318 | // The right-associative version of reduce, also known as `foldr`. 319 | // 从右边开始将列表中的数集合为一个单一的数 320 | _.reduceRight = _.foldr = createReduce(-1); 321 | 322 | // Return the first value which passes a truth test. Aliased as `detect`. 323 | // find 函数的作用在于说在列表中找到符合回调函数条件的元素, 一旦找到, 马上返回, 不会遍历整个列表. 324 | // 理解 find 先需要看 findIndex 和 findKey 这两个函数. 325 | _.find = _.detect = function(obj, predicate, context) { 326 | var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey; 327 | var key = keyFinder(obj, predicate, context); 328 | if (key !== void 0 && key !== -1) return obj[key]; 329 | }; 330 | 331 | // Return all the elements that pass a truth test. 332 | // Aliased as `select`. 333 | // _.filter 就是通过调用回调, 然后过滤出符合条件的元素, 然后加入到 result 数组中, 然后最终得到这个数组. 334 | _.filter = _.select = function(obj, predicate, context) { 335 | var results = []; 336 | predicate = cb(predicate, context); 337 | _.each(obj, function(value, index, list) { 338 | if (predicate(value, index, list)) results.push(value); 339 | }); 340 | return results; 341 | }; 342 | 343 | // Return all the elements for which a truth test fails. 344 | // 所谓 _.reject 就是 filter 的反函数, 它是过滤出不符合条件的元素, 然后将不符合 条件的元素加入到新数组中, 最终得到这个数组. 345 | _.reject = function(obj, predicate, context) { 346 | return _.filter(obj, _.negate(cb(predicate)), context); 347 | }; 348 | 349 | // Determine whether all of the elements match a truth test. 350 | // Aliased as `all`. 351 | // every 函数就是为了让集合中每个 item 都能使用回调来执行, 然后如果回调都是返回 true, 则最后返回 true, 352 | // 一旦有一个 item 执行回调之后返回 false, 那么 every 返回的也是 false. 也就是验证每个 item 是否都符合规则. 353 | _.every = _.all = function(obj, predicate, context) { 354 | predicate = cb(predicate, context); 355 | var keys = !isArrayLike(obj) && _.keys(obj), 356 | length = (keys || obj).length; 357 | for (var index = 0; index < length; index++) { 358 | var currentKey = keys ? keys[index] : index; 359 | if (!predicate(obj[currentKey], currentKey, obj)) return false; 360 | } 361 | return true; 362 | }; 363 | 364 | // Determine if at least one element in the object matches a truth test. 365 | // Aliased as `any`. 366 | // 而对于 some 就是监测集合中是否存在符合规则的, 如果一旦找到一个, 那么就结束循环然后返回 true, 否则就返回 false. 367 | _.some = _.any = function(obj, predicate, context) { 368 | predicate = cb(predicate, context); 369 | var keys = !isArrayLike(obj) && _.keys(obj), 370 | length = (keys || obj).length; 371 | for (var index = 0; index < length; index++) { 372 | var currentKey = keys ? keys[index] : index; 373 | if (predicate(obj[currentKey], currentKey, obj)) return true; 374 | } 375 | return false; 376 | }; 377 | 378 | // Determine if the array or object contains a given item (using `===`). 379 | // Aliased as `includes` and `include`. 380 | // 看集合中是否包含特定那个值. _.values 函数就是通过先获取对象的 key 数组(_.keys) ,然后遍历对象, 将对应键值放到数组中然后返回. 381 | // 然后看 indexOf 有没有找到列表有有对应的项. 382 | // indexOf 内部也比较有意思, 我已经在源码中注释了, 这里总得来说就是如果 fromIndex 传的是数字, 那么就从 fromIndex 开始查找. 如果传的是 true, 那么就说明是排序数组, 将采用二分查找来 383 | // 查找数组. 384 | // 如果没有传 fromIndex 或者 fromIndex 不需要特殊处理(true 与数字需要特殊处理), 那么就用 === 来做判断 385 | _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { 386 | if (!isArrayLike(obj)) obj = _.values(obj); 387 | if (typeof fromIndex != 'number' || guard) fromIndex = 0; 388 | return _.indexOf(obj, item, fromIndex) >= 0; 389 | }; 390 | 391 | // Invoke a method (with arguments) on every item in a collection. 392 | // 使传入的集合执行对应的方法, 并返回一个处理后的数组, 你可以将 invoke 方法看作是一个特殊用的 map 方法 393 | _.invoke = restArgs(function(obj, path, args) { 394 | var contextPath, func; 395 | if (_.isFunction(path)) { 396 | // 如果 path 传入一个函数 397 | func = path; 398 | } else if (_.isArray(path)) { 399 | // 如果 path 是一个数组 400 | //contextPath 取数组的除最后一个元素外的元素组成的数组(slice 不会影响原数组) 401 | contextPath = path.slice(0, -1); 402 | // path 取原数组最后一个元素 403 | path = path[path.length - 1]; 404 | } 405 | return _.map(obj, function(context) { 406 | var method = func; 407 | // 如果 method 为空 408 | if (!method) { 409 | // 如果有 contextPath 410 | if (contextPath && contextPath.length) { 411 | // 取 path 数组中最后一个键名对应的 context, 利用这个 context ( path 属性)来做处理而不是整个 context 412 | context = deepGet(context, contextPath); 413 | } 414 | if (context == null) return void 0; 415 | // 这句代码花了一点时间来读, 这里的 path 可以传字符串, 就是因为考虑到 item 可能是原生对象或者数组, 那么通过方括号就可以获取原生方法了 416 | // 例如: _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); 调用了数组的 sort 方法 417 | method = context[path]; 418 | } 419 | // 如果 method 没有赋值, 那么返回空 420 | // 如果有则返回 method 函数执行结果 421 | return method == null ? method : method.apply(context, args); 422 | }); 423 | }); 424 | 425 | // Convenience version of a common use case of `map`: fetching a property. 426 | // pluck 是 map 的简化版, 也就是将数组中对应的键名的值取出来放到一个数组中. 427 | _.pluck = function(obj, key) { 428 | return _.map(obj, _.property(key)); 429 | }; 430 | 431 | // Convenience version of a common use case of `filter`: selecting only objects 432 | // containing specific `key:value` pairs. 433 | // where 是 filter 的特殊用法, 回调使用的是 _.matcher, 筛选出包含 attr 的对象. 434 | _.where = function(obj, attrs) { 435 | return _.filter(obj, _.matcher(attrs)); 436 | }; 437 | 438 | // Convenience version of a common use case of `find`: getting the first object 439 | // containing specific `key:value` pairs. 440 | // find 的特殊用法, 找到 obj 中第一个包含 attr 的对象. 试想一下如果列表中的 item 是对象, 那么你需要很多步骤例如遍历等等, findWhere 明显提供了一个简单的 API. 441 | _.findWhere = function(obj, attrs) { 442 | return _.find(obj, _.matcher(attrs)); 443 | }; 444 | 445 | // Return the maximum element (or element-based computation). 446 | // 返回最大值 447 | _.max = function(obj, iteratee, context) { 448 | // 先记录最后 result 为最小值 449 | var result = -Infinity, lastComputed = -Infinity, 450 | value, computed; 451 | // 如果缺失回调函数或者 iteratee 是一个数字并且 obj 列表不是一个对象集合 452 | if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { 453 | // 获取 obj 中的值的集合 454 | obj = isArrayLike(obj) ? obj : _.values(obj); 455 | //遍历进行比较 456 | for (var i = 0, length = obj.length; i < length; i++) { 457 | value = obj[i]; 458 | if (value != null && value > result) { 459 | result = value; 460 | } 461 | } 462 | } else { 463 | //如果没有缺失回调, 先对回调函数进行优化包装 464 | iteratee = cb(iteratee, context); 465 | //遍历 obj 466 | _.each(obj, function(v, index, list) { 467 | // 使用 computed 缓存起 return 的值 468 | computed = iteratee(v, index, list); 469 | //如果 computed 值比 lastComputed 大, 那么 computed 就是较大值, 进行替换 470 | if (computed > lastComputed || computed === -Infinity && result === -Infinity) { 471 | // 这里用 result 是因为 item 不一定是数字类型, 可能是对象, 只是使用对象中的某个属性值进行比较大小 472 | result = v; 473 | lastComputed = computed; 474 | } 475 | }); 476 | } 477 | return result; 478 | }; 479 | 480 | // Return the minimum element (or element-based computation). 481 | // 返回最小值 482 | _.min = function(obj, iteratee, context) { 483 | // 先记录最后 result 为最大值 484 | var result = Infinity, lastComputed = Infinity, 485 | value, computed; 486 | 487 | if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { 488 | obj = isArrayLike(obj) ? obj : _.values(obj); 489 | for (var i = 0, length = obj.length; i < length; i++) { 490 | value = obj[i]; 491 | if (value != null && value < result) { 492 | result = value; 493 | } 494 | } 495 | } else { 496 | //如果没有缺失回调, 先对回调函数进行优化包装 497 | iteratee = cb(iteratee, context); 498 | _.each(obj, function(v, index, list) { 499 | // 使用 computed 缓存起 return 的值 500 | computed = iteratee(v, index, list); 501 | //如果 computed 值比 lastComputed 大, 那么 computed 就是较大值, 进行替换 502 | if (computed < lastComputed || computed === Infinity && result === Infinity) { 503 | // 这里用 result 是因为 item 不一定是数字类型, 可能是对象, 只是使用对象中的某个属性值进行比较大小 504 | result = v; 505 | lastComputed = computed; 506 | } 507 | }); 508 | } 509 | return result; 510 | }; 511 | 512 | // Shuffle a collection. 513 | // 将传入的 obj 进行乱序 514 | _.shuffle = function(obj) { 515 | return _.sample(obj, Infinity); 516 | }; 517 | 518 | // Sample **n** random values from a collection using the modern version of the 519 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 520 | // If **n** is not specified, returns a single random element. 521 | // The internal `guard` argument allows it to work with `map`. 522 | // 523 | _.sample = function(obj, n, guard) { 524 | if (n == null || guard) { 525 | if (!isArrayLike(obj)) obj = _.values(obj); 526 | return obj[_.random(obj.length - 1)]; 527 | } 528 | var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj); 529 | var length = getLength(sample); 530 | n = Math.max(Math.min(n, length), 0); 531 | var last = length - 1; 532 | for (var index = 0; index < n; index++) { 533 | var rand = _.random(index, last); 534 | var temp = sample[index]; 535 | sample[index] = sample[rand]; 536 | sample[rand] = temp; 537 | } 538 | return sample.slice(0, n); 539 | }; 540 | 541 | // Sort the object's values by a criterion produced by an iteratee. 542 | _.sortBy = function(obj, iteratee, context) { 543 | // 初始化下标变量, 生成迭代的回调函数. 544 | var index = 0; 545 | iteratee = cb(iteratee, context); 546 | // 一开始的那个 return 函数其实是像下面这样: 547 | // ``` 548 | // return _.pluck(array, 'value'); 549 | // ``` 550 | // 提取 array 中 item 中的 value 属性的值并返回一个数组. 551 | // 而对于 array 数组, 它相当于: 552 | return _.pluck(_.map(obj, function(value, key, list) { 553 | // 首先它对一开始传进来的 obj 进行一个 map 迭代, 554 | // 将里面的值换成类似 { value: obj[i], index: i, criteria: returnValue } 这样的结构, 555 | // criteria 属性就是使用 obj[i] 进行 sortBy 传入的回调执行后得到的值. 556 | return { 557 | value: value, 558 | index: index++, 559 | criteria: iteratee(value, key, list) 560 | }; 561 | }).sort(function(left, right) { 562 | // _.map 返回一个数组, 然后利用数组本身的 sort 方法, 进行排序, 563 | // 利用 item 里面的 criteria 进行比较, 将数字大的放在前面, 564 | // 1 表示在 left 在前面, -1 表示 right 在前面, 然后得到的数组 565 | var a = left.criteria; 566 | var b = right.criteria; 567 | if (a !== b) { 568 | if (a > b || a === void 0) return 1; 569 | if (a < b || b === void 0) return -1; 570 | } 571 | return left.index - right.index; 572 | //使用 pluck 来过滤得到排序后的 obj. 573 | }), 'value'); 574 | }; 575 | 576 | // An internal function used for aggregate "group by" operations. 577 | // 这是用来生成 group by 类函数的内部方法 578 | var group = function(behavior, partition) { 579 | // 返回闭包 580 | return function(obj, iteratee, context) { 581 | // 初始化 result 变量, 作为最后的返回结果 582 | var result = partition ? [[], []] : {}; 583 | // 生成包装回调 584 | iteratee = cb(iteratee, context); 585 | // 使用 each 遍历传入的 obj 586 | _.each(obj, function(value, index) { 587 | // 根据每个 API 调用时传入的回调, 生成一类元素的 key, 比如 1.3, 1.5 生成的类键名就是 1 588 | var key = iteratee(value, index, obj); 589 | //通过闭包, 执行创建 API 的时候传入的 behavior 函数 590 | behavior(result, value, key); 591 | }); 592 | // 返回结果 593 | return result; 594 | }; 595 | }; 596 | 597 | // Groups the object's values by a criterion. Pass either a string attribute 598 | // to group by, or a function that returns the criterion. 599 | _.groupBy = group(function(result, value, key) { 600 | // 如果 result 本身有这个键名, 那么就将 value 添加到对应的 key 数组中 601 | // 如果没有的话, 就创建 key 属性, 值为包含 value 值的数组 602 | if (_.has(result, key)) result[key].push(value); else result[key] = [value]; 603 | }); 604 | 605 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 606 | // when you know that your index values will be unique. 607 | // 这个 indexBy API 是 groupBy 的特殊用法, 和 groupBy 的用法与意义是类似的, 只是在使用这个 API 的时候, 你需要知道 608 | // 用于进行分类的键名是在传入的对象中是唯一的. 609 | _.indexBy = group(function(result, value, key) { 610 | result[key] = value; 611 | }); 612 | 613 | // Counts instances of an object that group by a certain criterion. Pass 614 | // either a string attribute to count by, or a function that returns the 615 | // criterion. 616 | // 这个 API 和 group API 也是非常相似, 只是 groupBy 函数返回的是分类数组, 其中的 item 是分类的一个个元素 617 | // countBy 则是一个类的数量, 比如 1.3, 1.5 使用分类就是 { '1': [1.3, 1.5] }, 使用 countBy 就是 { '1': 2 } 618 | _.countBy = group(function(result, value, key) { 619 | if (_.has(result, key)) result[key]++; else result[key] = 1; 620 | }); 621 | 622 | var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; 623 | // Safely create a real, live array from anything iterable. 624 | _.toArray = function(obj) { 625 | if (!obj) return []; 626 | if (_.isArray(obj)) return slice.call(obj); 627 | if (_.isString(obj)) { 628 | // Keep surrogate pair characters together 629 | return obj.match(reStrSymbol); 630 | } 631 | if (isArrayLike(obj)) return _.map(obj, _.identity); 632 | return _.values(obj); 633 | }; 634 | 635 | // Return the number of elements in an object. 636 | _.size = function(obj) { 637 | if (obj == null) return 0; 638 | return isArrayLike(obj) ? obj.length : _.keys(obj).length; 639 | }; 640 | 641 | // Split a collection into two arrays: one whose elements all satisfy the given 642 | // predicate, and one whose elements all do not satisfy the predicate. 643 | _.partition = group(function(result, value, pass) { 644 | result[pass ? 0 : 1].push(value); 645 | }, true); 646 | 647 | // Array Functions 648 | // --------------- 649 | 650 | // Get the first element of an array. Passing **n** will return the first N 651 | // values in the array. Aliased as `head` and `take`. The **guard** check 652 | // allows it to work with `_.map`. 653 | _.first = _.head = _.take = function(array, n, guard) { 654 | if (array == null || array.length < 1) return void 0; 655 | if (n == null || guard) return array[0]; 656 | return _.initial(array, array.length - n); 657 | }; 658 | 659 | // Returns everything but the last entry of the array. Especially useful on 660 | // the arguments object. Passing **n** will return all the values in 661 | // the array, excluding the last N. 662 | _.initial = function(array, n, guard) { 663 | return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); 664 | }; 665 | 666 | // Get the last element of an array. Passing **n** will return the last N 667 | // values in the array. 668 | _.last = function(array, n, guard) { 669 | if (array == null || array.length < 1) return void 0; 670 | if (n == null || guard) return array[array.length - 1]; 671 | return _.rest(array, Math.max(0, array.length - n)); 672 | }; 673 | 674 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 675 | // Especially useful on the arguments object. Passing an **n** will return 676 | // the rest N values in the array. 677 | _.rest = _.tail = _.drop = function(array, n, guard) { 678 | return slice.call(array, n == null || guard ? 1 : n); 679 | }; 680 | 681 | // Trim out all falsy values from an array. 682 | _.compact = function(array) { 683 | return _.filter(array, Boolean); 684 | }; 685 | 686 | // Internal implementation of a recursive `flatten` function. 687 | var flatten = function(input, shallow, strict, output) { 688 | output = output || []; 689 | var idx = output.length; 690 | for (var i = 0, length = getLength(input); i < length; i++) { 691 | var value = input[i]; 692 | if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { 693 | // Flatten current level of array or arguments object. 694 | if (shallow) { 695 | var j = 0, len = value.length; 696 | while (j < len) output[idx++] = value[j++]; 697 | } else { 698 | flatten(value, shallow, strict, output); 699 | idx = output.length; 700 | } 701 | } else if (!strict) { 702 | output[idx++] = value; 703 | } 704 | } 705 | return output; 706 | }; 707 | 708 | // Flatten out an array, either recursively (by default), or just one level. 709 | _.flatten = function(array, shallow) { 710 | return flatten(array, shallow, false); 711 | }; 712 | 713 | // Return a version of the array that does not contain the specified value(s). 714 | _.without = restArgs(function(array, otherArrays) { 715 | return _.difference(array, otherArrays); 716 | }); 717 | 718 | // Produce a duplicate-free version of the array. If the array has already 719 | // been sorted, you have the option of using a faster algorithm. 720 | // Aliased as `unique`. 721 | _.uniq = _.unique = function(array, isSorted, iteratee, context) { 722 | if (!_.isBoolean(isSorted)) { 723 | context = iteratee; 724 | iteratee = isSorted; 725 | isSorted = false; 726 | } 727 | if (iteratee != null) iteratee = cb(iteratee, context); 728 | var result = []; 729 | var seen = []; 730 | for (var i = 0, length = getLength(array); i < length; i++) { 731 | var value = array[i], 732 | computed = iteratee ? iteratee(value, i, array) : value; 733 | if (isSorted) { 734 | if (!i || seen !== computed) result.push(value); 735 | seen = computed; 736 | } else if (iteratee) { 737 | if (!_.contains(seen, computed)) { 738 | seen.push(computed); 739 | result.push(value); 740 | } 741 | } else if (!_.contains(result, value)) { 742 | result.push(value); 743 | } 744 | } 745 | return result; 746 | }; 747 | 748 | // Produce an array that contains the union: each distinct element from all of 749 | // the passed-in arrays. 750 | _.union = restArgs(function(arrays) { 751 | return _.uniq(flatten(arrays, true, true)); 752 | }); 753 | 754 | // Produce an array that contains every item shared between all the 755 | // passed-in arrays. 756 | _.intersection = function(array) { 757 | var result = []; 758 | var argsLength = arguments.length; 759 | for (var i = 0, length = getLength(array); i < length; i++) { 760 | var item = array[i]; 761 | if (_.contains(result, item)) continue; 762 | var j; 763 | for (j = 1; j < argsLength; j++) { 764 | if (!_.contains(arguments[j], item)) break; 765 | } 766 | if (j === argsLength) result.push(item); 767 | } 768 | return result; 769 | }; 770 | 771 | // Take the difference between one array and a number of other arrays. 772 | // Only the elements present in just the first array will remain. 773 | _.difference = restArgs(function(array, rest) { 774 | rest = flatten(rest, true, true); 775 | return _.filter(array, function(value){ 776 | return !_.contains(rest, value); 777 | }); 778 | }); 779 | 780 | // Complement of _.zip. Unzip accepts an array of arrays and groups 781 | // each array's elements on shared indices. 782 | _.unzip = function(array) { 783 | var length = array && _.max(array, getLength).length || 0; 784 | var result = Array(length); 785 | 786 | for (var index = 0; index < length; index++) { 787 | result[index] = _.pluck(array, index); 788 | } 789 | return result; 790 | }; 791 | 792 | // Zip together multiple lists into a single array -- elements that share 793 | // an index go together. 794 | _.zip = restArgs(_.unzip); 795 | 796 | // Converts lists into objects. Pass either a single array of `[key, value]` 797 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 798 | // the corresponding values. Passing by pairs is the reverse of _.pairs. 799 | _.object = function(list, values) { 800 | var result = {}; 801 | for (var i = 0, length = getLength(list); i < length; i++) { 802 | if (values) { 803 | result[list[i]] = values[i]; 804 | } else { 805 | result[list[i][0]] = list[i][1]; 806 | } 807 | } 808 | return result; 809 | }; 810 | 811 | // Generator function to create the findIndex and findLastIndex functions. 812 | // 生成 findIndex 和 findLastIndex 两个方法的核心函数 813 | // 通过 dir 来达到 for 循环的时候的方向选择. 814 | var createPredicateIndexFinder = function(dir) { 815 | return function(array, predicate, context) { 816 | // 优化与生成包装回调函数 817 | predicate = cb(predicate, context); 818 | // 获取元素 length 属性 819 | var length = getLength(array); 820 | // 选择开始遍历的下标 821 | var index = dir > 0 ? 0 : length - 1; 822 | for (; index >= 0 && index < length; index += dir) { 823 | // 找到立刻返回, 不会继续遍历下面的列表 824 | if (predicate(array[index], index, array)) return index; 825 | } 826 | //没有找到返回 -1 827 | return -1; 828 | }; 829 | }; 830 | 831 | // Returns the first index on an array-like that passes a predicate test. 832 | // 从左边开始寻找下标 833 | _.findIndex = createPredicateIndexFinder(1); 834 | // 从右边开始寻找下标 835 | _.findLastIndex = createPredicateIndexFinder(-1); 836 | 837 | // Use a comparator function to figure out the smallest index at which 838 | // an object should be inserted so as to maintain order. Uses binary search. 839 | _.sortedIndex = function(array, obj, iteratee, context) { 840 | iteratee = cb(iteratee, context, 1); 841 | var value = iteratee(obj); 842 | var low = 0, high = getLength(array); 843 | while (low < high) { 844 | var mid = Math.floor((low + high) / 2); 845 | if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; 846 | } 847 | return low; 848 | }; 849 | 850 | // Generator function to create the indexOf and lastIndexOf functions. 851 | // 生成 indexOf 和 lastIndexOf 的核心方法 852 | var createIndexFinder = function(dir, predicateFind, sortedIndex) { 853 | return function(array, item, idx) { 854 | // 先获取输入进来的 array 的 length 属性 855 | var i = 0, length = getLength(array); 856 | // 像 _.indexOf 文档中说, 如果第三个参数传的是数字, 那么说明需要完成的是从第几个开始找 857 | if (typeof idx == 'number') { 858 | // 从左边开始 859 | if (dir > 0) { 860 | // 如果 idx 大于 0 , 则使用 idx, 不是则从后面开始算 861 | i = idx >= 0 ? idx : Math.max(idx + length, i); 862 | } else { 863 | //从右边开始 864 | length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; 865 | } 866 | } else if (sortedIndex && idx && length) { 867 | // 如果有传入排序函数而且 idx 是 true, length 大于 0 868 | // 如果 idx 传的是 true, 说明传入的数组是一个已经排序的数组, 那么 indexOf 在查找 index 的时候就会使用二分查找来加快查询速度 869 | idx = sortedIndex(array, item); 870 | // 然后如果找到那么就返回 idx, 如果没有则返回 -1 871 | return array[idx] === item ? idx : -1; 872 | } 873 | // 如果 item 不是数字, 而是 NaN( NaN 不和任何数相等, 包括自己) 874 | if (item !== item) { 875 | // 使用 predicateFind 来查找下标.也就是 findIndex 或者 findLastIndex 函数 876 | // 使用的回调是 _.isNaN , 如果遍历 array 从 i 开始得到的数组中有 NaN 的, 那么就会返回对应的下标, 如果没有则为 -1; 877 | idx = predicateFind(slice.call(array, i, length), _.isNaN); 878 | // 注意返回的 idx 要加 i, 因为是从 i 开始寻找的. 879 | return idx >= 0 ? idx + i : -1; 880 | } 881 | // 如果上述情况都没有, 那么属于普通的查找情况, 那么就遍历然后通过 === 判断值是否相等. 882 | for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { 883 | if (array[idx] === item) return idx; 884 | } 885 | return -1; 886 | }; 887 | }; 888 | 889 | // Return the position of the first occurrence of an item in an array, 890 | // or -1 if the item is not included in the array. 891 | // If the array is large and already in sort order, pass `true` 892 | // for **isSorted** to use binary search. 893 | // 同样地, 通过 dir 为 1 和 -1 在 for 循环中表示方向 894 | // _.sortedIndex 如果传入 true 表示 array 已排序, 那么将使用二分查找来寻找. 895 | _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); 896 | _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); 897 | 898 | // Generate an integer Array containing an arithmetic progression. A port of 899 | // the native Python `range()` function. See 900 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 901 | _.range = function(start, stop, step) { 902 | if (stop == null) { 903 | stop = start || 0; 904 | start = 0; 905 | } 906 | if (!step) { 907 | step = stop < start ? -1 : 1; 908 | } 909 | 910 | var length = Math.max(Math.ceil((stop - start) / step), 0); 911 | var range = Array(length); 912 | 913 | for (var idx = 0; idx < length; idx++, start += step) { 914 | range[idx] = start; 915 | } 916 | 917 | return range; 918 | }; 919 | 920 | // Split an **array** into several arrays containing **count** or less elements 921 | // of initial array. 922 | _.chunk = function(array, count) { 923 | if (count == null || count < 1) return []; 924 | 925 | var result = []; 926 | var i = 0, length = array.length; 927 | while (i < length) { 928 | result.push(slice.call(array, i, i += count)); 929 | } 930 | return result; 931 | }; 932 | 933 | // Function (ahem) Functions 934 | // ------------------ 935 | 936 | // Determines whether to execute a function as a constructor 937 | // or a normal function with the provided arguments. 938 | var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { 939 | if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 940 | var self = baseCreate(sourceFunc.prototype); 941 | var result = sourceFunc.apply(self, args); 942 | if (_.isObject(result)) return result; 943 | return self; 944 | }; 945 | 946 | // Create a function bound to a given object (assigning `this`, and arguments, 947 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 948 | // available. 949 | _.bind = restArgs(function(func, context, args) { 950 | if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); 951 | var bound = restArgs(function(callArgs) { 952 | return executeBound(func, bound, context, this, args.concat(callArgs)); 953 | }); 954 | return bound; 955 | }); 956 | 957 | // Partially apply a function by creating a version that has had some of its 958 | // arguments pre-filled, without changing its dynamic `this` context. _ acts 959 | // as a placeholder by default, allowing any combination of arguments to be 960 | // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. 961 | _.partial = restArgs(function(func, boundArgs) { 962 | var placeholder = _.partial.placeholder; 963 | var bound = function() { 964 | var position = 0, length = boundArgs.length; 965 | var args = Array(length); 966 | for (var i = 0; i < length; i++) { 967 | args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; 968 | } 969 | while (position < arguments.length) args.push(arguments[position++]); 970 | return executeBound(func, bound, this, this, args); 971 | }; 972 | return bound; 973 | }); 974 | 975 | _.partial.placeholder = _; 976 | 977 | // Bind a number of an object's methods to that object. Remaining arguments 978 | // are the method names to be bound. Useful for ensuring that all callbacks 979 | // defined on an object belong to it. 980 | _.bindAll = restArgs(function(obj, keys) { 981 | keys = flatten(keys, false, false); 982 | var index = keys.length; 983 | if (index < 1) throw new Error('bindAll must be passed function names'); 984 | while (index--) { 985 | var key = keys[index]; 986 | obj[key] = _.bind(obj[key], obj); 987 | } 988 | }); 989 | 990 | // Memoize an expensive function by storing its results. 991 | _.memoize = function(func, hasher) { 992 | var memoize = function(key) { 993 | var cache = memoize.cache; 994 | var address = '' + (hasher ? hasher.apply(this, arguments) : key); 995 | if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); 996 | return cache[address]; 997 | }; 998 | memoize.cache = {}; 999 | return memoize; 1000 | }; 1001 | 1002 | // Delays a function for the given number of milliseconds, and then calls 1003 | // it with the arguments supplied. 1004 | _.delay = restArgs(function(func, wait, args) { 1005 | return setTimeout(function() { 1006 | return func.apply(null, args); 1007 | }, wait); 1008 | }); 1009 | 1010 | // Defers a function, scheduling it to run after the current call stack has 1011 | // cleared. 1012 | _.defer = _.partial(_.delay, _, 1); 1013 | 1014 | // Returns a function, that, when invoked, will only be triggered at most once 1015 | // during a given window of time. Normally, the throttled function will run 1016 | // as much as it can, without ever going more than once per `wait` duration; 1017 | // but if you'd like to disable the execution on the leading edge, pass 1018 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 1019 | _.throttle = function(func, wait, options) { 1020 | var timeout, context, args, result; 1021 | var previous = 0; 1022 | if (!options) options = {}; 1023 | 1024 | var later = function() { 1025 | previous = options.leading === false ? 0 : _.now(); 1026 | timeout = null; 1027 | result = func.apply(context, args); 1028 | if (!timeout) context = args = null; 1029 | }; 1030 | 1031 | var throttled = function() { 1032 | var now = _.now(); 1033 | if (!previous && options.leading === false) previous = now; 1034 | var remaining = wait - (now - previous); 1035 | context = this; 1036 | args = arguments; 1037 | if (remaining <= 0 || remaining > wait) { 1038 | if (timeout) { 1039 | clearTimeout(timeout); 1040 | timeout = null; 1041 | } 1042 | previous = now; 1043 | result = func.apply(context, args); 1044 | if (!timeout) context = args = null; 1045 | } else if (!timeout && options.trailing !== false) { 1046 | timeout = setTimeout(later, remaining); 1047 | } 1048 | return result; 1049 | }; 1050 | 1051 | throttled.cancel = function() { 1052 | clearTimeout(timeout); 1053 | previous = 0; 1054 | timeout = context = args = null; 1055 | }; 1056 | 1057 | return throttled; 1058 | }; 1059 | 1060 | // Returns a function, that, as long as it continues to be invoked, will not 1061 | // be triggered. The function will be called after it stops being called for 1062 | // N milliseconds. If `immediate` is passed, trigger the function on the 1063 | // leading edge, instead of the trailing. 1064 | _.debounce = function(func, wait, immediate) { 1065 | var timeout, result; 1066 | 1067 | var later = function(context, args) { 1068 | timeout = null; 1069 | if (args) result = func.apply(context, args); 1070 | }; 1071 | 1072 | var debounced = restArgs(function(args) { 1073 | if (timeout) clearTimeout(timeout); 1074 | if (immediate) { 1075 | var callNow = !timeout; 1076 | timeout = setTimeout(later, wait); 1077 | if (callNow) result = func.apply(this, args); 1078 | } else { 1079 | timeout = _.delay(later, wait, this, args); 1080 | } 1081 | 1082 | return result; 1083 | }); 1084 | 1085 | debounced.cancel = function() { 1086 | clearTimeout(timeout); 1087 | timeout = null; 1088 | }; 1089 | 1090 | return debounced; 1091 | }; 1092 | 1093 | // Returns the first function passed as an argument to the second, 1094 | // allowing you to adjust arguments, run code before and after, and 1095 | // conditionally execute the original function. 1096 | _.wrap = function(func, wrapper) { 1097 | return _.partial(wrapper, func); 1098 | }; 1099 | 1100 | // Returns a negated version of the passed-in predicate. 1101 | _.negate = function(predicate) { 1102 | return function() { 1103 | return !predicate.apply(this, arguments); 1104 | }; 1105 | }; 1106 | 1107 | // Returns a function that is the composition of a list of functions, each 1108 | // consuming the return value of the function that follows. 1109 | _.compose = function() { 1110 | var args = arguments; 1111 | var start = args.length - 1; 1112 | return function() { 1113 | var i = start; 1114 | var result = args[start].apply(this, arguments); 1115 | while (i--) result = args[i].call(this, result); 1116 | return result; 1117 | }; 1118 | }; 1119 | 1120 | // Returns a function that will only be executed on and after the Nth call. 1121 | _.after = function(times, func) { 1122 | return function() { 1123 | if (--times < 1) { 1124 | return func.apply(this, arguments); 1125 | } 1126 | }; 1127 | }; 1128 | 1129 | // Returns a function that will only be executed up to (but not including) the Nth call. 1130 | _.before = function(times, func) { 1131 | var memo; 1132 | return function() { 1133 | if (--times > 0) { 1134 | memo = func.apply(this, arguments); 1135 | } 1136 | if (times <= 1) func = null; 1137 | return memo; 1138 | }; 1139 | }; 1140 | 1141 | // Returns a function that will be executed at most one time, no matter how 1142 | // often you call it. Useful for lazy initialization. 1143 | _.once = _.partial(_.before, 2); 1144 | 1145 | _.restArgs = restArgs; 1146 | 1147 | // Object Functions 1148 | // ---------------- 1149 | 1150 | // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. 1151 | var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); 1152 | var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 1153 | 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; 1154 | 1155 | var collectNonEnumProps = function(obj, keys) { 1156 | var nonEnumIdx = nonEnumerableProps.length; 1157 | var constructor = obj.constructor; 1158 | var proto = _.isFunction(constructor) && constructor.prototype || ObjProto; 1159 | 1160 | // Constructor is a special case. 1161 | var prop = 'constructor'; 1162 | if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); 1163 | 1164 | while (nonEnumIdx--) { 1165 | prop = nonEnumerableProps[nonEnumIdx]; 1166 | if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { 1167 | keys.push(prop); 1168 | } 1169 | } 1170 | }; 1171 | 1172 | // Retrieve the names of an object's own properties. 1173 | // Delegates to **ECMAScript 5**'s native `Object.keys`. 1174 | _.keys = function(obj) { 1175 | if (!_.isObject(obj)) return []; 1176 | if (nativeKeys) return nativeKeys(obj); 1177 | var keys = []; 1178 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 1179 | // Ahem, IE < 9. 1180 | if (hasEnumBug) collectNonEnumProps(obj, keys); 1181 | return keys; 1182 | }; 1183 | 1184 | // Retrieve all the property names of an object. 1185 | _.allKeys = function(obj) { 1186 | if (!_.isObject(obj)) return []; 1187 | var keys = []; 1188 | for (var key in obj) keys.push(key); 1189 | // Ahem, IE < 9. 1190 | if (hasEnumBug) collectNonEnumProps(obj, keys); 1191 | return keys; 1192 | }; 1193 | 1194 | // Retrieve the values of an object's properties. 1195 | _.values = function(obj) { 1196 | var keys = _.keys(obj); 1197 | var length = keys.length; 1198 | var values = Array(length); 1199 | for (var i = 0; i < length; i++) { 1200 | values[i] = obj[keys[i]]; 1201 | } 1202 | return values; 1203 | }; 1204 | 1205 | // Returns the results of applying the iteratee to each element of the object. 1206 | // In contrast to _.map it returns an object. 1207 | _.mapObject = function(obj, iteratee, context) { 1208 | iteratee = cb(iteratee, context); 1209 | var keys = _.keys(obj), 1210 | length = keys.length, 1211 | results = {}; 1212 | for (var index = 0; index < length; index++) { 1213 | var currentKey = keys[index]; 1214 | results[currentKey] = iteratee(obj[currentKey], currentKey, obj); 1215 | } 1216 | return results; 1217 | }; 1218 | 1219 | // Convert an object into a list of `[key, value]` pairs. 1220 | // The opposite of _.object. 1221 | _.pairs = function(obj) { 1222 | var keys = _.keys(obj); 1223 | var length = keys.length; 1224 | var pairs = Array(length); 1225 | for (var i = 0; i < length; i++) { 1226 | pairs[i] = [keys[i], obj[keys[i]]]; 1227 | } 1228 | return pairs; 1229 | }; 1230 | 1231 | // Invert the keys and values of an object. The values must be serializable. 1232 | _.invert = function(obj) { 1233 | var result = {}; 1234 | var keys = _.keys(obj); 1235 | for (var i = 0, length = keys.length; i < length; i++) { 1236 | result[obj[keys[i]]] = keys[i]; 1237 | } 1238 | return result; 1239 | }; 1240 | 1241 | // Return a sorted list of the function names available on the object. 1242 | // Aliased as `methods`. 1243 | _.functions = _.methods = function(obj) { 1244 | var names = []; 1245 | for (var key in obj) { 1246 | if (_.isFunction(obj[key])) names.push(key); 1247 | } 1248 | return names.sort(); 1249 | }; 1250 | 1251 | // An internal function for creating assigner functions. 1252 | var createAssigner = function(keysFunc, defaults) { 1253 | return function(obj) { 1254 | var length = arguments.length; 1255 | if (defaults) obj = Object(obj); 1256 | if (length < 2 || obj == null) return obj; 1257 | for (var index = 1; index < length; index++) { 1258 | var source = arguments[index], 1259 | keys = keysFunc(source), 1260 | l = keys.length; 1261 | for (var i = 0; i < l; i++) { 1262 | var key = keys[i]; 1263 | if (!defaults || obj[key] === void 0) obj[key] = source[key]; 1264 | } 1265 | } 1266 | return obj; 1267 | }; 1268 | }; 1269 | 1270 | // Extend a given object with all the properties in passed-in object(s). 1271 | _.extend = createAssigner(_.allKeys); 1272 | 1273 | // Assigns a given object with all the own properties in the passed-in object(s). 1274 | // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 1275 | _.extendOwn = _.assign = createAssigner(_.keys); 1276 | 1277 | // Returns the first key on an object that passes a predicate test. 1278 | _.findKey = function(obj, predicate, context) { 1279 | predicate = cb(predicate, context); 1280 | var keys = _.keys(obj), key; 1281 | for (var i = 0, length = keys.length; i < length; i++) { 1282 | key = keys[i]; 1283 | if (predicate(obj[key], key, obj)) return key; 1284 | } 1285 | }; 1286 | 1287 | // Internal pick helper function to determine if `obj` has key `key`. 1288 | var keyInObj = function(value, key, obj) { 1289 | return key in obj; 1290 | }; 1291 | 1292 | // Return a copy of the object only containing the whitelisted properties. 1293 | _.pick = restArgs(function(obj, keys) { 1294 | var result = {}, iteratee = keys[0]; 1295 | if (obj == null) return result; 1296 | if (_.isFunction(iteratee)) { 1297 | if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); 1298 | keys = _.allKeys(obj); 1299 | } else { 1300 | iteratee = keyInObj; 1301 | keys = flatten(keys, false, false); 1302 | obj = Object(obj); 1303 | } 1304 | for (var i = 0, length = keys.length; i < length; i++) { 1305 | var key = keys[i]; 1306 | var value = obj[key]; 1307 | if (iteratee(value, key, obj)) result[key] = value; 1308 | } 1309 | return result; 1310 | }); 1311 | 1312 | // Return a copy of the object without the blacklisted properties. 1313 | _.omit = restArgs(function(obj, keys) { 1314 | var iteratee = keys[0], context; 1315 | if (_.isFunction(iteratee)) { 1316 | iteratee = _.negate(iteratee); 1317 | if (keys.length > 1) context = keys[1]; 1318 | } else { 1319 | keys = _.map(flatten(keys, false, false), String); 1320 | iteratee = function(value, key) { 1321 | return !_.contains(keys, key); 1322 | }; 1323 | } 1324 | return _.pick(obj, iteratee, context); 1325 | }); 1326 | 1327 | // Fill in a given object with default properties. 1328 | _.defaults = createAssigner(_.allKeys, true); 1329 | 1330 | // Creates an object that inherits from the given prototype object. 1331 | // If additional properties are provided then they will be added to the 1332 | // created object. 1333 | _.create = function(prototype, props) { 1334 | var result = baseCreate(prototype); 1335 | if (props) _.extendOwn(result, props); 1336 | return result; 1337 | }; 1338 | 1339 | // Create a (shallow-cloned) duplicate of an object. 1340 | _.clone = function(obj) { 1341 | if (!_.isObject(obj)) return obj; 1342 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 1343 | }; 1344 | 1345 | // Invokes interceptor with the obj, and then returns obj. 1346 | // The primary purpose of this method is to "tap into" a method chain, in 1347 | // order to perform operations on intermediate results within the chain. 1348 | _.tap = function(obj, interceptor) { 1349 | interceptor(obj); 1350 | return obj; 1351 | }; 1352 | 1353 | // Returns whether an object has a given set of `key:value` pairs. 1354 | _.isMatch = function(object, attrs) { 1355 | var keys = _.keys(attrs), length = keys.length; 1356 | if (object == null) return !length; 1357 | var obj = Object(object); 1358 | for (var i = 0; i < length; i++) { 1359 | var key = keys[i]; 1360 | if (attrs[key] !== obj[key] || !(key in obj)) return false; 1361 | } 1362 | return true; 1363 | }; 1364 | 1365 | 1366 | // Internal recursive comparison function for `isEqual`. 1367 | var eq, deepEq; 1368 | eq = function(a, b, aStack, bStack) { 1369 | // Identical objects are equal. `0 === -0`, but they aren't identical. 1370 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 1371 | if (a === b) return a !== 0 || 1 / a === 1 / b; 1372 | // `null` or `undefined` only equal to itself (strict comparison). 1373 | if (a == null || b == null) return false; 1374 | // `NaN`s are equivalent, but non-reflexive. 1375 | if (a !== a) return b !== b; 1376 | // Exhaust primitive checks 1377 | var type = typeof a; 1378 | if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; 1379 | return deepEq(a, b, aStack, bStack); 1380 | }; 1381 | 1382 | // Internal recursive comparison function for `isEqual`. 1383 | deepEq = function(a, b, aStack, bStack) { 1384 | // Unwrap any wrapped objects. 1385 | if (a instanceof _) a = a._wrapped; 1386 | if (b instanceof _) b = b._wrapped; 1387 | // Compare `[[Class]]` names. 1388 | var className = toString.call(a); 1389 | if (className !== toString.call(b)) return false; 1390 | switch (className) { 1391 | // Strings, numbers, regular expressions, dates, and booleans are compared by value. 1392 | case '[object RegExp]': 1393 | // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') 1394 | case '[object String]': 1395 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 1396 | // equivalent to `new String("5")`. 1397 | return '' + a === '' + b; 1398 | case '[object Number]': 1399 | // `NaN`s are equivalent, but non-reflexive. 1400 | // Object(NaN) is equivalent to NaN. 1401 | if (+a !== +a) return +b !== +b; 1402 | // An `egal` comparison is performed for other numeric values. 1403 | return +a === 0 ? 1 / +a === 1 / b : +a === +b; 1404 | case '[object Date]': 1405 | case '[object Boolean]': 1406 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 1407 | // millisecond representations. Note that invalid dates with millisecond representations 1408 | // of `NaN` are not equivalent. 1409 | return +a === +b; 1410 | case '[object Symbol]': 1411 | return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); 1412 | } 1413 | 1414 | var areArrays = className === '[object Array]'; 1415 | if (!areArrays) { 1416 | if (typeof a != 'object' || typeof b != 'object') return false; 1417 | 1418 | // Objects with different constructors are not equivalent, but `Object`s or `Array`s 1419 | // from different frames are. 1420 | var aCtor = a.constructor, bCtor = b.constructor; 1421 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && 1422 | _.isFunction(bCtor) && bCtor instanceof bCtor) 1423 | && ('constructor' in a && 'constructor' in b)) { 1424 | return false; 1425 | } 1426 | } 1427 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 1428 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 1429 | 1430 | // Initializing stack of traversed objects. 1431 | // It's done here since we only need them for objects and arrays comparison. 1432 | aStack = aStack || []; 1433 | bStack = bStack || []; 1434 | var length = aStack.length; 1435 | while (length--) { 1436 | // Linear search. Performance is inversely proportional to the number of 1437 | // unique nested structures. 1438 | if (aStack[length] === a) return bStack[length] === b; 1439 | } 1440 | 1441 | // Add the first object to the stack of traversed objects. 1442 | aStack.push(a); 1443 | bStack.push(b); 1444 | 1445 | // Recursively compare objects and arrays. 1446 | if (areArrays) { 1447 | // Compare array lengths to determine if a deep comparison is necessary. 1448 | length = a.length; 1449 | if (length !== b.length) return false; 1450 | // Deep compare the contents, ignoring non-numeric properties. 1451 | while (length--) { 1452 | if (!eq(a[length], b[length], aStack, bStack)) return false; 1453 | } 1454 | } else { 1455 | // Deep compare objects. 1456 | var keys = _.keys(a), key; 1457 | length = keys.length; 1458 | // Ensure that both objects contain the same number of properties before comparing deep equality. 1459 | if (_.keys(b).length !== length) return false; 1460 | while (length--) { 1461 | // Deep compare each member 1462 | key = keys[length]; 1463 | if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; 1464 | } 1465 | } 1466 | // Remove the first object from the stack of traversed objects. 1467 | aStack.pop(); 1468 | bStack.pop(); 1469 | return true; 1470 | }; 1471 | 1472 | // Perform a deep comparison to check if two objects are equal. 1473 | _.isEqual = function(a, b) { 1474 | return eq(a, b); 1475 | }; 1476 | 1477 | // Is a given array, string, or object empty? 1478 | // An "empty" object has no enumerable own-properties. 1479 | _.isEmpty = function(obj) { 1480 | if (obj == null) return true; 1481 | if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; 1482 | return _.keys(obj).length === 0; 1483 | }; 1484 | 1485 | // Is a given value a DOM element? 1486 | _.isElement = function(obj) { 1487 | return !!(obj && obj.nodeType === 1); 1488 | }; 1489 | 1490 | // Is a given value an array? 1491 | // Delegates to ECMA5's native Array.isArray 1492 | _.isArray = nativeIsArray || function(obj) { 1493 | return toString.call(obj) === '[object Array]'; 1494 | }; 1495 | 1496 | // Is a given variable an object? 1497 | _.isObject = function(obj) { 1498 | var type = typeof obj; 1499 | return type === 'function' || type === 'object' && !!obj; 1500 | }; 1501 | 1502 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet. 1503 | _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) { 1504 | _['is' + name] = function(obj) { 1505 | return toString.call(obj) === '[object ' + name + ']'; 1506 | }; 1507 | }); 1508 | 1509 | // Define a fallback version of the method in browsers (ahem, IE < 9), where 1510 | // there isn't any inspectable "Arguments" type. 1511 | if (!_.isArguments(arguments)) { 1512 | _.isArguments = function(obj) { 1513 | return _.has(obj, 'callee'); 1514 | }; 1515 | } 1516 | 1517 | // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, 1518 | // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). 1519 | var nodelist = root.document && root.document.childNodes; 1520 | if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { 1521 | _.isFunction = function(obj) { 1522 | return typeof obj == 'function' || false; 1523 | }; 1524 | } 1525 | 1526 | // Is a given object a finite number? 1527 | _.isFinite = function(obj) { 1528 | return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj)); 1529 | }; 1530 | 1531 | // Is the given value `NaN`? 1532 | _.isNaN = function(obj) { 1533 | return _.isNumber(obj) && isNaN(obj); 1534 | }; 1535 | 1536 | // Is a given value a boolean? 1537 | _.isBoolean = function(obj) { 1538 | return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; 1539 | }; 1540 | 1541 | // Is a given value equal to null? 1542 | _.isNull = function(obj) { 1543 | return obj === null; 1544 | }; 1545 | 1546 | // Is a given variable undefined? 1547 | _.isUndefined = function(obj) { 1548 | return obj === void 0; 1549 | }; 1550 | 1551 | // Shortcut function for checking if an object has a given property directly 1552 | // on itself (in other words, not on a prototype). 1553 | _.has = function(obj, path) { 1554 | if (!_.isArray(path)) { 1555 | return obj != null && hasOwnProperty.call(obj, path); 1556 | } 1557 | var length = path.length; 1558 | for (var i = 0; i < length; i++) { 1559 | var key = path[i]; 1560 | if (obj == null || !hasOwnProperty.call(obj, key)) { 1561 | return false; 1562 | } 1563 | obj = obj[key]; 1564 | } 1565 | return !!length; 1566 | }; 1567 | 1568 | // Utility Functions 1569 | // ----------------- 1570 | 1571 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1572 | // previous owner. Returns a reference to the Underscore object. 1573 | _.noConflict = function() { 1574 | root._ = previousUnderscore; 1575 | return this; 1576 | }; 1577 | 1578 | // Keep the identity function around for default iteratees. 1579 | _.identity = function(value) { 1580 | return value; 1581 | }; 1582 | 1583 | // Predicate-generating functions. Often useful outside of Underscore. 1584 | _.constant = function(value) { 1585 | return function() { 1586 | return value; 1587 | }; 1588 | }; 1589 | 1590 | _.noop = function(){}; 1591 | 1592 | _.property = function(path) { 1593 | if (!_.isArray(path)) { 1594 | return shallowProperty(path); 1595 | } 1596 | return function(obj) { 1597 | return deepGet(obj, path); 1598 | }; 1599 | }; 1600 | 1601 | // Generates a function for a given object that returns a given property. 1602 | _.propertyOf = function(obj) { 1603 | if (obj == null) { 1604 | return function(){}; 1605 | } 1606 | return function(path) { 1607 | return !_.isArray(path) ? obj[path] : deepGet(obj, path); 1608 | }; 1609 | }; 1610 | 1611 | // Returns a predicate for checking whether an object has a given set of 1612 | // `key:value` pairs. 1613 | _.matcher = _.matches = function(attrs) { 1614 | attrs = _.extendOwn({}, attrs); 1615 | return function(obj) { 1616 | return _.isMatch(obj, attrs); 1617 | }; 1618 | }; 1619 | 1620 | // Run a function **n** times. 1621 | _.times = function(n, iteratee, context) { 1622 | var accum = Array(Math.max(0, n)); 1623 | iteratee = optimizeCb(iteratee, context, 1); 1624 | for (var i = 0; i < n; i++) accum[i] = iteratee(i); 1625 | return accum; 1626 | }; 1627 | 1628 | // Return a random integer between min and max (inclusive). 1629 | _.random = function(min, max) { 1630 | if (max == null) { 1631 | max = min; 1632 | min = 0; 1633 | } 1634 | return min + Math.floor(Math.random() * (max - min + 1)); 1635 | }; 1636 | 1637 | // A (possibly faster) way to get the current timestamp as an integer. 1638 | _.now = Date.now || function() { 1639 | return new Date().getTime(); 1640 | }; 1641 | 1642 | // List of HTML entities for escaping. 1643 | var escapeMap = { 1644 | '&': '&', 1645 | '<': '<', 1646 | '>': '>', 1647 | '"': '"', 1648 | "'": ''', 1649 | '`': '`' 1650 | }; 1651 | var unescapeMap = _.invert(escapeMap); 1652 | 1653 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1654 | var createEscaper = function(map) { 1655 | var escaper = function(match) { 1656 | return map[match]; 1657 | }; 1658 | // Regexes for identifying a key that needs to be escaped. 1659 | var source = '(?:' + _.keys(map).join('|') + ')'; 1660 | var testRegexp = RegExp(source); 1661 | var replaceRegexp = RegExp(source, 'g'); 1662 | return function(string) { 1663 | string = string == null ? '' : '' + string; 1664 | return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; 1665 | }; 1666 | }; 1667 | _.escape = createEscaper(escapeMap); 1668 | _.unescape = createEscaper(unescapeMap); 1669 | 1670 | // Traverses the children of `obj` along `path`. If a child is a function, it 1671 | // is invoked with its parent as context. Returns the value of the final 1672 | // child, or `fallback` if any child is undefined. 1673 | _.result = function(obj, path, fallback) { 1674 | if (!_.isArray(path)) path = [path]; 1675 | var length = path.length; 1676 | if (!length) { 1677 | return _.isFunction(fallback) ? fallback.call(obj) : fallback; 1678 | } 1679 | for (var i = 0; i < length; i++) { 1680 | var prop = obj == null ? void 0 : obj[path[i]]; 1681 | if (prop === void 0) { 1682 | prop = fallback; 1683 | i = length; // Ensure we don't continue iterating. 1684 | } 1685 | obj = _.isFunction(prop) ? prop.call(obj) : prop; 1686 | } 1687 | return obj; 1688 | }; 1689 | 1690 | // Generate a unique integer id (unique within the entire client session). 1691 | // Useful for temporary DOM ids. 1692 | var idCounter = 0; 1693 | _.uniqueId = function(prefix) { 1694 | var id = ++idCounter + ''; 1695 | return prefix ? prefix + id : id; 1696 | }; 1697 | 1698 | // By default, Underscore uses ERB-style template delimiters, change the 1699 | // following template settings to use alternative delimiters. 1700 | _.templateSettings = { 1701 | evaluate: /<%([\s\S]+?)%>/g, 1702 | interpolate: /<%=([\s\S]+?)%>/g, 1703 | escape: /<%-([\s\S]+?)%>/g 1704 | }; 1705 | 1706 | // When customizing `templateSettings`, if you don't want to define an 1707 | // interpolation, evaluation or escaping regex, we need one that is 1708 | // guaranteed not to match. 1709 | var noMatch = /(.)^/; 1710 | 1711 | // Certain characters need to be escaped so that they can be put into a 1712 | // string literal. 1713 | var escapes = { 1714 | "'": "'", 1715 | '\\': '\\', 1716 | '\r': 'r', 1717 | '\n': 'n', 1718 | '\u2028': 'u2028', 1719 | '\u2029': 'u2029' 1720 | }; 1721 | 1722 | var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; 1723 | 1724 | var escapeChar = function(match) { 1725 | return '\\' + escapes[match]; 1726 | }; 1727 | 1728 | // JavaScript micro-templating, similar to John Resig's implementation. 1729 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1730 | // and correctly escapes quotes within interpolated code. 1731 | // NB: `oldSettings` only exists for backwards compatibility. 1732 | _.template = function(text, settings, oldSettings) { 1733 | if (!settings && oldSettings) settings = oldSettings; 1734 | settings = _.defaults({}, settings, _.templateSettings); 1735 | 1736 | // Combine delimiters into one regular expression via alternation. 1737 | var matcher = RegExp([ 1738 | (settings.escape || noMatch).source, 1739 | (settings.interpolate || noMatch).source, 1740 | (settings.evaluate || noMatch).source 1741 | ].join('|') + '|$', 'g'); 1742 | 1743 | // Compile the template source, escaping string literals appropriately. 1744 | var index = 0; 1745 | var source = "__p+='"; 1746 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1747 | source += text.slice(index, offset).replace(escapeRegExp, escapeChar); 1748 | index = offset + match.length; 1749 | 1750 | if (escape) { 1751 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1752 | } else if (interpolate) { 1753 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1754 | } else if (evaluate) { 1755 | source += "';\n" + evaluate + "\n__p+='"; 1756 | } 1757 | 1758 | // Adobe VMs need the match returned to produce the correct offset. 1759 | return match; 1760 | }); 1761 | source += "';\n"; 1762 | 1763 | // If a variable is not specified, place data values in local scope. 1764 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1765 | 1766 | source = "var __t,__p='',__j=Array.prototype.join," + 1767 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1768 | source + 'return __p;\n'; 1769 | 1770 | var render; 1771 | try { 1772 | render = new Function(settings.variable || 'obj', '_', source); 1773 | } catch (e) { 1774 | e.source = source; 1775 | throw e; 1776 | } 1777 | 1778 | var template = function(data) { 1779 | return render.call(this, data, _); 1780 | }; 1781 | 1782 | // Provide the compiled source as a convenience for precompilation. 1783 | var argument = settings.variable || 'obj'; 1784 | template.source = 'function(' + argument + '){\n' + source + '}'; 1785 | 1786 | return template; 1787 | }; 1788 | 1789 | // Add a "chain" function. Start chaining a wrapped Underscore object. 1790 | _.chain = function(obj) { 1791 | var instance = _(obj); 1792 | instance._chain = true; 1793 | return instance; 1794 | }; 1795 | 1796 | // OOP 1797 | // --------------- 1798 | // If Underscore is called as a function, it returns a wrapped object that 1799 | // can be used OO-style. This wrapper holds altered versions of all the 1800 | // underscore functions. Wrapped objects may be chained. 1801 | 1802 | // Helper function to continue chaining intermediate results. 1803 | var chainResult = function(instance, obj) { 1804 | return instance._chain ? _(obj).chain() : obj; 1805 | }; 1806 | 1807 | // Add your own custom functions to the Underscore object. 1808 | _.mixin = function(obj) { 1809 | _.each(_.functions(obj), function(name) { 1810 | var func = _[name] = obj[name]; 1811 | _.prototype[name] = function() { 1812 | var args = [this._wrapped]; 1813 | push.apply(args, arguments); 1814 | return chainResult(this, func.apply(_, args)); 1815 | }; 1816 | }); 1817 | return _; 1818 | }; 1819 | 1820 | // Add all of the Underscore functions to the wrapper object. 1821 | _.mixin(_); 1822 | 1823 | // Add all mutator Array functions to the wrapper. 1824 | _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1825 | var method = ArrayProto[name]; 1826 | _.prototype[name] = function() { 1827 | var obj = this._wrapped; 1828 | method.apply(obj, arguments); 1829 | if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; 1830 | return chainResult(this, obj); 1831 | }; 1832 | }); 1833 | 1834 | // Add all accessor Array functions to the wrapper. 1835 | _.each(['concat', 'join', 'slice'], function(name) { 1836 | var method = ArrayProto[name]; 1837 | _.prototype[name] = function() { 1838 | return chainResult(this, method.apply(this._wrapped, arguments)); 1839 | }; 1840 | }); 1841 | 1842 | // Extracts the result from a wrapped and chained object. 1843 | _.prototype.value = function() { 1844 | return this._wrapped; 1845 | }; 1846 | 1847 | // Provide unwrapping proxy for some methods used in engine operations 1848 | // such as arithmetic and JSON stringification. 1849 | _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; 1850 | 1851 | _.prototype.toString = function() { 1852 | return String(this._wrapped); 1853 | }; 1854 | 1855 | // AMD registration happens at the end for compatibility with AMD loaders 1856 | // that may not enforce next-turn semantics on modules. Even though general 1857 | // practice for AMD registration is to be anonymous, underscore registers 1858 | // as a named module because, like jQuery, it is a base library that is 1859 | // popular enough to be bundled in a third party lib, but not be part of 1860 | // an AMD load request. Those cases could generate an error when an 1861 | // anonymous define() is called outside of a loader request. 1862 | if (typeof define == 'function' && define.amd) { 1863 | define('underscore', [], function() { 1864 | return _; 1865 | }); 1866 | } 1867 | }()); 1868 | --------------------------------------------------------------------------------