├── README.md ├── bower.json ├── index.js ├── package.json └── utils ├── bmob.js ├── bmobSocketIo.js ├── common.js ├── underscore.js └── util.js /README.md: -------------------------------------------------------------------------------- 1 | Bmob 微信小程序SDK 2 | =================== 3 | 本项目是Bmob 为小程序客户端开发提供 SDK 支持,里面包含实时数据与数据存储相关操作。 4 | 5 | 6 | ### SDK 获取与安装 7 | Bmob 小程序解决方案客户端 [Demo](https://github.com/magic007/wechatAppDemo "Demo") 已经集成并使用最新版的 SDK,并且已经写了大量常见业务代码。需要快速了解的可以从 [Demo](https://github.com/magic007/wechatAppDemo "Demo") 开始。 8 | 9 | 10 | ### 将sdk引入到微信小程序中 11 | ---------- 12 | > 方法一: 13 | 1. 下载将`utils`目录 复制至项目目录下 14 | 2. App.js 初始化SDK 15 | ``` 16 | const Bmob = require('utils/bmob.js'); 17 | Bmob.initialize("你的Application ID", "你的REST API Key"); 18 | ``` 19 | 20 | > 方法二: 21 | ``` 22 | npm install -g bower 23 | bower install bmob-weapp 24 | const Bmob = require('./bower_components/bmob-weapp/utils/bmob.js'); 25 | Bmob.initialize("你的Application ID", "你的REST API Key"); 26 | ``` 27 | 28 | 29 | 本小程序Sdk 不需要购买任何相关服务器,即可操作数据库与微信小程序打通,开箱即用。如是之前平台老用户则不需重复申请账号,并且打通之前的Android,IOS,等平台数据,本示例整合了常用的功能,开发请按照以下步骤操作。 30 | 31 | ### 小程序实战学习视频教程 32 | 33 | #### 链接地址: 34 | [小程序视频教程](http://doc.bmob.cn/video/index.html#3 "小程序视频教程") 35 | 36 | >Tip: 如果你对数据处理要求比较高,可以引入`underscore.js`库。 37 | 38 | ### 配置AppId和AppSecret 39 | ---------- 40 | 登录网址后台点击`应用`->`设置`->`应用配置`填写```AppID```,`AppSecret`。 41 | >Tip: 如果你的小程序不需要获取用户`open id`功能,则不需要配置。 42 | 43 | >### 开发文档 44 | [Bmob 小程序开发文档](http://doc.bmob.cn/data/wechat_app/index.html "开发文档") 45 | 46 | ---------- 47 | 48 | #### 版本 v3.6.1 49 | 50 | > **Note:** 51 | > 52 | > - 应微信要求,登陆 auth() 函数,不再保存用户头像 53 | > 54 | > - 增加user.getUserInfo()函数, 更新用户昵称头像信息 55 | > 56 | > ​ 57 | 58 | #### 版本 v3.6.0 59 | 60 | > **Note:** 61 | > 62 | > - 登陆 auth() 函数,增加then回调,如果出现错误,控制台会提示 63 | > - 兼容小程序游戏,去掉hideNavigationBarLoading 相关函数 64 | > - 增加微信支付退款功能 65 | > - 修复批量删除函数必须用Bmob对象Bug 66 | 67 | #### 版本 v3.5.0 68 | 69 | > **Note:** 70 | > - 数据SDK整合app.js相关功能 71 | > - 数据启用新域名 72 | > - 增加全新socketSDK 73 | > - 优化登陆流程,登陆更加智能 74 | ---------- 75 | #### 版本 v3.4.1 76 | > **Note:** 77 | > - 修复个别时候图片上传进度不正确 78 | > - 增加分组、统计等功能 79 | > - 增加BQL 查询功能 80 | 81 | 82 | #### 版本 v3.4.0 83 | > **Note:** 84 | > - 增加获取手机号解密示例 85 | > - 增加小程序客服回复示例 86 | 87 | 88 | #### 版本 v3.3.2 89 | > **Note:** 90 | > - 修复视频格式Bug 91 | > - 新特性 文件上传增加进度显示 92 | 93 | #### 版本 v3.3.1 94 | > **Note:** 95 | > - 增加试试通讯域名wss.bmobcloud.com 96 | > - 聊天室优化部分流程 97 | 98 | 99 | #### 版本 v3.0.0 100 | > **Note:** 101 | > 102 | > - 此版本主要变动开发者请求API地址,兼容微信白名单工单 103 | > - index代码增加模板消息推送。 104 | 105 | #### 版本 v2.1.0 106 | > **Note:** 107 | > 108 | > - 增加日记详细页面 109 | > - 修复小程序点击事件冒泡问题catchtap 110 | > - 首页增加模糊搜索 111 | > - 修复我发布的没显示bug 112 | 113 | #### 版本 v2.0.2 114 | > **Note:** 115 | > 116 | > - 增加我发布的 117 | > - 修复部分bug 118 | 119 | 120 | #### 版本 v2.0.1 121 | > **Note:** 122 | > 123 | > - 图片上传增加批量上传 124 | > - 页面底部增加版权 125 | 126 | #### 版本 v2.0.0 127 | > **Note:** 128 | > 129 | > - 增加新接口,生成二维码应用内推广链接 130 | > - 升级UI2.0 131 | > - 增加反馈功能模版 132 | 133 | #### 版本 v1.1.0 134 | > **Note:** 135 | > 136 | > - 增加新登录接口,使用请看app.js 137 | > - 增加新图片删除接口,使用请看接口图片上传 138 | 139 | #### 版本 v1.0.0 140 | > **Note:** 141 | > 142 | > - 增加微信支付接口,只需填写key即可微信收款 143 | 144 | 145 | #### 版本 v0.5.0 146 | > **Note:** 147 | 148 | > - 修复返回当前用户延时问题 149 | > - 修复修改用户表缓存没更新问题 150 | 151 | ---------- 152 | 153 | #### 版本 v0.4.2 154 | > **Note:** 155 | 156 | > - 修复短信验证Bug 157 | > - 添加短信验证Demo 158 | 159 | ---------- 160 | 161 | #### 版本 v0.4.1 162 | > **Note:** 163 | 164 | > - 修复注册跳转错误 165 | > - 增加获取OpenId示例 166 | 167 | ---------- 168 | 169 | 170 | #### 版本 v0.4.0 171 | > **Note:** 172 | 173 | > - 添加接口示例 174 | > - 添加短信发送示例 175 | > - 添加退出账户操作 176 | > - 增加个人信息展示 177 | 178 | ---------- 179 | 180 | #### 版本 v0.3.0 181 | > **Note:** 182 | 183 | > - 添加微信小程序关联Bmob后端填写key 184 | > - 小程序获取openid 185 | 186 | ---------- 187 | 188 | #### 版本 v0.2.0 189 | > **Note:** 190 | 191 | > - 修复部分bug 192 | > - 增加文件上传 193 | 194 | ---------- 195 | 196 | #### 版本 v0.1.0 197 | > **Note:** 198 | 199 | > - 数据增删改查 200 | > - 登录 201 | > - 注册 202 | >- 短信 203 | 204 | ---------- 205 | 206 | > **Tip:** 更多信息请查看[官方文档](http://docs.bmob.cn/data/wechatApp/a_faststart/doc/index.html "官方使用文档") ,如需帮助可以加入小程序讨论QQ群:**118541934**。 207 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bmob-weapp-sdk", 3 | "authors": [ 4 | "Bmob Cloud" 5 | ], 6 | "description": "Bmob 小程序客户端 SDK", 7 | "main": "utils/bmob.js", 8 | "license": "MIT", 9 | "homepage": "", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // 正确 2 | const Bmob = require('./utils/bmob') 3 | const BmobSocketIo = require('./utils/bmobSocketIo') 4 | export { Bmob, BmobSocketIo}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bmob-weapp", 3 | "version": "3.5.0", 4 | "description": "小程序SDK", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bmob/bmob-WeApp-sdk.git" 12 | }, 13 | "keywords": [ 14 | "bmob" 15 | ], 16 | "author": "magic", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/bmob/bmob-WeApp-sdk/issues" 20 | }, 21 | "homepage": "https://github.com/bmob/bmob-WeApp-sdk#readme" 22 | } 23 | -------------------------------------------------------------------------------- /utils/bmobSocketIo.js: -------------------------------------------------------------------------------- 1 | const Emitter = { 2 | setup(target) { 3 | let listeners = [] 4 | 5 | Object.assign(target, { 6 | on(type, handle) { 7 | if (typeof handle == 'function') { 8 | listeners.push([type, handle]) 9 | } 10 | }, 11 | emit(type, ...params) { 12 | listeners.forEach( 13 | ([listenType, handle]) => type == listenType && handle(...params) 14 | ) 15 | }, 16 | removeAllListeners() { 17 | listeners = [] 18 | } 19 | }) 20 | } 21 | } 22 | 23 | /** 24 | * 基于小程序 WebSocket 接口封装信道 25 | */ 26 | module.exports = class BmobSocketIo { 27 | constructor(applicationId) { 28 | 29 | this.config = { 30 | host: 'wss.bmobcloud.com' 31 | } 32 | Emitter.setup((this.emitter = {})) 33 | this.applicationId = applicationId 34 | this.initialize() 35 | } 36 | handshake() { 37 | function complete(data) { 38 | if (data instanceof Error) { 39 | self.connecting = false 40 | self.onError(data.message) 41 | } else { 42 | return data.split(':')[0] 43 | } 44 | } 45 | 46 | var url = 'https://'+this.config.host+'/socket.io/1/?t=' + new Date().getTime() 47 | var dataObject = {} 48 | var data = JSON.stringify(dataObject) 49 | 50 | var method = 'GET' 51 | 52 | return new Promise((resolve, reject) => { 53 | wx.request({ 54 | method: method, 55 | url: url, 56 | data: data, 57 | header: { 58 | 'content-type': 'text/plain' 59 | }, 60 | success: function(res) { 61 | if (res.data && res.data.statusCode) { 62 | return resolve('request error', e) 63 | } else if (res.statusCode != 200) { 64 | return resolve('request error', e) 65 | } else { 66 | return resolve(complete(res.data)) 67 | } 68 | }, 69 | fail: function(e) { 70 | return resolve('request error', e) 71 | } 72 | }) 73 | }) 74 | } 75 | initialize() { 76 | this.handshake().then(protocol => { 77 | try { 78 | this.connect( 79 | `wss://${this.config.host}/socket.io/1/websocket/` + protocol, 80 | {} 81 | ) 82 | } catch (connectError) { 83 | console.error({ connectError }) 84 | throw connectError 85 | } 86 | }) 87 | this.on('close', () => { 88 | console.log('连接已中断') 89 | }) 90 | 91 | return new Promise((resolve, reject) => { 92 | this.on('server_pub', data => { 93 | switch (data.action) { 94 | case 'updateTable': 95 | this.onUpdateTable(data.tableName, data.data) 96 | break 97 | case 'updateRow': 98 | this.onUpdateRow(data.tableName, data.objectId, data.data) 99 | break 100 | case 'deleteTable': 101 | this.onDeleteTable(data.tableName, data.data) 102 | break 103 | case 'deleteRow': 104 | this.onDeleteRow(data.tableName, data.objectId, data.data) 105 | break 106 | } 107 | }) 108 | 109 | //连接上socket.io服务器后触发的事件 110 | this.on('client_send_data', resp => { 111 | this.onInitListen() 112 | }) 113 | }) 114 | } 115 | 116 | onInitListen() {} 117 | 118 | connect(url, header) { 119 | // 小程序 wx.connectSocket() API header 参数无效,把会话信息附加在 URL 上 120 | const query = Object.keys(header) 121 | .map(key => `${key}=${encodeURIComponent(header[key])}`) 122 | .join('&') 123 | const seperator = url.indexOf('?') > -1 ? '&' : '?' 124 | url = [url, query].join(seperator) 125 | 126 | return new Promise((resolve, reject) => { 127 | wx.onSocketOpen(resolve) 128 | wx.onSocketError(reject) 129 | wx.onSocketMessage(packet => { 130 | try { 131 | let filter = function(str) { 132 | const { name, args } = JSON.parse(str) 133 | return { name, args } 134 | } 135 | let str = packet.data 136 | let startStr = str.slice(0,4) 137 | // 检测心跳 138 | if('2:::'===startStr){ 139 | this.emit(false,true) 140 | } 141 | str = str.slice(4) 142 | 143 | // 截取后不能为空 144 | if (str == null || str == '') { 145 | return 146 | } 147 | const { name, args } = filter(str) 148 | let data = args == null ? '' : JSON.parse(args[0]) 149 | this.emitter.emit(name, data) 150 | } catch (e) { 151 | console.log('Handle packet failed: ' + packet.data, e) 152 | } 153 | }) 154 | wx.onSocketClose(() => this.emitter.emit('close')) 155 | wx.connectSocket({ url, header }) 156 | }) 157 | } 158 | 159 | on(message, handle) { 160 | this.emitter.on(message, handle) 161 | } 162 | 163 | emit(message, data) { 164 | data=data==undefined?'5:::':'2:::' 165 | message = message?JSON.stringify(message):'' 166 | wx.sendSocketMessage({ 167 | data: data + message 168 | }) 169 | } 170 | 171 | emitData(name, data) { 172 | data = JSON.stringify(data) 173 | return { name: name, args: [data] } 174 | } 175 | 176 | //"unsub_updateTable" ,"unsub_updateRow", "unsub_deleteTable", "unsub_deleteRow" 177 | //订阅更新数据表的数据 178 | updateTable = function(tablename) { 179 | var data = { 180 | appKey: this.applicationId, 181 | tableName: tablename, 182 | objectId: '', 183 | action: 'updateTable' 184 | } 185 | data = this.emitData('client_sub', data) 186 | this.emit(data) 187 | } 188 | 189 | //取消订阅更新数据表的数据 190 | unsubUpdateTable = function(tablename) { 191 | var data = { 192 | appKey: this.applicationId, 193 | tableName: tablename, 194 | objectId: '', 195 | action: 'unsub_updateTable' 196 | } 197 | data = this.emitData('client_sub', data) 198 | this.emit(data) 199 | } 200 | 201 | //订阅行更新的数据 202 | updateRow = function(tablename, objectId) { 203 | var data = { 204 | appKey: this.applicationId, 205 | tableName: tablename, 206 | objectId: objectId, 207 | action: 'updateRow' 208 | } 209 | data = this.emitData('client_sub', data) 210 | this.emit(data) 211 | } 212 | 213 | //取消订阅行更新的数据 214 | unsubUpdateRow = function(tablename, objectId) { 215 | var data = { 216 | appKey: this.applicationId, 217 | tableName: tablename, 218 | objectId: objectId, 219 | action: 'unsub_updateRow' 220 | } 221 | data = this.emitData('client_sub', data) 222 | this.emit(data) 223 | } 224 | 225 | //订阅表删除的数据 226 | deleteTable = function(tablename) { 227 | var data = { 228 | appKey: this.applicationId, 229 | tableName: tablename, 230 | objectId: '', 231 | action: 'deleteTable' 232 | } 233 | data = this.emitData('client_sub', data) 234 | this.emit(data) 235 | } 236 | 237 | //取消订阅表删除的数据 238 | unsubDeleteTable = function(tablename) { 239 | var data = { 240 | appKey: this.applicationId, 241 | tableName: tablename, 242 | objectId: '', 243 | action: 'unsub_deleteTable' 244 | } 245 | data = this.emitData('client_sub', data) 246 | this.emit(data) 247 | } 248 | 249 | //订阅更新数据表的数据 250 | deleteRow = function(tablename, objectId) { 251 | var data = { 252 | appKey: this.applicationId, 253 | tableName: tablename, 254 | objectId: objectId, 255 | action: 'deleteRow' 256 | } 257 | data = this.emitData('client_sub', data) 258 | this.emit(data) 259 | } 260 | 261 | //订阅更新数据表的数据 262 | unsubDeleteRow = function(tablename, objectId) { 263 | var data = { 264 | appKey: this.applicationId, 265 | tableName: tablename, 266 | objectId: objectId, 267 | action: 'unsub_deleteRow' 268 | } 269 | data = this.emitData('client_sub', data) 270 | this.emit(data) 271 | } 272 | 273 | //监听服务器返回的更新数据表的数据,需要用户重写 274 | onUpdateTable = function(tablename, data) {} 275 | 276 | //监听服务器返回的更新数据表的数据,需要用户重写 277 | onUpdateRow = function(tablename, objectId, data) {} 278 | 279 | //监听服务器返回的更新数据表的数据,需要用户重写 280 | onDeleteTable = function(tablename, data) {} 281 | 282 | //监听服务器返回的更新数据表的数据,需要用户重写 283 | onDeleteRow = function(tablename, objectId, data) {} 284 | } 285 | -------------------------------------------------------------------------------- /utils/common.js: -------------------------------------------------------------------------------- 1 | function showTip(sms, icon, fun, t) { 2 | if (!t) { 3 | t = 1000; 4 | } 5 | wx.showToast({ 6 | title: sms, 7 | icon: icon, 8 | duration: t, 9 | success: fun 10 | }) 11 | } 12 | 13 | function showModal(c,t,fun) { 14 | if(!t) 15 | t='提示' 16 | wx.showModal({ 17 | title: t, 18 | content: c, 19 | showCancel:false, 20 | success: fun 21 | }) 22 | } 23 | 24 | 25 | module.exports.showTip = showTip; 26 | module.exports.showModal = showModal; -------------------------------------------------------------------------------- /utils/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.8.3 2 | // http://underscorejs.org 3 | // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // Baseline setup 9 | // -------------- 10 | 11 | // Establish the root object, `window` in the browser, or `exports` on the server. 12 | //var root = this; 13 | 14 | // Save the previous value of the `_` variable. 15 | //var previousUnderscore = root._; 16 | 17 | // Save bytes in the minified (but not gzipped) version: 18 | var ArrayProto = Array.prototype, 19 | ObjProto = Object.prototype, 20 | FuncProto = Function.prototype; 21 | 22 | // Create quick reference variables for speed access to core prototypes. 23 | var 24 | push = ArrayProto.push, 25 | slice = ArrayProto.slice, 26 | toString = ObjProto.toString, 27 | hasOwnProperty = ObjProto.hasOwnProperty; 28 | 29 | // All **ECMAScript 5** native function implementations that we hope to use 30 | // are declared here. 31 | var 32 | nativeIsArray = Array.isArray, 33 | nativeKeys = Object.keys, 34 | nativeBind = FuncProto.bind, 35 | nativeCreate = Object.create; 36 | 37 | // Naked function reference for surrogate-prototype-swapping. 38 | var Ctor = function() {}; 39 | 40 | // Create a safe reference to the Underscore object for use below. 41 | var _ = function(obj) { 42 | if (obj instanceof _) return obj; 43 | if (!(this instanceof _)) return new _(obj); 44 | this._wrapped = obj; 45 | }; 46 | 47 | // Export the Underscore object for **Node.js**, with 48 | // backwards-compatibility for the old `require()` API. If we're in 49 | // the browser, add `_` as a global object. 50 | if (typeof exports !== 'undefined') { 51 | if (typeof module !== 'undefined' && module.exports) { 52 | exports = module.exports = _; 53 | } 54 | exports._ = _; 55 | } else { 56 | root._ = _; 57 | } 58 | 59 | // Current version. 60 | _.VERSION = '1.8.3'; 61 | 62 | // Internal function that returns an efficient (for current engines) version 63 | // of the passed-in callback, to be repeatedly applied in other Underscore 64 | // functions. 65 | var optimizeCb = function(func, context, argCount) { 66 | if (context === void 0) return func; 67 | switch (argCount == null ? 3 : argCount) { 68 | case 1: 69 | return function(value) { 70 | return func.call(context, value); 71 | }; 72 | case 2: 73 | return function(value, other) { 74 | return func.call(context, value, other); 75 | }; 76 | case 3: 77 | return function(value, index, collection) { 78 | return func.call(context, value, index, collection); 79 | }; 80 | case 4: 81 | return function(accumulator, value, index, collection) { 82 | return func.call(context, accumulator, value, index, collection); 83 | }; 84 | } 85 | return function() { 86 | return func.apply(context, arguments); 87 | }; 88 | }; 89 | 90 | // A mostly-internal function to generate callbacks that can be applied 91 | // to each element in a collection, returning the desired result — either 92 | // identity, an arbitrary callback, a property matcher, or a property accessor. 93 | var cb = function(value, context, argCount) { 94 | if (value == null) return _.identity; 95 | if (_.isFunction(value)) return optimizeCb(value, context, argCount); 96 | if (_.isObject(value)) return _.matcher(value); 97 | return _.property(value); 98 | }; 99 | _.iteratee = function(value, context) { 100 | return cb(value, context, Infinity); 101 | }; 102 | 103 | // An internal function for creating assigner functions. 104 | var createAssigner = function(keysFunc, undefinedOnly) { 105 | return function(obj) { 106 | var length = arguments.length; 107 | if (length < 2 || obj == null) return obj; 108 | for (var index = 1; index < length; index++) { 109 | var source = arguments[index], 110 | keys = keysFunc(source), 111 | l = keys.length; 112 | for (var i = 0; i < l; i++) { 113 | var key = keys[i]; 114 | if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; 115 | } 116 | } 117 | return obj; 118 | }; 119 | }; 120 | 121 | // An internal function for creating a new object that inherits from another. 122 | var baseCreate = function(prototype) { 123 | if (!_.isObject(prototype)) return {}; 124 | if (nativeCreate) return nativeCreate(prototype); 125 | Ctor.prototype = prototype; 126 | var result = new Ctor; 127 | Ctor.prototype = null; 128 | return result; 129 | }; 130 | 131 | var property = function(key) { 132 | return function(obj) { 133 | return obj == null ? void 0 : obj[key]; 134 | }; 135 | }; 136 | 137 | // Helper for collection methods to determine whether a collection 138 | // should be iterated as an array or as an object 139 | // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength 140 | // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 141 | var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 142 | var getLength = property('length'); 143 | var isArrayLike = function(collection) { 144 | var length = getLength(collection); 145 | return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; 146 | }; 147 | 148 | // Collection Functions 149 | // -------------------- 150 | 151 | // The cornerstone, an `each` implementation, aka `forEach`. 152 | // Handles raw objects in addition to array-likes. Treats all 153 | // sparse array-likes as if they were dense. 154 | _.each = _.forEach = function(obj, iteratee, context) { 155 | iteratee = optimizeCb(iteratee, context); 156 | var i, length; 157 | if (isArrayLike(obj)) { 158 | for (i = 0, length = obj.length; i < length; i++) { 159 | iteratee(obj[i], i, obj); 160 | } 161 | } else { 162 | var keys = _.keys(obj); 163 | for (i = 0, length = keys.length; i < length; i++) { 164 | iteratee(obj[keys[i]], keys[i], obj); 165 | } 166 | } 167 | return obj; 168 | }; 169 | 170 | // Return the results of applying the iteratee to each element. 171 | _.map = _.collect = function(obj, iteratee, context) { 172 | iteratee = cb(iteratee, context); 173 | var keys = !isArrayLike(obj) && _.keys(obj), 174 | length = (keys || obj).length, 175 | results = Array(length); 176 | for (var index = 0; index < length; index++) { 177 | var currentKey = keys ? keys[index] : index; 178 | results[index] = iteratee(obj[currentKey], currentKey, obj); 179 | } 180 | return results; 181 | }; 182 | 183 | // Create a reducing function iterating left or right. 184 | function createReduce(dir) { 185 | // Optimized iterator function as using arguments.length 186 | // in the main function will deoptimize the, see #1991. 187 | function iterator(obj, iteratee, memo, keys, index, length) { 188 | for (; index >= 0 && index < length; index += dir) { 189 | var currentKey = keys ? keys[index] : index; 190 | memo = iteratee(memo, obj[currentKey], currentKey, obj); 191 | } 192 | return memo; 193 | } 194 | 195 | return function(obj, iteratee, memo, context) { 196 | iteratee = optimizeCb(iteratee, context, 4); 197 | var keys = !isArrayLike(obj) && _.keys(obj), 198 | length = (keys || obj).length, 199 | index = dir > 0 ? 0 : length - 1; 200 | // Determine the initial value if none is provided. 201 | if (arguments.length < 3) { 202 | memo = obj[keys ? keys[index] : index]; 203 | index += dir; 204 | } 205 | return iterator(obj, iteratee, memo, keys, index, length); 206 | }; 207 | } 208 | 209 | // **Reduce** builds up a single result from a list of values, aka `inject`, 210 | // or `foldl`. 211 | _.reduce = _.foldl = _.inject = createReduce(1); 212 | 213 | // The right-associative version of reduce, also known as `foldr`. 214 | _.reduceRight = _.foldr = createReduce(-1); 215 | 216 | // Return the first value which passes a truth test. Aliased as `detect`. 217 | _.find = _.detect = function(obj, predicate, context) { 218 | var key; 219 | if (isArrayLike(obj)) { 220 | key = _.findIndex(obj, predicate, context); 221 | } else { 222 | key = _.findKey(obj, predicate, context); 223 | } 224 | if (key !== void 0 && key !== -1) return obj[key]; 225 | }; 226 | 227 | // Return all the elements that pass a truth test. 228 | // Aliased as `select`. 229 | _.filter = _.select = function(obj, predicate, context) { 230 | var results = []; 231 | predicate = cb(predicate, context); 232 | _.each(obj, function(value, index, list) { 233 | if (predicate(value, index, list)) results.push(value); 234 | }); 235 | return results; 236 | }; 237 | 238 | // Return all the elements for which a truth test fails. 239 | _.reject = function(obj, predicate, context) { 240 | return _.filter(obj, _.negate(cb(predicate)), context); 241 | }; 242 | 243 | // Determine whether all of the elements match a truth test. 244 | // Aliased as `all`. 245 | _.every = _.all = function(obj, predicate, context) { 246 | predicate = cb(predicate, context); 247 | var keys = !isArrayLike(obj) && _.keys(obj), 248 | length = (keys || obj).length; 249 | for (var index = 0; index < length; index++) { 250 | var currentKey = keys ? keys[index] : index; 251 | if (!predicate(obj[currentKey], currentKey, obj)) return false; 252 | } 253 | return true; 254 | }; 255 | 256 | // Determine if at least one element in the object matches a truth test. 257 | // Aliased as `any`. 258 | _.some = _.any = function(obj, predicate, context) { 259 | predicate = cb(predicate, context); 260 | var keys = !isArrayLike(obj) && _.keys(obj), 261 | length = (keys || obj).length; 262 | for (var index = 0; index < length; index++) { 263 | var currentKey = keys ? keys[index] : index; 264 | if (predicate(obj[currentKey], currentKey, obj)) return true; 265 | } 266 | return false; 267 | }; 268 | 269 | // Determine if the array or object contains a given item (using `===`). 270 | // Aliased as `includes` and `include`. 271 | _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { 272 | if (!isArrayLike(obj)) obj = _.values(obj); 273 | if (typeof fromIndex != 'number' || guard) fromIndex = 0; 274 | return _.indexOf(obj, item, fromIndex) >= 0; 275 | }; 276 | 277 | // Invoke a method (with arguments) on every item in a collection. 278 | _.invoke = function(obj, method) { 279 | var args = slice.call(arguments, 2); 280 | var isFunc = _.isFunction(method); 281 | return _.map(obj, function(value) { 282 | var func = isFunc ? method : value[method]; 283 | return func == null ? func : func.apply(value, args); 284 | }); 285 | }; 286 | 287 | // Convenience version of a common use case of `map`: fetching a property. 288 | _.pluck = function(obj, key) { 289 | return _.map(obj, _.property(key)); 290 | }; 291 | 292 | // Convenience version of a common use case of `filter`: selecting only objects 293 | // containing specific `key:value` pairs. 294 | _.where = function(obj, attrs) { 295 | return _.filter(obj, _.matcher(attrs)); 296 | }; 297 | 298 | // Convenience version of a common use case of `find`: getting the first object 299 | // containing specific `key:value` pairs. 300 | _.findWhere = function(obj, attrs) { 301 | return _.find(obj, _.matcher(attrs)); 302 | }; 303 | 304 | // Return the maximum element (or element-based computation). 305 | _.max = function(obj, iteratee, context) { 306 | var result = -Infinity, 307 | lastComputed = -Infinity, 308 | value, computed; 309 | if (iteratee == null && obj != null) { 310 | obj = isArrayLike(obj) ? obj : _.values(obj); 311 | for (var i = 0, length = obj.length; i < length; i++) { 312 | value = obj[i]; 313 | if (value > result) { 314 | result = value; 315 | } 316 | } 317 | } else { 318 | iteratee = cb(iteratee, context); 319 | _.each(obj, function(value, index, list) { 320 | computed = iteratee(value, index, list); 321 | if (computed > lastComputed || computed === -Infinity && result === -Infinity) { 322 | result = value; 323 | lastComputed = computed; 324 | } 325 | }); 326 | } 327 | return result; 328 | }; 329 | 330 | // Return the minimum element (or element-based computation). 331 | _.min = function(obj, iteratee, context) { 332 | var result = Infinity, 333 | lastComputed = Infinity, 334 | value, computed; 335 | if (iteratee == null && obj != null) { 336 | obj = isArrayLike(obj) ? obj : _.values(obj); 337 | for (var i = 0, length = obj.length; i < length; i++) { 338 | value = obj[i]; 339 | if (value < result) { 340 | result = value; 341 | } 342 | } 343 | } else { 344 | iteratee = cb(iteratee, context); 345 | _.each(obj, function(value, index, list) { 346 | computed = iteratee(value, index, list); 347 | if (computed < lastComputed || computed === Infinity && result === Infinity) { 348 | result = value; 349 | lastComputed = computed; 350 | } 351 | }); 352 | } 353 | return result; 354 | }; 355 | 356 | // Shuffle a collection, using the modern version of the 357 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 358 | _.shuffle = function(obj) { 359 | var set = isArrayLike(obj) ? obj : _.values(obj); 360 | var length = set.length; 361 | var shuffled = Array(length); 362 | for (var index = 0, rand; index < length; index++) { 363 | rand = _.random(0, index); 364 | if (rand !== index) shuffled[index] = shuffled[rand]; 365 | shuffled[rand] = set[index]; 366 | } 367 | return shuffled; 368 | }; 369 | 370 | // Sample **n** random values from a collection. 371 | // If **n** is not specified, returns a single random element. 372 | // The internal `guard` argument allows it to work with `map`. 373 | _.sample = function(obj, n, guard) { 374 | if (n == null || guard) { 375 | if (!isArrayLike(obj)) obj = _.values(obj); 376 | return obj[_.random(obj.length - 1)]; 377 | } 378 | return _.shuffle(obj).slice(0, Math.max(0, n)); 379 | }; 380 | 381 | // Sort the object's values by a criterion produced by an iteratee. 382 | _.sortBy = function(obj, iteratee, context) { 383 | iteratee = cb(iteratee, context); 384 | return _.pluck(_.map(obj, function(value, index, list) { 385 | return { 386 | value: value, 387 | index: index, 388 | criteria: iteratee(value, index, list) 389 | }; 390 | }).sort(function(left, right) { 391 | var a = left.criteria; 392 | var b = right.criteria; 393 | if (a !== b) { 394 | if (a > b || a === void 0) return 1; 395 | if (a < b || b === void 0) return -1; 396 | } 397 | return left.index - right.index; 398 | }), 'value'); 399 | }; 400 | 401 | // An internal function used for aggregate "group by" operations. 402 | var group = function(behavior) { 403 | return function(obj, iteratee, context) { 404 | var result = {}; 405 | iteratee = cb(iteratee, context); 406 | _.each(obj, function(value, index) { 407 | var key = iteratee(value, index, obj); 408 | behavior(result, value, key); 409 | }); 410 | return result; 411 | }; 412 | }; 413 | 414 | // Groups the object's values by a criterion. Pass either a string attribute 415 | // to group by, or a function that returns the criterion. 416 | _.groupBy = group(function(result, value, key) { 417 | if (_.has(result, key)) result[key].push(value); 418 | else result[key] = [value]; 419 | }); 420 | 421 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 422 | // when you know that your index values will be unique. 423 | _.indexBy = group(function(result, value, key) { 424 | result[key] = value; 425 | }); 426 | 427 | // Counts instances of an object that group by a certain criterion. Pass 428 | // either a string attribute to count by, or a function that returns the 429 | // criterion. 430 | _.countBy = group(function(result, value, key) { 431 | if (_.has(result, key)) result[key]++; 432 | else result[key] = 1; 433 | }); 434 | 435 | // Safely create a real, live array from anything iterable. 436 | _.toArray = function(obj) { 437 | if (!obj) return []; 438 | if (_.isArray(obj)) return slice.call(obj); 439 | if (isArrayLike(obj)) return _.map(obj, _.identity); 440 | return _.values(obj); 441 | }; 442 | 443 | // Return the number of elements in an object. 444 | _.size = function(obj) { 445 | if (obj == null) return 0; 446 | return isArrayLike(obj) ? obj.length : _.keys(obj).length; 447 | }; 448 | 449 | // Split a collection into two arrays: one whose elements all satisfy the given 450 | // predicate, and one whose elements all do not satisfy the predicate. 451 | _.partition = function(obj, predicate, context) { 452 | predicate = cb(predicate, context); 453 | var pass = [], 454 | fail = []; 455 | _.each(obj, function(value, key, obj) { 456 | (predicate(value, key, obj) ? pass : fail).push(value); 457 | }); 458 | return [pass, fail]; 459 | }; 460 | 461 | // Array Functions 462 | // --------------- 463 | 464 | // Get the first element of an array. Passing **n** will return the first N 465 | // values in the array. Aliased as `head` and `take`. The **guard** check 466 | // allows it to work with `_.map`. 467 | _.first = _.head = _.take = function(array, n, guard) { 468 | if (array == null) return void 0; 469 | if (n == null || guard) return array[0]; 470 | return _.initial(array, array.length - n); 471 | }; 472 | 473 | // Returns everything but the last entry of the array. Especially useful on 474 | // the arguments object. Passing **n** will return all the values in 475 | // the array, excluding the last N. 476 | _.initial = function(array, n, guard) { 477 | return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); 478 | }; 479 | 480 | // Get the last element of an array. Passing **n** will return the last N 481 | // values in the array. 482 | _.last = function(array, n, guard) { 483 | if (array == null) return void 0; 484 | if (n == null || guard) return array[array.length - 1]; 485 | return _.rest(array, Math.max(0, array.length - n)); 486 | }; 487 | 488 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 489 | // Especially useful on the arguments object. Passing an **n** will return 490 | // the rest N values in the array. 491 | _.rest = _.tail = _.drop = function(array, n, guard) { 492 | return slice.call(array, n == null || guard ? 1 : n); 493 | }; 494 | 495 | // Trim out all falsy values from an array. 496 | _.compact = function(array) { 497 | return _.filter(array, _.identity); 498 | }; 499 | 500 | // Internal implementation of a recursive `flatten` function. 501 | var flatten = function(input, shallow, strict, startIndex) { 502 | var output = [], 503 | idx = 0; 504 | for (var i = startIndex || 0, length = getLength(input); i < length; i++) { 505 | var value = input[i]; 506 | if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { 507 | //flatten current level of array or arguments object 508 | if (!shallow) value = flatten(value, shallow, strict); 509 | var j = 0, 510 | len = value.length; 511 | output.length += len; 512 | while (j < len) { 513 | output[idx++] = value[j++]; 514 | } 515 | } else if (!strict) { 516 | output[idx++] = value; 517 | } 518 | } 519 | return output; 520 | }; 521 | 522 | // Flatten out an array, either recursively (by default), or just one level. 523 | _.flatten = function(array, shallow) { 524 | return flatten(array, shallow, false); 525 | }; 526 | 527 | // Return a version of the array that does not contain the specified value(s). 528 | _.without = function(array) { 529 | return _.difference(array, slice.call(arguments, 1)); 530 | }; 531 | 532 | // Produce a duplicate-free version of the array. If the array has already 533 | // been sorted, you have the option of using a faster algorithm. 534 | // Aliased as `unique`. 535 | _.uniq = _.unique = function(array, isSorted, iteratee, context) { 536 | if (!_.isBoolean(isSorted)) { 537 | context = iteratee; 538 | iteratee = isSorted; 539 | isSorted = false; 540 | } 541 | if (iteratee != null) iteratee = cb(iteratee, context); 542 | var result = []; 543 | var seen = []; 544 | for (var i = 0, length = getLength(array); i < length; i++) { 545 | var value = array[i], 546 | computed = iteratee ? iteratee(value, i, array) : value; 547 | if (isSorted) { 548 | if (!i || seen !== computed) result.push(value); 549 | seen = computed; 550 | } else if (iteratee) { 551 | if (!_.contains(seen, computed)) { 552 | seen.push(computed); 553 | result.push(value); 554 | } 555 | } else if (!_.contains(result, value)) { 556 | result.push(value); 557 | } 558 | } 559 | return result; 560 | }; 561 | 562 | // Produce an array that contains the union: each distinct element from all of 563 | // the passed-in arrays. 564 | _.union = function() { 565 | return _.uniq(flatten(arguments, true, true)); 566 | }; 567 | 568 | // Produce an array that contains every item shared between all the 569 | // passed-in arrays. 570 | _.intersection = function(array) { 571 | var result = []; 572 | var argsLength = arguments.length; 573 | for (var i = 0, length = getLength(array); i < length; i++) { 574 | var item = array[i]; 575 | if (_.contains(result, item)) continue; 576 | for (var j = 1; j < argsLength; j++) { 577 | if (!_.contains(arguments[j], item)) break; 578 | } 579 | if (j === argsLength) result.push(item); 580 | } 581 | return result; 582 | }; 583 | 584 | // Take the difference between one array and a number of other arrays. 585 | // Only the elements present in just the first array will remain. 586 | _.difference = function(array) { 587 | var rest = flatten(arguments, true, true, 1); 588 | return _.filter(array, function(value) { 589 | return !_.contains(rest, value); 590 | }); 591 | }; 592 | 593 | // Zip together multiple lists into a single array -- elements that share 594 | // an index go together. 595 | _.zip = function() { 596 | return _.unzip(arguments); 597 | }; 598 | 599 | // Complement of _.zip. Unzip accepts an array of arrays and groups 600 | // each array's elements on shared indices 601 | _.unzip = function(array) { 602 | var length = array && _.max(array, getLength).length || 0; 603 | var result = Array(length); 604 | 605 | for (var index = 0; index < length; index++) { 606 | result[index] = _.pluck(array, index); 607 | } 608 | return result; 609 | }; 610 | 611 | // Converts lists into objects. Pass either a single array of `[key, value]` 612 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 613 | // the corresponding values. 614 | _.object = function(list, values) { 615 | var result = {}; 616 | for (var i = 0, length = getLength(list); i < length; i++) { 617 | if (values) { 618 | result[list[i]] = values[i]; 619 | } else { 620 | result[list[i][0]] = list[i][1]; 621 | } 622 | } 623 | return result; 624 | }; 625 | 626 | // Generator function to create the findIndex and findLastIndex functions 627 | function createPredicateIndexFinder(dir) { 628 | return function(array, predicate, context) { 629 | predicate = cb(predicate, context); 630 | var length = getLength(array); 631 | var index = dir > 0 ? 0 : length - 1; 632 | for (; index >= 0 && index < length; index += dir) { 633 | if (predicate(array[index], index, array)) return index; 634 | } 635 | return -1; 636 | }; 637 | } 638 | 639 | // Returns the first index on an array-like that passes a predicate test 640 | _.findIndex = createPredicateIndexFinder(1); 641 | _.findLastIndex = createPredicateIndexFinder(-1); 642 | 643 | // Use a comparator function to figure out the smallest index at which 644 | // an object should be inserted so as to maintain order. Uses binary search. 645 | _.sortedIndex = function(array, obj, iteratee, context) { 646 | iteratee = cb(iteratee, context, 1); 647 | var value = iteratee(obj); 648 | var low = 0, 649 | high = getLength(array); 650 | while (low < high) { 651 | var mid = Math.floor((low + high) / 2); 652 | if (iteratee(array[mid]) < value) low = mid + 1; 653 | else high = mid; 654 | } 655 | return low; 656 | }; 657 | 658 | // Generator function to create the indexOf and lastIndexOf functions 659 | function createIndexFinder(dir, predicateFind, sortedIndex) { 660 | return function(array, item, idx) { 661 | var i = 0, 662 | length = getLength(array); 663 | if (typeof idx == 'number') { 664 | if (dir > 0) { 665 | i = idx >= 0 ? idx : Math.max(idx + length, i); 666 | } else { 667 | length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; 668 | } 669 | } else if (sortedIndex && idx && length) { 670 | idx = sortedIndex(array, item); 671 | return array[idx] === item ? idx : -1; 672 | } 673 | if (item !== item) { 674 | idx = predicateFind(slice.call(array, i, length), _.isNaN); 675 | return idx >= 0 ? idx + i : -1; 676 | } 677 | for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { 678 | if (array[idx] === item) return idx; 679 | } 680 | return -1; 681 | }; 682 | } 683 | 684 | // Return the position of the first occurrence of an item in an array, 685 | // or -1 if the item is not included in the array. 686 | // If the array is large and already in sort order, pass `true` 687 | // for **isSorted** to use binary search. 688 | _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); 689 | _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); 690 | 691 | // Generate an integer Array containing an arithmetic progression. A port of 692 | // the native Python `range()` function. See 693 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 694 | _.range = function(start, stop, step) { 695 | if (stop == null) { 696 | stop = start || 0; 697 | start = 0; 698 | } 699 | step = step || 1; 700 | 701 | var length = Math.max(Math.ceil((stop - start) / step), 0); 702 | var range = Array(length); 703 | 704 | for (var idx = 0; idx < length; idx++, start += step) { 705 | range[idx] = start; 706 | } 707 | 708 | return range; 709 | }; 710 | 711 | // Function (ahem) Functions 712 | // ------------------ 713 | 714 | // Determines whether to execute a function as a constructor 715 | // or a normal function with the provided arguments 716 | var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { 717 | if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 718 | var self = baseCreate(sourceFunc.prototype); 719 | var result = sourceFunc.apply(self, args); 720 | if (_.isObject(result)) return result; 721 | return self; 722 | }; 723 | 724 | // Create a function bound to a given object (assigning `this`, and arguments, 725 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 726 | // available. 727 | _.bind = function(func, context) { 728 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 729 | if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); 730 | var args = slice.call(arguments, 2); 731 | var bound = function() { 732 | return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); 733 | }; 734 | return bound; 735 | }; 736 | 737 | // Partially apply a function by creating a version that has had some of its 738 | // arguments pre-filled, without changing its dynamic `this` context. _ acts 739 | // as a placeholder, allowing any combination of arguments to be pre-filled. 740 | _.partial = function(func) { 741 | var boundArgs = slice.call(arguments, 1); 742 | var bound = function() { 743 | var position = 0, 744 | length = boundArgs.length; 745 | var args = Array(length); 746 | for (var i = 0; i < length; i++) { 747 | args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; 748 | } 749 | while (position < arguments.length) args.push(arguments[position++]); 750 | return executeBound(func, bound, this, this, args); 751 | }; 752 | return bound; 753 | }; 754 | 755 | // Bind a number of an object's methods to that object. Remaining arguments 756 | // are the method names to be bound. Useful for ensuring that all callbacks 757 | // defined on an object belong to it. 758 | _.bindAll = function(obj) { 759 | var i, length = arguments.length, 760 | key; 761 | if (length <= 1) throw new Error('bindAll must be passed function names'); 762 | for (i = 1; i < length; i++) { 763 | key = arguments[i]; 764 | obj[key] = _.bind(obj[key], obj); 765 | } 766 | return obj; 767 | }; 768 | 769 | // Memoize an expensive function by storing its results. 770 | _.memoize = function(func, hasher) { 771 | var memoize = function(key) { 772 | var cache = memoize.cache; 773 | var address = '' + (hasher ? hasher.apply(this, arguments) : key); 774 | if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); 775 | return cache[address]; 776 | }; 777 | memoize.cache = {}; 778 | return memoize; 779 | }; 780 | 781 | // Delays a function for the given number of milliseconds, and then calls 782 | // it with the arguments supplied. 783 | _.delay = function(func, wait) { 784 | var args = slice.call(arguments, 2); 785 | return setTimeout(function() { 786 | return func.apply(null, args); 787 | }, wait); 788 | }; 789 | 790 | // Defers a function, scheduling it to run after the current call stack has 791 | // cleared. 792 | _.defer = _.partial(_.delay, _, 1); 793 | 794 | // Returns a function, that, when invoked, will only be triggered at most once 795 | // during a given window of time. Normally, the throttled function will run 796 | // as much as it can, without ever going more than once per `wait` duration; 797 | // but if you'd like to disable the execution on the leading edge, pass 798 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 799 | _.throttle = function(func, wait, options) { 800 | var context, args, result; 801 | var timeout = null; 802 | var previous = 0; 803 | if (!options) options = {}; 804 | var later = function() { 805 | previous = options.leading === false ? 0 : _.now(); 806 | timeout = null; 807 | result = func.apply(context, args); 808 | if (!timeout) context = args = null; 809 | }; 810 | return function() { 811 | var now = _.now(); 812 | if (!previous && options.leading === false) previous = now; 813 | var remaining = wait - (now - previous); 814 | context = this; 815 | args = arguments; 816 | if (remaining <= 0 || remaining > wait) { 817 | if (timeout) { 818 | clearTimeout(timeout); 819 | timeout = null; 820 | } 821 | previous = now; 822 | result = func.apply(context, args); 823 | if (!timeout) context = args = null; 824 | } else if (!timeout && options.trailing !== false) { 825 | timeout = setTimeout(later, remaining); 826 | } 827 | return result; 828 | }; 829 | }; 830 | 831 | // Returns a function, that, as long as it continues to be invoked, will not 832 | // be triggered. The function will be called after it stops being called for 833 | // N milliseconds. If `immediate` is passed, trigger the function on the 834 | // leading edge, instead of the trailing. 835 | _.debounce = function(func, wait, immediate) { 836 | var timeout, args, context, timestamp, result; 837 | 838 | var later = function() { 839 | var last = _.now() - timestamp; 840 | 841 | if (last < wait && last >= 0) { 842 | timeout = setTimeout(later, wait - last); 843 | } else { 844 | timeout = null; 845 | if (!immediate) { 846 | result = func.apply(context, args); 847 | if (!timeout) context = args = null; 848 | } 849 | } 850 | }; 851 | 852 | return function() { 853 | context = this; 854 | args = arguments; 855 | timestamp = _.now(); 856 | var callNow = immediate && !timeout; 857 | if (!timeout) timeout = setTimeout(later, wait); 858 | if (callNow) { 859 | result = func.apply(context, args); 860 | context = args = null; 861 | } 862 | 863 | return result; 864 | }; 865 | }; 866 | 867 | // Returns the first function passed as an argument to the second, 868 | // allowing you to adjust arguments, run code before and after, and 869 | // conditionally execute the original function. 870 | _.wrap = function(func, wrapper) { 871 | return _.partial(wrapper, func); 872 | }; 873 | 874 | // Returns a negated version of the passed-in predicate. 875 | _.negate = function(predicate) { 876 | return function() { 877 | return !predicate.apply(this, arguments); 878 | }; 879 | }; 880 | 881 | // Returns a function that is the composition of a list of functions, each 882 | // consuming the return value of the function that follows. 883 | _.compose = function() { 884 | var args = arguments; 885 | var start = args.length - 1; 886 | return function() { 887 | var i = start; 888 | var result = args[start].apply(this, arguments); 889 | while (i--) result = args[i].call(this, result); 890 | return result; 891 | }; 892 | }; 893 | 894 | // Returns a function that will only be executed on and after the Nth call. 895 | _.after = function(times, func) { 896 | return function() { 897 | if (--times < 1) { 898 | return func.apply(this, arguments); 899 | } 900 | }; 901 | }; 902 | 903 | // Returns a function that will only be executed up to (but not including) the Nth call. 904 | _.before = function(times, func) { 905 | var memo; 906 | return function() { 907 | if (--times > 0) { 908 | memo = func.apply(this, arguments); 909 | } 910 | if (times <= 1) func = null; 911 | return memo; 912 | }; 913 | }; 914 | 915 | // Returns a function that will be executed at most one time, no matter how 916 | // often you call it. Useful for lazy initialization. 917 | _.once = _.partial(_.before, 2); 918 | 919 | // Object Functions 920 | // ---------------- 921 | 922 | // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. 923 | var hasEnumBug = !{ toString: null }.propertyIsEnumerable('toString'); 924 | var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 925 | 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString' 926 | ]; 927 | 928 | function collectNonEnumProps(obj, keys) { 929 | var nonEnumIdx = nonEnumerableProps.length; 930 | var constructor = obj.constructor; 931 | var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; 932 | 933 | // Constructor is a special case. 934 | var prop = 'constructor'; 935 | if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); 936 | 937 | while (nonEnumIdx--) { 938 | prop = nonEnumerableProps[nonEnumIdx]; 939 | if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { 940 | keys.push(prop); 941 | } 942 | } 943 | } 944 | 945 | // Retrieve the names of an object's own properties. 946 | // Delegates to **ECMAScript 5**'s native `Object.keys` 947 | _.keys = function(obj) { 948 | if (!_.isObject(obj)) return []; 949 | if (nativeKeys) return nativeKeys(obj); 950 | var keys = []; 951 | for (var key in obj) 952 | if (_.has(obj, key)) keys.push(key); 953 | // Ahem, IE < 9. 954 | if (hasEnumBug) collectNonEnumProps(obj, keys); 955 | return keys; 956 | }; 957 | 958 | // Retrieve all the property names of an object. 959 | _.allKeys = function(obj) { 960 | if (!_.isObject(obj)) return []; 961 | var keys = []; 962 | for (var key in obj) keys.push(key); 963 | // Ahem, IE < 9. 964 | if (hasEnumBug) collectNonEnumProps(obj, keys); 965 | return keys; 966 | }; 967 | 968 | // Retrieve the values of an object's properties. 969 | _.values = function(obj) { 970 | var keys = _.keys(obj); 971 | var length = keys.length; 972 | var values = Array(length); 973 | for (var i = 0; i < length; i++) { 974 | values[i] = obj[keys[i]]; 975 | } 976 | return values; 977 | }; 978 | 979 | // Returns the results of applying the iteratee to each element of the object 980 | // In contrast to _.map it returns an object 981 | _.mapObject = function(obj, iteratee, context) { 982 | iteratee = cb(iteratee, context); 983 | var keys = _.keys(obj), 984 | length = keys.length, 985 | results = {}, 986 | currentKey; 987 | for (var index = 0; index < length; index++) { 988 | currentKey = keys[index]; 989 | results[currentKey] = iteratee(obj[currentKey], currentKey, obj); 990 | } 991 | return results; 992 | }; 993 | 994 | // Convert an object into a list of `[key, value]` pairs. 995 | _.pairs = function(obj) { 996 | var keys = _.keys(obj); 997 | var length = keys.length; 998 | var pairs = Array(length); 999 | for (var i = 0; i < length; i++) { 1000 | pairs[i] = [keys[i], obj[keys[i]]]; 1001 | } 1002 | return pairs; 1003 | }; 1004 | 1005 | // Invert the keys and values of an object. The values must be serializable. 1006 | _.invert = function(obj) { 1007 | var result = {}; 1008 | var keys = _.keys(obj); 1009 | for (var i = 0, length = keys.length; i < length; i++) { 1010 | result[obj[keys[i]]] = keys[i]; 1011 | } 1012 | return result; 1013 | }; 1014 | 1015 | // Return a sorted list of the function names available on the object. 1016 | // Aliased as `methods` 1017 | _.functions = _.methods = function(obj) { 1018 | var names = []; 1019 | for (var key in obj) { 1020 | if (_.isFunction(obj[key])) names.push(key); 1021 | } 1022 | return names.sort(); 1023 | }; 1024 | 1025 | // Extend a given object with all the properties in passed-in object(s). 1026 | _.extend = createAssigner(_.allKeys); 1027 | 1028 | // Assigns a given object with all the own properties in the passed-in object(s) 1029 | // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 1030 | _.extendOwn = _.assign = createAssigner(_.keys); 1031 | 1032 | // Returns the first key on an object that passes a predicate test 1033 | _.findKey = function(obj, predicate, context) { 1034 | predicate = cb(predicate, context); 1035 | var keys = _.keys(obj), 1036 | key; 1037 | for (var i = 0, length = keys.length; i < length; i++) { 1038 | key = keys[i]; 1039 | if (predicate(obj[key], key, obj)) return key; 1040 | } 1041 | }; 1042 | 1043 | // Return a copy of the object only containing the whitelisted properties. 1044 | _.pick = function(object, oiteratee, context) { 1045 | var result = {}, 1046 | obj = object, 1047 | iteratee, keys; 1048 | if (obj == null) return result; 1049 | if (_.isFunction(oiteratee)) { 1050 | keys = _.allKeys(obj); 1051 | iteratee = optimizeCb(oiteratee, context); 1052 | } else { 1053 | keys = flatten(arguments, false, false, 1); 1054 | iteratee = function(value, key, obj) { return key in obj; }; 1055 | obj = Object(obj); 1056 | } 1057 | for (var i = 0, length = keys.length; i < length; i++) { 1058 | var key = keys[i]; 1059 | var value = obj[key]; 1060 | if (iteratee(value, key, obj)) result[key] = value; 1061 | } 1062 | return result; 1063 | }; 1064 | 1065 | // Return a copy of the object without the blacklisted properties. 1066 | _.omit = function(obj, iteratee, context) { 1067 | if (_.isFunction(iteratee)) { 1068 | iteratee = _.negate(iteratee); 1069 | } else { 1070 | var keys = _.map(flatten(arguments, false, false, 1), String); 1071 | iteratee = function(value, key) { 1072 | return !_.contains(keys, key); 1073 | }; 1074 | } 1075 | return _.pick(obj, iteratee, context); 1076 | }; 1077 | 1078 | // Fill in a given object with default properties. 1079 | _.defaults = createAssigner(_.allKeys, true); 1080 | 1081 | // Creates an object that inherits from the given prototype object. 1082 | // If additional properties are provided then they will be added to the 1083 | // created object. 1084 | _.create = function(prototype, props) { 1085 | var result = baseCreate(prototype); 1086 | if (props) _.extendOwn(result, props); 1087 | return result; 1088 | }; 1089 | 1090 | // Create a (shallow-cloned) duplicate of an object. 1091 | _.clone = function(obj) { 1092 | if (!_.isObject(obj)) return obj; 1093 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 1094 | }; 1095 | 1096 | // Invokes interceptor with the obj, and then returns obj. 1097 | // The primary purpose of this method is to "tap into" a method chain, in 1098 | // order to perform operations on intermediate results within the chain. 1099 | _.tap = function(obj, interceptor) { 1100 | interceptor(obj); 1101 | return obj; 1102 | }; 1103 | 1104 | // Returns whether an object has a given set of `key:value` pairs. 1105 | _.isMatch = function(object, attrs) { 1106 | var keys = _.keys(attrs), 1107 | length = keys.length; 1108 | if (object == null) return !length; 1109 | var obj = Object(object); 1110 | for (var i = 0; i < length; i++) { 1111 | var key = keys[i]; 1112 | if (attrs[key] !== obj[key] || !(key in obj)) return false; 1113 | } 1114 | return true; 1115 | }; 1116 | 1117 | 1118 | // Internal recursive comparison function for `isEqual`. 1119 | var eq = function(a, b, aStack, bStack) { 1120 | // Identical objects are equal. `0 === -0`, but they aren't identical. 1121 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 1122 | if (a === b) return a !== 0 || 1 / a === 1 / b; 1123 | // A strict comparison is necessary because `null == undefined`. 1124 | if (a == null || b == null) return a === b; 1125 | // Unwrap any wrapped objects. 1126 | if (a instanceof _) a = a._wrapped; 1127 | if (b instanceof _) b = b._wrapped; 1128 | // Compare `[[Class]]` names. 1129 | var className = toString.call(a); 1130 | if (className !== toString.call(b)) return false; 1131 | switch (className) { 1132 | // Strings, numbers, regular expressions, dates, and booleans are compared by value. 1133 | case '[object RegExp]': 1134 | // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') 1135 | case '[object String]': 1136 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 1137 | // equivalent to `new String("5")`. 1138 | return '' + a === '' + b; 1139 | case '[object Number]': 1140 | // `NaN`s are equivalent, but non-reflexive. 1141 | // Object(NaN) is equivalent to NaN 1142 | if (+a !== +a) return +b !== +b; 1143 | // An `egal` comparison is performed for other numeric values. 1144 | return +a === 0 ? 1 / +a === 1 / b : +a === +b; 1145 | case '[object Date]': 1146 | case '[object Boolean]': 1147 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 1148 | // millisecond representations. Note that invalid dates with millisecond representations 1149 | // of `NaN` are not equivalent. 1150 | return +a === +b; 1151 | } 1152 | 1153 | var areArrays = className === '[object Array]'; 1154 | if (!areArrays) { 1155 | if (typeof a != 'object' || typeof b != 'object') return false; 1156 | 1157 | // Objects with different constructors are not equivalent, but `Object`s or `Array`s 1158 | // from different frames are. 1159 | var aCtor = a.constructor, 1160 | bCtor = b.constructor; 1161 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && 1162 | _.isFunction(bCtor) && bCtor instanceof bCtor) && 1163 | ('constructor' in a && 'constructor' in b)) { 1164 | return false; 1165 | } 1166 | } 1167 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 1168 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 1169 | 1170 | // Initializing stack of traversed objects. 1171 | // It's done here since we only need them for objects and arrays comparison. 1172 | aStack = aStack || []; 1173 | bStack = bStack || []; 1174 | var length = aStack.length; 1175 | while (length--) { 1176 | // Linear search. Performance is inversely proportional to the number of 1177 | // unique nested structures. 1178 | if (aStack[length] === a) return bStack[length] === b; 1179 | } 1180 | 1181 | // Add the first object to the stack of traversed objects. 1182 | aStack.push(a); 1183 | bStack.push(b); 1184 | 1185 | // Recursively compare objects and arrays. 1186 | if (areArrays) { 1187 | // Compare array lengths to determine if a deep comparison is necessary. 1188 | length = a.length; 1189 | if (length !== b.length) return false; 1190 | // Deep compare the contents, ignoring non-numeric properties. 1191 | while (length--) { 1192 | if (!eq(a[length], b[length], aStack, bStack)) return false; 1193 | } 1194 | } else { 1195 | // Deep compare objects. 1196 | var keys = _.keys(a), 1197 | key; 1198 | length = keys.length; 1199 | // Ensure that both objects contain the same number of properties before comparing deep equality. 1200 | if (_.keys(b).length !== length) return false; 1201 | while (length--) { 1202 | // Deep compare each member 1203 | key = keys[length]; 1204 | if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; 1205 | } 1206 | } 1207 | // Remove the first object from the stack of traversed objects. 1208 | aStack.pop(); 1209 | bStack.pop(); 1210 | return true; 1211 | }; 1212 | 1213 | // Perform a deep comparison to check if two objects are equal. 1214 | _.isEqual = function(a, b) { 1215 | return eq(a, b); 1216 | }; 1217 | 1218 | // Is a given array, string, or object empty? 1219 | // An "empty" object has no enumerable own-properties. 1220 | _.isEmpty = function(obj) { 1221 | if (obj == null) return true; 1222 | if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; 1223 | return _.keys(obj).length === 0; 1224 | }; 1225 | 1226 | // Is a given value a DOM element? 1227 | _.isElement = function(obj) { 1228 | return !!(obj && obj.nodeType === 1); 1229 | }; 1230 | 1231 | // Is a given value an array? 1232 | // Delegates to ECMA5's native Array.isArray 1233 | _.isArray = nativeIsArray || function(obj) { 1234 | return toString.call(obj) === '[object Array]'; 1235 | }; 1236 | 1237 | // Is a given variable an object? 1238 | _.isObject = function(obj) { 1239 | var type = typeof obj; 1240 | return type === 'function' || type === 'object' && !!obj; 1241 | }; 1242 | 1243 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. 1244 | _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { 1245 | _['is' + name] = function(obj) { 1246 | return toString.call(obj) === '[object ' + name + ']'; 1247 | }; 1248 | }); 1249 | 1250 | // Define a fallback version of the method in browsers (ahem, IE < 9), where 1251 | // there isn't any inspectable "Arguments" type. 1252 | if (!_.isArguments(arguments)) { 1253 | _.isArguments = function(obj) { 1254 | return _.has(obj, 'callee'); 1255 | }; 1256 | } 1257 | 1258 | // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, 1259 | // IE 11 (#1621), and in Safari 8 (#1929). 1260 | if (typeof /./ != 'function' && typeof Int8Array != 'object') { 1261 | _.isFunction = function(obj) { 1262 | return typeof obj == 'function' || false; 1263 | }; 1264 | } 1265 | 1266 | // Is a given object a finite number? 1267 | _.isFinite = function(obj) { 1268 | return isFinite(obj) && !isNaN(parseFloat(obj)); 1269 | }; 1270 | 1271 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 1272 | _.isNaN = function(obj) { 1273 | return _.isNumber(obj) && obj !== +obj; 1274 | }; 1275 | 1276 | // Is a given value a boolean? 1277 | _.isBoolean = function(obj) { 1278 | return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; 1279 | }; 1280 | 1281 | // Is a given value equal to null? 1282 | _.isNull = function(obj) { 1283 | return obj === null; 1284 | }; 1285 | 1286 | // Is a given variable undefined? 1287 | _.isUndefined = function(obj) { 1288 | return obj === void 0; 1289 | }; 1290 | 1291 | // Shortcut function for checking if an object has a given property directly 1292 | // on itself (in other words, not on a prototype). 1293 | _.has = function(obj, key) { 1294 | return obj != null && hasOwnProperty.call(obj, key); 1295 | }; 1296 | 1297 | // Utility Functions 1298 | // ----------------- 1299 | 1300 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1301 | // previous owner. Returns a reference to the Underscore object. 1302 | _.noConflict = function() { 1303 | root._ = previousUnderscore; 1304 | return this; 1305 | }; 1306 | 1307 | // Keep the identity function around for default iteratees. 1308 | _.identity = function(value) { 1309 | return value; 1310 | }; 1311 | 1312 | // Predicate-generating functions. Often useful outside of Underscore. 1313 | _.constant = function(value) { 1314 | return function() { 1315 | return value; 1316 | }; 1317 | }; 1318 | 1319 | _.noop = function() {}; 1320 | 1321 | _.property = property; 1322 | 1323 | // Generates a function for a given object that returns a given property. 1324 | _.propertyOf = function(obj) { 1325 | return obj == null ? function() {} : function(key) { 1326 | return obj[key]; 1327 | }; 1328 | }; 1329 | 1330 | // Returns a predicate for checking whether an object has a given set of 1331 | // `key:value` pairs. 1332 | _.matcher = _.matches = function(attrs) { 1333 | attrs = _.extendOwn({}, attrs); 1334 | return function(obj) { 1335 | return _.isMatch(obj, attrs); 1336 | }; 1337 | }; 1338 | 1339 | // Run a function **n** times. 1340 | _.times = function(n, iteratee, context) { 1341 | var accum = Array(Math.max(0, n)); 1342 | iteratee = optimizeCb(iteratee, context, 1); 1343 | for (var i = 0; i < n; i++) accum[i] = iteratee(i); 1344 | return accum; 1345 | }; 1346 | 1347 | // Return a random integer between min and max (inclusive). 1348 | _.random = function(min, max) { 1349 | if (max == null) { 1350 | max = min; 1351 | min = 0; 1352 | } 1353 | return min + Math.floor(Math.random() * (max - min + 1)); 1354 | }; 1355 | 1356 | // A (possibly faster) way to get the current timestamp as an integer. 1357 | _.now = Date.now || function() { 1358 | return new Date().getTime(); 1359 | }; 1360 | 1361 | // List of HTML entities for escaping. 1362 | var escapeMap = { 1363 | '&': '&', 1364 | '<': '<', 1365 | '>': '>', 1366 | '"': '"', 1367 | "'": ''', 1368 | '`': '`' 1369 | }; 1370 | var unescapeMap = _.invert(escapeMap); 1371 | 1372 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1373 | var createEscaper = function(map) { 1374 | var escaper = function(match) { 1375 | return map[match]; 1376 | }; 1377 | // Regexes for identifying a key that needs to be escaped 1378 | var source = '(?:' + _.keys(map).join('|') + ')'; 1379 | var testRegexp = RegExp(source); 1380 | var replaceRegexp = RegExp(source, 'g'); 1381 | return function(string) { 1382 | string = string == null ? '' : '' + string; 1383 | return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; 1384 | }; 1385 | }; 1386 | _.escape = createEscaper(escapeMap); 1387 | _.unescape = createEscaper(unescapeMap); 1388 | 1389 | // If the value of the named `property` is a function then invoke it with the 1390 | // `object` as context; otherwise, return it. 1391 | _.result = function(object, property, fallback) { 1392 | var value = object == null ? void 0 : object[property]; 1393 | if (value === void 0) { 1394 | value = fallback; 1395 | } 1396 | return _.isFunction(value) ? value.call(object) : value; 1397 | }; 1398 | 1399 | // Generate a unique integer id (unique within the entire client session). 1400 | // Useful for temporary DOM ids. 1401 | var idCounter = 0; 1402 | _.uniqueId = function(prefix) { 1403 | var id = ++idCounter + ''; 1404 | return prefix ? prefix + id : id; 1405 | }; 1406 | 1407 | // By default, Underscore uses ERB-style template delimiters, change the 1408 | // following template settings to use alternative delimiters. 1409 | _.templateSettings = { 1410 | evaluate: /<%([\s\S]+?)%>/g, 1411 | interpolate: /<%=([\s\S]+?)%>/g, 1412 | escape: /<%-([\s\S]+?)%>/g 1413 | }; 1414 | 1415 | // When customizing `templateSettings`, if you don't want to define an 1416 | // interpolation, evaluation or escaping regex, we need one that is 1417 | // guaranteed not to match. 1418 | var noMatch = /(.)^/; 1419 | 1420 | // Certain characters need to be escaped so that they can be put into a 1421 | // string literal. 1422 | var escapes = { 1423 | "'": "'", 1424 | '\\': '\\', 1425 | '\r': 'r', 1426 | '\n': 'n', 1427 | '\u2028': 'u2028', 1428 | '\u2029': 'u2029' 1429 | }; 1430 | 1431 | var escaper = /\\|'|\r|\n|\u2028|\u2029/g; 1432 | 1433 | var escapeChar = function(match) { 1434 | return '\\' + escapes[match]; 1435 | }; 1436 | 1437 | // JavaScript micro-templating, similar to John Resig's implementation. 1438 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1439 | // and correctly escapes quotes within interpolated code. 1440 | // NB: `oldSettings` only exists for backwards compatibility. 1441 | _.template = function(text, settings, oldSettings) { 1442 | if (!settings && oldSettings) settings = oldSettings; 1443 | settings = _.defaults({}, settings, _.templateSettings); 1444 | 1445 | // Combine delimiters into one regular expression via alternation. 1446 | var matcher = RegExp([ 1447 | (settings.escape || noMatch).source, 1448 | (settings.interpolate || noMatch).source, 1449 | (settings.evaluate || noMatch).source 1450 | ].join('|') + '|$', 'g'); 1451 | 1452 | // Compile the template source, escaping string literals appropriately. 1453 | var index = 0; 1454 | var source = "__p+='"; 1455 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1456 | source += text.slice(index, offset).replace(escaper, escapeChar); 1457 | index = offset + match.length; 1458 | 1459 | if (escape) { 1460 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1461 | } else if (interpolate) { 1462 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1463 | } else if (evaluate) { 1464 | source += "';\n" + evaluate + "\n__p+='"; 1465 | } 1466 | 1467 | // Adobe VMs need the match returned to produce the correct offest. 1468 | return match; 1469 | }); 1470 | source += "';\n"; 1471 | 1472 | // If a variable is not specified, place data values in local scope. 1473 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1474 | 1475 | source = "var __t,__p='',__j=Array.prototype.join," + 1476 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1477 | source + 'return __p;\n'; 1478 | 1479 | try { 1480 | var render = new Function(settings.variable || 'obj', '_', source); 1481 | } catch (e) { 1482 | e.source = source; 1483 | throw e; 1484 | } 1485 | 1486 | var template = function(data) { 1487 | return render.call(this, data, _); 1488 | }; 1489 | 1490 | // Provide the compiled source as a convenience for precompilation. 1491 | var argument = settings.variable || 'obj'; 1492 | template.source = 'function(' + argument + '){\n' + source + '}'; 1493 | 1494 | return template; 1495 | }; 1496 | 1497 | // Add a "chain" function. Start chaining a wrapped Underscore object. 1498 | _.chain = function(obj) { 1499 | var instance = _(obj); 1500 | instance._chain = true; 1501 | return instance; 1502 | }; 1503 | 1504 | // OOP 1505 | // --------------- 1506 | // If Underscore is called as a function, it returns a wrapped object that 1507 | // can be used OO-style. This wrapper holds altered versions of all the 1508 | // underscore functions. Wrapped objects may be chained. 1509 | 1510 | // Helper function to continue chaining intermediate results. 1511 | var result = function(instance, obj) { 1512 | return instance._chain ? _(obj).chain() : obj; 1513 | }; 1514 | 1515 | // Add your own custom functions to the Underscore object. 1516 | _.mixin = function(obj) { 1517 | _.each(_.functions(obj), function(name) { 1518 | var func = _[name] = obj[name]; 1519 | _.prototype[name] = function() { 1520 | var args = [this._wrapped]; 1521 | push.apply(args, arguments); 1522 | return result(this, func.apply(_, args)); 1523 | }; 1524 | }); 1525 | }; 1526 | 1527 | // Add all of the Underscore functions to the wrapper object. 1528 | _.mixin(_); 1529 | 1530 | // Add all mutator Array functions to the wrapper. 1531 | _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1532 | var method = ArrayProto[name]; 1533 | _.prototype[name] = function() { 1534 | var obj = this._wrapped; 1535 | method.apply(obj, arguments); 1536 | if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; 1537 | return result(this, obj); 1538 | }; 1539 | }); 1540 | 1541 | // Add all accessor Array functions to the wrapper. 1542 | _.each(['concat', 'join', 'slice'], function(name) { 1543 | var method = ArrayProto[name]; 1544 | _.prototype[name] = function() { 1545 | return result(this, method.apply(this._wrapped, arguments)); 1546 | }; 1547 | }); 1548 | 1549 | // Extracts the result from a wrapped and chained object. 1550 | _.prototype.value = function() { 1551 | return this._wrapped; 1552 | }; 1553 | 1554 | // Provide unwrapping proxy for some methods used in engine operations 1555 | // such as arithmetic and JSON stringification. 1556 | _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; 1557 | 1558 | _.prototype.toString = function() { 1559 | return '' + this._wrapped; 1560 | }; 1561 | 1562 | // AMD registration happens at the end for compatibility with AMD loaders 1563 | // that may not enforce next-turn semantics on modules. Even though general 1564 | // practice for AMD registration is to be anonymous, underscore registers 1565 | // as a named module because, like jQuery, it is a base library that is 1566 | // popular enough to be bundled in a third party lib, but not be part of 1567 | // an AMD load request. Those cases could generate an error when an 1568 | // anonymous define() is called outside of a loader request. 1569 | if (typeof define === 'function' && define.amd) { 1570 | define('underscore', [], function() { 1571 | return _; 1572 | }); 1573 | } 1574 | }.call(this)); -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | function formatTime(date) { 2 | var year = date.getFullYear() 3 | var month = date.getMonth() + 1 4 | var day = date.getDate() 5 | 6 | var hour = date.getHours() 7 | var minute = date.getMinutes() 8 | var second = date.getSeconds() 9 | 10 | 11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 12 | } 13 | 14 | function formatNumber(n) { 15 | n = n.toString() 16 | return n[1] ? n : '0' + n 17 | } 18 | 19 | module.exports = { 20 | formatTime: formatTime 21 | } 22 | --------------------------------------------------------------------------------