├── 1.MVVM框架设计及实现 ├── aaObserver.js ├── core.js ├── index.html └── jQuery.js ├── 2.MVVM框架设计及实现 ├── Collection.js ├── Directive.js ├── Observer.js ├── core.js ├── index.html └── lib │ ├── jQuery.js │ └── underscore.js ├── 3.MVVM框架设计及实现 ├── Binding.js ├── Collection.js ├── Compiler.js ├── Config.js ├── Directive.js ├── Emitter.js ├── Observer.js ├── Utils.js ├── ViewModel.js ├── directive │ └── on.js ├── index.html └── lib │ ├── aaRequire.js │ ├── jQuery.js │ └── underscore.js └── README.md /1.MVVM框架设计及实现/aaObserver.js: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************** 3 | * 简单的自定义事件,观察者模式 4 | * @by Aaron 5 | * github:https://github.com/JsAaron/aaObserver 6 | * blog:http://www.cnblogs.com/aaronjs/ 7 | *****************************************************************/ 8 | ;(function(O) { 9 | if (typeof module === "object" && typeof require === "function") { 10 | module.exports.aaObserver = O; 11 | } else { 12 | this.aaObserver = O; 13 | } 14 | })(function() { 15 | 16 | var slice = Array.prototype.slice, 17 | nativeForEach = Array.prototype.forEach; 18 | 19 | function each(obj, callback, context) { 20 | if (obj == null) return; 21 | //如果支持本地forEach方法,并且是函数 22 | if (nativeForEach && obj.forEach === nativeForEach) { 23 | obj.forEach(callback, context); 24 | } else if (obj.length === +obj.length) { 25 | //for循环迭代 26 | for (var i = 0, l = obj.length; i < l; i++) { 27 | callback.call(context, obj[i], i, obj); 28 | } 29 | } 30 | }; 31 | 32 | function bind(event, fn) { 33 | var events = this.events = this.events || {}, 34 | parts = event.split(/\s+/), 35 | i = 0, 36 | num = parts.length, 37 | part; 38 | if (events[event] && events[event].length) return this; 39 | each(parts, function(part, index) { 40 | events[part] = events[part] || []; 41 | events[part].push(fn); 42 | }) 43 | return this; 44 | } 45 | 46 | function one(event, fn) { 47 | this.bind(event, function fnc() { 48 | fn.apply(this, slice.call(arguments)); 49 | this.unbind(event, fnc); 50 | }); 51 | return this; 52 | } 53 | 54 | function unbind(event, fn) { 55 | var events = this.events, 56 | eventName, i, parts, num; 57 | if (!events) return; 58 | parts = event.split(/\s+/); 59 | each(parts, function(eventName, index) { 60 | if (eventName in events !== false) { 61 | events[eventName].splice(events[eventName].indexOf(fn), 1); 62 | if (!events[eventName].length) { //修正没有事件直接删除空数组 63 | delete events[eventName]; 64 | } 65 | } 66 | }) 67 | return this; 68 | } 69 | 70 | function trigger(event) { 71 | var events = this.events, 72 | i, args, falg; 73 | if (!events || event in events === false) return; 74 | args = slice.call(arguments, 1); 75 | for (i = events[event].length - 1; i >= 0; i--) { 76 | falg = events[event][i].apply(this, args); 77 | } 78 | return falg; //修正带返回 79 | } 80 | 81 | return function() { 82 | this.subscribe = bind; 83 | this.remove = unbind; 84 | this.publish = trigger; 85 | this.one = one; 86 | return this; 87 | }; 88 | }()); 89 | 90 | -------------------------------------------------------------------------------- /1.MVVM框架设计及实现/core.js: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 前端MVVM框架的实现 3 | * 实现第一步:双向绑定 4 | * @by Aaron 5 | * 分析的源码:https://github.com/RubyLouvre/avalon 6 | * github:https://github.com/JsAaron/aaMVVM 7 | * blog:http://www.cnblogs.com/aaronjs/ 8 | *****************************************************************/ 9 | 10 | 11 | (function(){ 12 | var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖 13 | var prefix = 'ao-'; //命名私有前缀 14 | var expose = Date.now(); 15 | var subscribers = 'aaron-' + expose; 16 | var stopRepeatAssign = false 17 | function noop() {} 18 | 19 | MVVM = function() {}; 20 | 21 | var VMODELS = MVVM.vmodels = {}; 22 | MVVM.define = function(name, factory) { 23 | var scope = {}; 24 | //收集所有定义 25 | factory(scope); 26 | 27 | //生成带get set控制器与自定义事件能力的vm对象 28 | var model = modelFactory(scope); 29 | 30 | //改变函数引用变成转化后vm对象,而不是scope对象 31 | stopRepeatAssign = true 32 | factory(model) 33 | stopRepeatAssign = false; 34 | 35 | model.$id = name; 36 | return VMODELS[name] = model; 37 | }; 38 | 39 | 40 | function modelFactory(scope) { 41 | var vModel = {}, //真正的视图模型对象 42 | originalModel = {}; //原始模型数据 43 | 44 | var accessingProperties = {}; //监控属性,转化成set get访问控制器 45 | var watchProperties = arguments[2] || {} //强制要监听的属性 46 | var normalProperties = {} //普通属性 47 | 48 | //分解创建句柄 49 | for (var k in scope) { 50 | resolveAccess(k, scope[k], originalModel, normalProperties, accessingProperties, watchProperties); 51 | } 52 | 53 | //转成访问控制器 54 | vModel = Object.defineProperties(vModel, withValue(accessingProperties)); 55 | 56 | //没有转化的函数,混入到新的vm对象中 57 | for (var name in normalProperties) { 58 | vModel[name] = normalProperties[name] 59 | } 60 | 61 | watchProperties.vModel = vModel 62 | aaObserver.call(vModel); //赋予自定义事件能力 63 | vModel.$id = generateID() 64 | vModel.$accessors = accessingProperties 65 | vModel.$originalModel = originalModel; //原始值 66 | vModel[subscribers] = [] 67 | return vModel 68 | } 69 | 70 | //转成访问控制器 71 | //set or get 72 | function resolveAccess(name, val, originalModel, normalProperties, accessingProperties, watchProperties) { 73 | 74 | //缓存原始值 75 | originalModel[name] = val 76 | 77 | var valueType = $.type(val); 78 | 79 | //如果是函数,不用监控 80 | if (valueType === 'function') { 81 | return normalProperties[name] = val 82 | } 83 | 84 | var accessor, oldArgs 85 | 86 | if (valueType === 'number') { 87 | //创建监控属性或数组,自变量,由用户触发其改变 88 | accessor = function(newValue){ 89 | var vmodel = watchProperties.vModel 90 | var preValue = originalModel[name]; 91 | if (arguments.length) { //set 92 | if (stopRepeatAssign) { 93 | return //阻止重复赋值 94 | } 95 | //确定是新设置值 96 | if (!isEqual(preValue, newValue)) { 97 | originalModel[name] = newValue //更新$model中的值 98 | //自身的依赖更新 99 | notifySubscribers(accessor); 100 | } 101 | } else { //get 102 | collectSubscribers(accessor) //收集视图函数 103 | return accessor.$vmodel || preValue //返回需要获取的值 104 | } 105 | }; 106 | accessor[subscribers] = [] //订阅者数组 107 | originalModel[name] = val 108 | } 109 | 110 | //保存监控处理 111 | accessingProperties[name] = accessor 112 | } 113 | 114 | //通知依赖于这个访问器的订阅者更新自身 115 | function notifySubscribers(accessor) { 116 | var list = accessor[subscribers] 117 | if (list && list.length) { 118 | var args = [].slice.call(arguments, 1) 119 | for (var i = list.length, fn; fn = list[--i];) { 120 | var el = fn.element 121 | fn.handler(fn.evaluator.apply(0, fn.args || []), el, fn) 122 | } 123 | } 124 | } 125 | 126 | 127 | //收集依赖于这个访问器的订阅者 128 | function collectSubscribers(accessor) { 129 | if (Registry[expose]) { //只有当注册了才收集 130 | var list = accessor[subscribers] 131 | list && ensure(list, Registry[expose]) //只有数组不存在此元素才push进去 132 | } 133 | } 134 | 135 | function ensure(target, item) { 136 | //只有当前数组不存在此元素时只添加它 137 | if (target.indexOf(item) === -1) { 138 | target.push(item) 139 | } 140 | return target; 141 | } 142 | 143 | 144 | //创建对象访问规则 145 | function withValue(access) { 146 | var descriptors = {} 147 | for (var i in access) { 148 | descriptors[i] = { 149 | get : access[i], 150 | set : access[i], 151 | enumerable : true, 152 | configurable : true 153 | } 154 | } 155 | return descriptors 156 | } 157 | 158 | 159 | function generateID() { 160 | return "aaron" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) 161 | } 162 | 163 | 164 | //======================节点绑定============================ 165 | 166 | var scanTag = MVVM.scanTag = function(element, vModel) { 167 | var div = document.getElementById('aa-attr'); 168 | var p = document.getElementById('aa-text'); 169 | var attrs = div.attributes; 170 | var bindings = [];//存储绑定数据 171 | $.each(attrs, function(index, ele) { 172 | var match; 173 | if (match = ele.name.match(/ao-(\w+)-?(.*)/)) { 174 | //如果是以指定前缀命名的 175 | var type = match[1] 176 | var param = match[2] || "" 177 | var binding = { 178 | type : type, 179 | param : param, 180 | element : div, 181 | name : match[0], 182 | value : ele.value 183 | } 184 | bindings.push(binding) 185 | } 186 | }) 187 | executeBindings(bindings,VMODELS['box']) 188 | 189 | //解析文本类型 190 | executeBindings([{ 191 | filters: undefined, 192 | element: document.getElementById('aa-text'), 193 | nodeType: 3, 194 | type: "text", 195 | value: " w " 196 | }], VMODELS['box']) 197 | } 198 | 199 | 200 | //执行绑定 201 | function executeBindings(bindings, vModel){ 202 | $.each(bindings,function(i,data){ 203 | bindingHandlers[data.type](data, vModel) 204 | if (data.evaluator && data.name) { //移除数据绑定,防止被二次解析 205 | //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99 206 | data.element.removeAttribute(data.name) 207 | } 208 | }) 209 | } 210 | 211 | 212 | function parseExprProxy(code, scopes, data){ 213 | parseExpr(code, scopes, data) 214 | //如果存在求值函数 215 | if (data.evaluator) { 216 | //找到对应的处理句柄 217 | data.handler = bindingExecutors[data.type]; 218 | data.evaluator.toString = function() { 219 | return data.type + " binding to eval(" + code + ")" 220 | } 221 | //方便调试 222 | //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定 223 | //将它移出订阅者列表 224 | registerSubscriber(data) 225 | } 226 | } 227 | 228 | //生成求值函数与 229 | //视图刷新函数 230 | function parseExpr(code, scopes, data){ 231 | var dataType = data.type 232 | var name = "vm" + expose; 233 | var prefix = "var " + data.value + " = " + name + "." + data.value; 234 | data.args = [scopes]; 235 | //绑定类型 236 | if (dataType === 'click') { 237 | code = 'click' 238 | code = code.replace("(", ".call(this,"); 239 | code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") 240 | var lastIndex = code.lastIndexOf("\nreturn") 241 | var header = code.slice(0, lastIndex) 242 | var footer = code.slice(lastIndex) 243 | code = header + "\nif(MVVM.openComputedCollect) return ;" + footer; 244 | var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 245 | } else { 246 | var code = "\nreturn " + data.value + ";"; 247 | var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 248 | } 249 | //生成求值函数 250 | data.evaluator = fn; 251 | } 252 | 253 | 254 | /********************************************************************* 255 | * 依赖收集与触发 * 256 | **********************************************************************/ 257 | function registerSubscriber(data) { 258 | Registry[expose] = data //暴光此函数,方便collectSubscribers收集 259 | MVVM.openComputedCollect = true //排除事件处理函数 260 | var fn = data.evaluator 261 | if (fn) { //如果是求值函数 262 | data.handler(fn.apply(0, data.args), data.element, data) 263 | } 264 | MVVM.openComputedCollect = false 265 | delete Registry[expose] 266 | } 267 | 268 | var bindingHandlers = { 269 | css: function(data, vModel) { 270 | var text = data.value.trim(); 271 | data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 272 | parseExprProxy(text, vModel, data) 273 | }, 274 | click: function(data, vModel) { 275 | var value = data.value 276 | data.type = "on" 277 | data.hasArgs = void 0 278 | data.handlerName = "on" 279 | parseExprProxy(value, vModel, data) 280 | }, 281 | text: function(data, vModel) { 282 | parseExprProxy(data.value, vModel, data) 283 | } 284 | } 285 | 286 | //执行最终的处理代码 287 | var bindingExecutors = { 288 | //修改css 289 | css: function(val, elem, data) { 290 | var method = data.type, 291 | attrName = data.param; 292 | $(elem).css(attrName, val) 293 | }, 294 | on: function(val, elem, data) { 295 | var fn = data.evaluator 296 | var args = data.args 297 | var vmodels = data.vmodels 298 | var callback = function(e) { 299 | return fn.apply(0,args).call(this, e) 300 | } 301 | elem.addEventListener('click', callback, false) 302 | data.evaluator = data.handler = noop 303 | }, 304 | text: function(val, elem, data) { 305 | $(elem).text(val) 306 | } 307 | } 308 | 309 | 310 | var isEqual = Object.is || function(v1, v2) { 311 | if (v1 === 0 && v2 === 0) { 312 | return 1 / v1 === 1 / v2 313 | } else if (v1 !== v1) { 314 | return v2 !== v2 315 | } else { 316 | return v1 === v2; 317 | } 318 | } 319 | 320 | })(); 321 | -------------------------------------------------------------------------------- /1.MVVM框架设计及实现/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MVVM学习之旅 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

{{ w }}

15 |
16 | 17 | 35 | 36 | -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/Collection.js: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 监控数组(与ms-each配合使用) * 3 | **********************************************************************/ 4 | 5 | function Collection(model) { 6 | this._id = generateID(); 7 | this[subscribers] = [] 8 | this._model = model 9 | this._events = {} 10 | this._ = modelFactory({ 11 | 'length': model.length 12 | }) 13 | this._.subscribe('length', function(a, b) { 14 | 15 | }) 16 | aaObserver.call(this); 17 | } 18 | 19 | Collection.prototype = { 20 | 21 | //转化成对应的数据模型 22 | _convert: function(val) { 23 | var type = $.type(val) 24 | if (rchecktype.test(type)) { //如果是多维结构 25 | alert(1) 26 | val = val.$id ? val : modelFactory(val, val) 27 | } 28 | return val 29 | }, 30 | 31 | //数据,数据长度 32 | _add: function(array, pos) { 33 | //获取当前数组的长度 34 | var oldLength = this.length; 35 | var self = this; 36 | pos = typeof pos === "number" ? pos : oldLength; 37 | var added = []; 38 | //把数组中的每一个参数给再次分解 39 | $.each(array, function(i, arr) { 40 | added[i] = self._convert(arr) //多维结构继续分解 41 | }); 42 | // [].slice.apply(this, [pos, 0].concat(added)); 43 | }, 44 | 45 | push: function(model) { 46 | this._model = model; 47 | this._add.apply(this, arguments) 48 | console.log(this) 49 | }, 50 | } -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/Directive.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // 3 | // 解析所有通过ao自定义的语法指令 4 | // 通过声明式是结构来控制样式与行为 5 | // 6 | //======================================== 7 | 8 | MVVM.scan = function(element, vmodel) { 9 | element = element || root 10 | var vmodels = vmodel ? [].concat(vmodel) : [] 11 | scanTag(element, vmodels) 12 | } 13 | 14 | 15 | function scanTag(element, vmodels) { 16 | //找到这个作用域 17 | var controllerValue = element.getAttribute(prefix + "controller"); 18 | if (controllerValue) { 19 | vmodels = MVVM.vmodels[controllerValue] 20 | //移除标记 21 | element.removeAttribute(prefix + "controller") 22 | } 23 | scanAttrNodes(element, vmodels) 24 | } 25 | 26 | //扫描属性节点 27 | function scanAttrNodes(element, vmodels) { 28 | var match, bindings = [], //存放绑定数据a 29 | attributes = element.attributes; 30 | 31 | for (var i = 0, attr; attr = attributes[i++];) { 32 | if (attr.specified) { 33 | if (match = attr.name.match(/ao-(\w+)-?(.*)/)) { 34 | //如果是以指定前缀命名的 35 | var type = match[1]; 36 | var param = match[2] || ""; 37 | var binding = { 38 | 'type' : type, 39 | 'param' : param, 40 | 'element' : element, 41 | 'name' : match[0], 42 | 'value' : attr.value 43 | } 44 | bindings.push(binding) 45 | } 46 | } 47 | } 48 | 49 | //如果有绑定 50 | if (bindings.length) { 51 | executeBindings(bindings, vmodels); 52 | } 53 | 54 | scanNodes(element, vmodels) //扫描子孙元素 55 | } 56 | 57 | 58 | //检索所有子节点 59 | function scanNodes(parent, vmodels) { 60 | //取出第一个子节点 61 | var node = parent.firstChild; 62 | while (node) { 63 | switch (node.nodeType) { 64 | case 1: 65 | scanTag(node, vmodels) //扫描元素节点 66 | break; 67 | case 3: 68 | if (/\{\{(.*?)\}\}/.test(node.data)) { //是{{}}表达式格式 69 | scanText(node, vmodels) //扫描文本节点 70 | } 71 | break; 72 | } 73 | //找到下一个兄弟节点 74 | node = node.nextSibling; 75 | } 76 | } 77 | 78 | //================================================== 79 | // 扫描文本节点 80 | // 将分解的序列放入文档碎片 81 | //================================================== 82 | function scanText(textNode, vmodels) { 83 | var bindings = [], 84 | tokens = tokenize(textNode.data); 85 | 86 | if (tokens.length) { 87 | for (var i = 0, token; token = tokens[i++];) { 88 | var node = document.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点 89 | if (token.expr) { 90 | var binding = { 91 | type : "text", 92 | node : node, 93 | nodeType : 3, 94 | value : token.value 95 | } 96 | bindings.push(binding) //收集带有插值表达式的文本 97 | } 98 | documentFragment.appendChild(node) 99 | } 100 | textNode.parentNode.replaceChild(documentFragment, textNode) 101 | executeBindings(bindings, vmodels) 102 | } 103 | } 104 | 105 | //==================================================================== 106 | // 解析{{}}数据,V2版本排除管道过滤符的处理 107 | // data = "哈哈 {{ w }} x {{ h }} y {{z}} 呵呵" 108 | // 一个文本节点可能有多个插值表达式,这样的格式需要转化成 109 | // 词法分析器 110 | // tkoens 111 | // 代码经过词法分析后就得到了一个Token序列,紧接着拿Token序列去其他事情 112 | // 113 | // tokens [ 114 | // { 115 | // expr : true/false 是否为表达式 116 | // value: 值 117 | // }, 118 | // ............... 119 | // ] 120 | //=========================================================================== 121 | var openTag = '{{', 122 | closeTag = '}}'; 123 | 124 | function tokenize(str) { 125 | var tokens = [], 126 | value, 127 | start = 0, 128 | stop; 129 | 130 | do { 131 | //扫描是不是开始{{,那么意味着前前面还有数据 132 | stop = str.indexOf(openTag, start) 133 | if (stop === -1) { 134 | //意味着搜索到了末尾 135 | break 136 | } 137 | //获取到{{左边的文本,保存 138 | value = str.slice(start, stop) 139 | if (value) { 140 | tokens.push({ 141 | value: value, 142 | expr: false 143 | }) 144 | } 145 | 146 | //插值表达式的处理 147 | start = stop + openTag.length 148 | stop = str.indexOf(closeTag, start) 149 | if (stop === -1) { 150 | break 151 | } 152 | value = str.slice(start, stop) 153 | if (value) { //处理{{ }}插值表达式 154 | tokens.push({ 155 | value: value, 156 | expr: true 157 | }) 158 | } 159 | //开始下一次检测 160 | start = stop + closeTag.length; 161 | } while (1) 162 | 163 | 164 | value = str.slice(start) 165 | if (value) { //}} 右边的文本 166 | tokens.push({ 167 | value: value, 168 | expr: false 169 | }) 170 | } 171 | 172 | return tokens; 173 | } 174 | 175 | 176 | //执行绑定 177 | function executeBindings(bindings, vmodels) { 178 | _.each(bindings, function(data, i) { 179 | bindingHandlers[data.type](data, vmodels) 180 | if (data.evaluator && data.name) { //移除数据绑定,防止被二次解析 181 | //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99 182 | data.element.removeAttribute(data.name) 183 | } 184 | }) 185 | } -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/Observer.js: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************** 3 | * 简单的自定义事件,观察者模式 4 | * @by Aaron 5 | * github:https://github.com/JsAaron/aaObserver 6 | * blog:http://www.cnblogs.com/aaronjs/ 7 | *****************************************************************/ 8 | ;(function(O) { 9 | if (typeof module === "object" && typeof require === "function") { 10 | module.exports.aaObserver = O; 11 | } else { 12 | this.aaObserver = O; 13 | } 14 | })(function() { 15 | 16 | var slice = Array.prototype.slice, 17 | nativeForEach = Array.prototype.forEach; 18 | 19 | function each(obj, callback, context) { 20 | if (obj == null) return; 21 | //如果支持本地forEach方法,并且是函数 22 | if (nativeForEach && obj.forEach === nativeForEach) { 23 | obj.forEach(callback, context); 24 | } else if (obj.length === +obj.length) { 25 | //for循环迭代 26 | for (var i = 0, l = obj.length; i < l; i++) { 27 | callback.call(context, obj[i], i, obj); 28 | } 29 | } 30 | }; 31 | 32 | function bind(event, fn) { 33 | var events = this.events = this.events || {}, 34 | parts = event.split(/\s+/), 35 | i = 0, 36 | num = parts.length, 37 | part; 38 | if (events[event] && events[event].length) return this; 39 | each(parts, function(part, index) { 40 | events[part] = events[part] || []; 41 | events[part].push(fn); 42 | }) 43 | return this; 44 | } 45 | 46 | function one(event, fn) { 47 | this.bind(event, function fnc() { 48 | fn.apply(this, slice.call(arguments)); 49 | this.unbind(event, fnc); 50 | }); 51 | return this; 52 | } 53 | 54 | function unbind(event, fn) { 55 | var events = this.events, 56 | eventName, i, parts, num; 57 | if (!events) return; 58 | parts = event.split(/\s+/); 59 | each(parts, function(eventName, index) { 60 | if (eventName in events !== false) { 61 | events[eventName].splice(events[eventName].indexOf(fn), 1); 62 | if (!events[eventName].length) { //修正没有事件直接删除空数组 63 | delete events[eventName]; 64 | } 65 | } 66 | }) 67 | return this; 68 | } 69 | 70 | function trigger(event) { 71 | var events = this.events, 72 | i, args, falg; 73 | if (!events || event in events === false) return; 74 | args = slice.call(arguments, 1); 75 | for (i = events[event].length - 1; i >= 0; i--) { 76 | falg = events[event][i].apply(this, args); 77 | } 78 | return falg; //修正带返回 79 | } 80 | 81 | return function() { 82 | this.subscribe = bind; 83 | this.remove = unbind; 84 | this.publish = trigger; 85 | this.one = one; 86 | return this; 87 | }; 88 | }()); 89 | 90 | -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/core.js: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 前端MVVM框架的实现 3 | * 第二弹:DOM模块化模板引擎 4 | * @by Aaron 5 | * 源码分析博客:http://www.cnblogs.com/aaronjs/ 6 | * 源码分析Github:https://github.com/JsAaron/aaMVVM 7 | * Avalon源码:https://github.com/RubyLouvre/avalon 8 | * DOM的处理前期全采用jQuery代替 9 | *****************************************************************/ 10 | var root = document.documentElement; 11 | var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖 12 | var prefix = 'ao-'; //命名私有前缀 13 | var expose = Date.now(); 14 | var subscribers = 'aaron-' + expose; 15 | var stopRepeatAssign = false; 16 | var rchecktype = /^(?:array|object)$/i; //判断当前的类型只能是数组或者对象 17 | var documentFragment = document.createDocumentFragment(); 18 | 19 | function noop() {} 20 | 21 | MVVM = function() {}; 22 | 23 | var VMODELS = MVVM.vmodels = {}; 24 | 25 | MVVM.define = function(name, factory) { 26 | var scope = {}; 27 | //收集所有定义 28 | factory(scope); 29 | //生成带get set控制器与自定义事件能力的vm对象 30 | var model = modelFactory(scope); 31 | //改变函数引用变成转化后vm对象,而不是scope对象 32 | stopRepeatAssign = true 33 | factory(model) 34 | stopRepeatAssign = false; 35 | model._id = name; 36 | return VMODELS[name] = model; 37 | }; 38 | 39 | //=============================================== 40 | // 数据源转化工厂,元数据转转成视图模型对象 41 | // 对应多出 42 | // 1 set/get方法 43 | // 2 自定义事件能力 44 | //================================================ 45 | function modelFactory(scope, originalModel) { 46 | //复杂数据生成方式,Array/Object 47 | if ($.isArray(scope)) { 48 | var originalArr = scope.concat(); //原数组的作为新生成的监控数组的originalModel而存在 49 | scope.length = 0 50 | var collection = new Collection(scope); //生成带控制的基本结构 51 | collection.push(originalArr); 52 | return collection; 53 | } 54 | 55 | var vmodel = {}, //转化后真正的视图模型对象 56 | originalModel = {}, //原始模型数据 57 | accessingProperties = {}, //监控属性,转化成set get访问控制器 58 | watchProperties = arguments[2] || {}, //强制要监听的属性 59 | normalProperties = {}; //普通属性 60 | 61 | //分解创建句柄 62 | for (var k in scope) { 63 | parseModel(k, scope[k], originalModel, normalProperties, accessingProperties, watchProperties); 64 | } 65 | 66 | //转成访问控制器 67 | vmodel = Object.defineProperties(vmodel, withValue(accessingProperties)); 68 | 69 | //没有转化的函数,混入到新的vm对象中 70 | for (var name in normalProperties) { 71 | vmodel[name] = normalProperties[name] 72 | } 73 | 74 | watchProperties.vmodel = vmodel 75 | aaObserver.call(vmodel); //赋予自定义事件能力 76 | vmodel._id = generateID() 77 | vmodel._accessors = accessingProperties 78 | vmodel._originalModel = originalModel; //原始值 79 | vmodel[subscribers] = [] 80 | return vmodel 81 | } 82 | 83 | //解析模型,转化成对应的set/get处理方法 84 | function parseModel(name, val, originalModel, normalProperties, accessingProperties, watchProperties) { 85 | //缓存原始值 86 | originalModel[name] = val 87 | //得到值类型 88 | var valueType = $.type(val); 89 | //如果是函数,不用监控,意味着这是事件回调句柄 90 | if (valueType === 'function') { 91 | return normalProperties[name] = val 92 | } 93 | //如果值类型是对象,并且有get方法,为计算属性 94 | if (valueType === "object" && typeof val.get === "function" && Object.keys(val).length <= 2) { 95 | 96 | } else { 97 | //否则为监控属性 98 | var accessor = createAccessingProperties(valueType, originalModel, name, val, watchProperties); 99 | } 100 | //保存监控处理 101 | accessingProperties[name] = accessor; 102 | } 103 | 104 | //================================================== 105 | // 创建监控属性或数组,自变量,由用户触发其改变 106 | // 1 基本数据结构 107 | // 2 数组结构 108 | // 3 对象结构 109 | //==================================================== 110 | function createAccessingProperties(valueType, originalModel, name, val, watchProperties) { 111 | var accessor = function(newValue) { 112 | var vmodel = watchProperties.vmodel 113 | var preValue = originalModel[name]; 114 | if (arguments.length) { //set 115 | if (stopRepeatAssign) { 116 | return //阻止重复赋值 117 | } 118 | //确定是新设置值 119 | if (!isEqual(preValue, newValue)) { 120 | originalModel[name] = newValue //更新$model中的值 121 | //自身的依赖更新 122 | notifySubscribers(accessor); 123 | } 124 | } else { //get 125 | collectSubscribers(accessor) //收集视图函数 126 | return accessor.$vmodel || preValue //返回需要获取的值 127 | } 128 | }; 129 | accessor[subscribers] = [] //订阅者数组,保存所有的view依赖映射 130 | 131 | //生成监控属性要区分内部值的类型 132 | if (rchecktype.test(valueType)) { //复杂数据,通过递归处理 133 | //复杂结构 134 | var complexValue = modelFactory(val, val); 135 | accessor._vmodel = complexValue; 136 | originalModel[name] = complexValue._model 137 | } else { 138 | //普通的基本类型 139 | originalModel[name] = val; 140 | } 141 | 142 | return accessor; 143 | } 144 | 145 | 146 | //通知依赖于这个访问器的订阅者更新自身 147 | function notifySubscribers(accessor) { 148 | var list = accessor[subscribers] 149 | if (list && list.length) { 150 | var args = [].slice.call(arguments, 1) 151 | for (var i = list.length, fn; fn = list[--i];) { 152 | var el = fn.element 153 | fn.handler(fn.evaluator.apply(0, fn.args || []), el, fn) 154 | } 155 | } 156 | } 157 | 158 | 159 | //收集依赖于这个访问器的订阅者 160 | function collectSubscribers(accessor) { 161 | if (Registry[expose]) { //只有当注册了才收集 162 | var list = accessor[subscribers] 163 | list && ensure(list, Registry[expose]) //只有数组不存在此元素才push进去 164 | } 165 | } 166 | 167 | function ensure(target, item) { 168 | //只有当前数组不存在此元素时只添加它 169 | if (target.indexOf(item) === -1) { 170 | target.push(item) 171 | } 172 | return target; 173 | } 174 | 175 | //创建对象访问规则 176 | function withValue(access) { 177 | var descriptors = {} 178 | for (var i in access) { 179 | descriptors[i] = { 180 | get: access[i], 181 | set: access[i], 182 | enumerable: true, 183 | configurable: true 184 | } 185 | } 186 | return descriptors 187 | } 188 | 189 | function generateID() { 190 | return "aaron" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) 191 | } 192 | 193 | function parseExprProxy(code, scopes, data) { 194 | parseExpr(code, scopes, data) 195 | //如果存在求值函数 196 | if (data.evaluator) { 197 | //找到对应的处理句柄 198 | data.handler = bindingExecutors[data.type]; 199 | data.evaluator.toString = function() { 200 | return data.type + " binding to eval(" + code + ")" 201 | } 202 | //方便调试 203 | //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定 204 | //将它移出订阅者列表 205 | registerSubscriber(data) 206 | } 207 | } 208 | 209 | //生成求值函数与 210 | //视图刷新函数 211 | function parseExpr(code, scopes, data) { 212 | var dataType = data.type 213 | var name = "vm" + expose; 214 | var prefix = "var " + data.value + " = " + name + "." + data.value; 215 | data.args = [scopes]; 216 | //绑定类型 217 | if (dataType === 'click') { 218 | code = 'click' 219 | code = code.replace("(", ".call(this,"); 220 | code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") 221 | var lastIndex = code.lastIndexOf("\nreturn") 222 | var header = code.slice(0, lastIndex) 223 | var footer = code.slice(lastIndex) 224 | code = header + "\nif(MVVM.openComputedCollect) return ;" + footer; 225 | var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 226 | } else { 227 | var code = "\nreturn " + data.value + ";"; 228 | var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 229 | } 230 | //生成求值函数 231 | data.evaluator = fn; 232 | } 233 | 234 | /********************************************************************* 235 | * 依赖收集与触发 * 236 | **********************************************************************/ 237 | function registerSubscriber(data) { 238 | Registry[expose] = data //暴光此函数,方便collectSubscribers收集 239 | MVVM.openComputedCollect = true //排除事件处理函数 240 | var fn = data.evaluator 241 | if (fn) { //如果是求值函数 242 | data.handler(fn.apply(0, data.args), data.element, data) 243 | } 244 | MVVM.openComputedCollect = false 245 | delete Registry[expose] 246 | } 247 | 248 | var bindingHandlers = { 249 | css: function(data, vmodel) { 250 | var text = data.value.trim(); 251 | data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 252 | parseExprProxy(text, vmodel, data) 253 | }, 254 | click: function(data, vmodel) { 255 | var value = data.value 256 | data.type = "on" 257 | data.hasArgs = void 0 258 | data.handlerName = "on" 259 | parseExprProxy(value, vmodel, data) 260 | }, 261 | text: function(data, vmodel) { 262 | parseExprProxy(data.value, vmodel, data) 263 | } 264 | } 265 | 266 | //执行最终的处理代码 267 | var bindingExecutors = { 268 | //修改css 269 | css: function(val, elem, data) { 270 | var method = data.type, 271 | attrName = data.param; 272 | $(elem).css(attrName, val) 273 | }, 274 | on: function(val, elem, data) { 275 | var fn = data.evaluator 276 | var args = data.args 277 | var vmodels = data.vmodels 278 | var callback = function(e) { 279 | return fn.apply(0, args).call(this, e) 280 | } 281 | elem.addEventListener('click', callback, false) 282 | data.evaluator = data.handler = noop 283 | }, 284 | text: function(val, elem, data) { 285 | if (data.nodeType === 3) { 286 | data.node.data = val 287 | } else { 288 | $(elem).text(val) 289 | } 290 | } 291 | } 292 | 293 | var isEqual = Object.is || function(v1, v2) { 294 | if (v1 === 0 && v2 === 0) { 295 | return 1 / v1 === 1 / v2 296 | } else if (v1 !== v1) { 297 | return v2 !== v2 298 | } else { 299 | return v1 === v2; 300 | } 301 | } -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MVVM学习之旅 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

图片的尺寸 {{ w }} * {{ h }} {{ t }}

19 |
20 | 21 | 37 | 38 | -------------------------------------------------------------------------------- /2.MVVM框架设计及实现/lib/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 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 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | //use the faster Date.now if available. 24 | var getTime = (Date.now || function() { 25 | return new Date().getTime(); 26 | }); 27 | 28 | // Create quick reference variables for speed access to core prototypes. 29 | var 30 | push = ArrayProto.push, 31 | slice = ArrayProto.slice, 32 | concat = ArrayProto.concat, 33 | toString = ObjProto.toString, 34 | hasOwnProperty = ObjProto.hasOwnProperty; 35 | 36 | // All **ECMAScript 5** native function implementations that we hope to use 37 | // are declared here. 38 | var 39 | nativeForEach = ArrayProto.forEach, 40 | nativeMap = ArrayProto.map, 41 | nativeReduce = ArrayProto.reduce, 42 | nativeReduceRight = ArrayProto.reduceRight, 43 | nativeFilter = ArrayProto.filter, 44 | nativeEvery = ArrayProto.every, 45 | nativeSome = ArrayProto.some, 46 | nativeIndexOf = ArrayProto.indexOf, 47 | nativeLastIndexOf = ArrayProto.lastIndexOf, 48 | nativeIsArray = Array.isArray, 49 | nativeKeys = Object.keys, 50 | nativeBind = FuncProto.bind; 51 | 52 | // Create a safe reference to the Underscore object for use below. 53 | var _ = function(obj) { 54 | if (obj instanceof _) return obj; 55 | if (!(this instanceof _)) return new _(obj); 56 | this._wrapped = obj; 57 | }; 58 | 59 | // Export the Underscore object for **Node.js**, with 60 | // backwards-compatibility for the old `require()` API. If we're in 61 | // the browser, add `_` as a global object via a string identifier, 62 | // for Closure Compiler "advanced" mode. 63 | if (typeof exports !== 'undefined') { 64 | if (typeof module !== 'undefined' && module.exports) { 65 | exports = module.exports = _; 66 | } 67 | exports._ = _; 68 | } else { 69 | root._ = _; 70 | } 71 | 72 | // Current version. 73 | _.VERSION = '1.5.2'; 74 | 75 | // Collection Functions 76 | // -------------------- 77 | 78 | // The cornerstone, an `each` implementation, aka `forEach`. 79 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 80 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 81 | var each = _.each = _.forEach = function(obj, iterator, context) { 82 | if (obj == null) return; 83 | if (nativeForEach && obj.forEach === nativeForEach) { 84 | obj.forEach(iterator, context); 85 | } else if (obj.length === +obj.length) { 86 | for (var i = 0, length = obj.length; i < length; i++) { 87 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 88 | } 89 | } else { 90 | var keys = _.keys(obj); 91 | for (var i = 0, length = keys.length; i < length; i++) { 92 | if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; 93 | } 94 | } 95 | }; 96 | 97 | // Return the results of applying the iterator to each element. 98 | // Delegates to **ECMAScript 5**'s native `map` if available. 99 | _.map = _.collect = function(obj, iterator, context) { 100 | var results = []; 101 | if (obj == null) return results; 102 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 103 | each(obj, function(value, index, list) { 104 | results.push(iterator.call(context, value, index, list)); 105 | }); 106 | return results; 107 | }; 108 | 109 | var reduceError = 'Reduce of empty array with no initial value'; 110 | 111 | // **Reduce** builds up a single result from a list of values, aka `inject`, 112 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 113 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 114 | var initial = arguments.length > 2; 115 | if (obj == null) obj = []; 116 | if (nativeReduce && obj.reduce === nativeReduce) { 117 | if (context) iterator = _.bind(iterator, context); 118 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 119 | } 120 | each(obj, function(value, index, list) { 121 | if (!initial) { 122 | memo = value; 123 | initial = true; 124 | } else { 125 | memo = iterator.call(context, memo, value, index, list); 126 | } 127 | }); 128 | if (!initial) throw new TypeError(reduceError); 129 | return memo; 130 | }; 131 | 132 | // The right-associative version of reduce, also known as `foldr`. 133 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 134 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 135 | var initial = arguments.length > 2; 136 | if (obj == null) obj = []; 137 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 138 | if (context) iterator = _.bind(iterator, context); 139 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 140 | } 141 | var length = obj.length; 142 | if (length !== +length) { 143 | var keys = _.keys(obj); 144 | length = keys.length; 145 | } 146 | each(obj, function(value, index, list) { 147 | index = keys ? keys[--length] : --length; 148 | if (!initial) { 149 | memo = obj[index]; 150 | initial = true; 151 | } else { 152 | memo = iterator.call(context, memo, obj[index], index, list); 153 | } 154 | }); 155 | if (!initial) throw new TypeError(reduceError); 156 | return memo; 157 | }; 158 | 159 | // Return the first value which passes a truth test. Aliased as `detect`. 160 | _.find = _.detect = function(obj, iterator, context) { 161 | var result; 162 | any(obj, function(value, index, list) { 163 | if (iterator.call(context, value, index, list)) { 164 | result = value; 165 | return true; 166 | } 167 | }); 168 | return result; 169 | }; 170 | 171 | // Return all the elements that pass a truth test. 172 | // Delegates to **ECMAScript 5**'s native `filter` if available. 173 | // Aliased as `select`. 174 | _.filter = _.select = function(obj, iterator, context) { 175 | var results = []; 176 | if (obj == null) return results; 177 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 178 | each(obj, function(value, index, list) { 179 | if (iterator.call(context, value, index, list)) results.push(value); 180 | }); 181 | return results; 182 | }; 183 | 184 | // Return all the elements for which a truth test fails. 185 | _.reject = function(obj, iterator, context) { 186 | return _.filter(obj, function(value, index, list) { 187 | return !iterator.call(context, value, index, list); 188 | }, context); 189 | }; 190 | 191 | // Determine whether all of the elements match a truth test. 192 | // Delegates to **ECMAScript 5**'s native `every` if available. 193 | // Aliased as `all`. 194 | _.every = _.all = function(obj, iterator, context) { 195 | iterator || (iterator = _.identity); 196 | var result = true; 197 | if (obj == null) return result; 198 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 199 | each(obj, function(value, index, list) { 200 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 201 | }); 202 | return !!result; 203 | }; 204 | 205 | // Determine if at least one element in the object matches a truth test. 206 | // Delegates to **ECMAScript 5**'s native `some` if available. 207 | // Aliased as `any`. 208 | var any = _.some = _.any = function(obj, iterator, context) { 209 | iterator || (iterator = _.identity); 210 | var result = false; 211 | if (obj == null) return result; 212 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 213 | each(obj, function(value, index, list) { 214 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 215 | }); 216 | return !!result; 217 | }; 218 | 219 | // Determine if the array or object contains a given value (using `===`). 220 | // Aliased as `include`. 221 | _.contains = _.include = function(obj, target) { 222 | if (obj == null) return false; 223 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 224 | return any(obj, function(value) { 225 | return value === target; 226 | }); 227 | }; 228 | 229 | // Invoke a method (with arguments) on every item in a collection. 230 | _.invoke = function(obj, method) { 231 | var args = slice.call(arguments, 2); 232 | var isFunc = _.isFunction(method); 233 | return _.map(obj, function(value) { 234 | return (isFunc ? method : value[method]).apply(value, args); 235 | }); 236 | }; 237 | 238 | // Convenience version of a common use case of `map`: fetching a property. 239 | _.pluck = function(obj, key) { 240 | return _.map(obj, function(value){ return value[key]; }); 241 | }; 242 | 243 | // Convenience version of a common use case of `filter`: selecting only objects 244 | // containing specific `key:value` pairs. 245 | _.where = function(obj, attrs, first) { 246 | if (_.isEmpty(attrs)) return first ? void 0 : []; 247 | return _[first ? 'find' : 'filter'](obj, function(value) { 248 | for (var key in attrs) { 249 | if (attrs[key] !== value[key]) return false; 250 | } 251 | return true; 252 | }); 253 | }; 254 | 255 | // Convenience version of a common use case of `find`: getting the first object 256 | // containing specific `key:value` pairs. 257 | _.findWhere = function(obj, attrs) { 258 | return _.where(obj, attrs, true); 259 | }; 260 | 261 | // Return the maximum element or (element-based computation). 262 | // Can't optimize arrays of integers longer than 65,535 elements. 263 | // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) 264 | _.max = function(obj, iterator, context) { 265 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 266 | return Math.max.apply(Math, obj); 267 | } 268 | if (!iterator && _.isEmpty(obj)) return -Infinity; 269 | var result = {computed : -Infinity, value: -Infinity}; 270 | each(obj, function(value, index, list) { 271 | var computed = iterator ? iterator.call(context, value, index, list) : value; 272 | computed > result.computed && (result = {value : value, computed : computed}); 273 | }); 274 | return result.value; 275 | }; 276 | 277 | // Return the minimum element (or element-based computation). 278 | _.min = function(obj, iterator, context) { 279 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 280 | return Math.min.apply(Math, obj); 281 | } 282 | if (!iterator && _.isEmpty(obj)) return Infinity; 283 | var result = {computed : Infinity, value: Infinity}; 284 | each(obj, function(value, index, list) { 285 | var computed = iterator ? iterator.call(context, value, index, list) : value; 286 | computed < result.computed && (result = {value : value, computed : computed}); 287 | }); 288 | return result.value; 289 | }; 290 | 291 | // Shuffle an array, using the modern version of the 292 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 293 | _.shuffle = function(obj) { 294 | var rand; 295 | var index = 0; 296 | var shuffled = []; 297 | each(obj, function(value) { 298 | rand = _.random(index++); 299 | shuffled[index - 1] = shuffled[rand]; 300 | shuffled[rand] = value; 301 | }); 302 | return shuffled; 303 | }; 304 | 305 | // Sample **n** random values from a collection. 306 | // If **n** is not specified, returns a single random element. 307 | // The internal `guard` argument allows it to work with `map`. 308 | _.sample = function(obj, n, guard) { 309 | if (n == null || guard) { 310 | if (obj.length !== +obj.length) obj = _.values(obj); 311 | return obj[_.random(obj.length - 1)]; 312 | } 313 | return _.shuffle(obj).slice(0, Math.max(0, n)); 314 | }; 315 | 316 | // An internal function to generate lookup iterators. 317 | var lookupIterator = function(value) { 318 | return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 319 | }; 320 | 321 | // Sort the object's values by a criterion produced by an iterator. 322 | _.sortBy = function(obj, value, context) { 323 | var iterator = value == null ? _.identity : lookupIterator(value); 324 | return _.pluck(_.map(obj, function(value, index, list) { 325 | return { 326 | value: value, 327 | index: index, 328 | criteria: iterator.call(context, value, index, list) 329 | }; 330 | }).sort(function(left, right) { 331 | var a = left.criteria; 332 | var b = right.criteria; 333 | if (a !== b) { 334 | if (a > b || a === void 0) return 1; 335 | if (a < b || b === void 0) return -1; 336 | } 337 | return left.index - right.index; 338 | }), 'value'); 339 | }; 340 | 341 | // An internal function used for aggregate "group by" operations. 342 | var group = function(behavior) { 343 | return function(obj, value, context) { 344 | var result = {}; 345 | var iterator = value == null ? _.identity : lookupIterator(value); 346 | each(obj, function(value, index) { 347 | var key = iterator.call(context, value, index, obj); 348 | behavior(result, key, value); 349 | }); 350 | return result; 351 | }; 352 | }; 353 | 354 | // Groups the object's values by a criterion. Pass either a string attribute 355 | // to group by, or a function that returns the criterion. 356 | _.groupBy = group(function(result, key, value) { 357 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 358 | }); 359 | 360 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 361 | // when you know that your index values will be unique. 362 | _.indexBy = group(function(result, key, value) { 363 | result[key] = value; 364 | }); 365 | 366 | // Counts instances of an object that group by a certain criterion. Pass 367 | // either a string attribute to count by, or a function that returns the 368 | // criterion. 369 | _.countBy = group(function(result, key) { 370 | _.has(result, key) ? result[key]++ : result[key] = 1; 371 | }); 372 | 373 | // Use a comparator function to figure out the smallest index at which 374 | // an object should be inserted so as to maintain order. Uses binary search. 375 | _.sortedIndex = function(array, obj, iterator, context) { 376 | iterator = iterator == null ? _.identity : lookupIterator(iterator); 377 | var value = iterator.call(context, obj); 378 | var low = 0, high = array.length; 379 | while (low < high) { 380 | var mid = (low + high) >>> 1; 381 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 382 | } 383 | return low; 384 | }; 385 | 386 | // Safely create a real, live array from anything iterable. 387 | _.toArray = function(obj) { 388 | if (!obj) return []; 389 | if (_.isArray(obj)) return slice.call(obj); 390 | if (obj.length === +obj.length) return _.map(obj, _.identity); 391 | return _.values(obj); 392 | }; 393 | 394 | // Return the number of elements in an object. 395 | _.size = function(obj) { 396 | if (obj == null) return 0; 397 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 398 | }; 399 | 400 | // Array Functions 401 | // --------------- 402 | 403 | // Get the first element of an array. Passing **n** will return the first N 404 | // values in the array. Aliased as `head` and `take`. The **guard** check 405 | // allows it to work with `_.map`. 406 | _.first = _.head = _.take = function(array, n, guard) { 407 | if (array == null) return void 0; 408 | return (n == null) || guard ? array[0] : slice.call(array, 0, n); 409 | }; 410 | 411 | // Returns everything but the last entry of the array. Especially useful on 412 | // the arguments object. Passing **n** will return all the values in 413 | // the array, excluding the last N. The **guard** check allows it to work with 414 | // `_.map`. 415 | _.initial = function(array, n, guard) { 416 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 417 | }; 418 | 419 | // Get the last element of an array. Passing **n** will return the last N 420 | // values in the array. The **guard** check allows it to work with `_.map`. 421 | _.last = function(array, n, guard) { 422 | if (array == null) return void 0; 423 | if ((n == null) || guard) { 424 | return array[array.length - 1]; 425 | } else { 426 | return slice.call(array, Math.max(array.length - n, 0)); 427 | } 428 | }; 429 | 430 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 431 | // Especially useful on the arguments object. Passing an **n** will return 432 | // the rest N values in the array. The **guard** 433 | // check allows it to work with `_.map`. 434 | _.rest = _.tail = _.drop = function(array, n, guard) { 435 | return slice.call(array, (n == null) || guard ? 1 : n); 436 | }; 437 | 438 | // Trim out all falsy values from an array. 439 | _.compact = function(array) { 440 | return _.filter(array, _.identity); 441 | }; 442 | 443 | // Internal implementation of a recursive `flatten` function. 444 | var flatten = function(input, shallow, output) { 445 | if (shallow && _.every(input, _.isArray)) { 446 | return concat.apply(output, input); 447 | } 448 | each(input, function(value) { 449 | if (_.isArray(value) || _.isArguments(value)) { 450 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 451 | } else { 452 | output.push(value); 453 | } 454 | }); 455 | return output; 456 | }; 457 | 458 | // Flatten out an array, either recursively (by default), or just one level. 459 | _.flatten = function(array, shallow) { 460 | return flatten(array, shallow, []); 461 | }; 462 | 463 | // Return a version of the array that does not contain the specified value(s). 464 | _.without = function(array) { 465 | return _.difference(array, slice.call(arguments, 1)); 466 | }; 467 | 468 | // Produce a duplicate-free version of the array. If the array has already 469 | // been sorted, you have the option of using a faster algorithm. 470 | // Aliased as `unique`. 471 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 472 | if (_.isFunction(isSorted)) { 473 | context = iterator; 474 | iterator = isSorted; 475 | isSorted = false; 476 | } 477 | var initial = iterator ? _.map(array, iterator, context) : array; 478 | var results = []; 479 | var seen = []; 480 | each(initial, function(value, index) { 481 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 482 | seen.push(value); 483 | results.push(array[index]); 484 | } 485 | }); 486 | return results; 487 | }; 488 | 489 | // Produce an array that contains the union: each distinct element from all of 490 | // the passed-in arrays. 491 | _.union = function() { 492 | return _.uniq(_.flatten(arguments, true)); 493 | }; 494 | 495 | // Produce an array that contains every item shared between all the 496 | // passed-in arrays. 497 | _.intersection = function(array) { 498 | var rest = slice.call(arguments, 1); 499 | return _.filter(_.uniq(array), function(item) { 500 | return _.every(rest, function(other) { 501 | return _.indexOf(other, item) >= 0; 502 | }); 503 | }); 504 | }; 505 | 506 | // Take the difference between one array and a number of other arrays. 507 | // Only the elements present in just the first array will remain. 508 | _.difference = function(array) { 509 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 510 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 511 | }; 512 | 513 | // Zip together multiple lists into a single array -- elements that share 514 | // an index go together. 515 | _.zip = function() { 516 | var length = _.max(_.pluck(arguments, "length").concat(0)); 517 | var results = new Array(length); 518 | for (var i = 0; i < length; i++) { 519 | results[i] = _.pluck(arguments, '' + i); 520 | } 521 | return results; 522 | }; 523 | 524 | // Converts lists into objects. Pass either a single array of `[key, value]` 525 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 526 | // the corresponding values. 527 | _.object = function(list, values) { 528 | if (list == null) return {}; 529 | var result = {}; 530 | for (var i = 0, length = list.length; i < length; i++) { 531 | if (values) { 532 | result[list[i]] = values[i]; 533 | } else { 534 | result[list[i][0]] = list[i][1]; 535 | } 536 | } 537 | return result; 538 | }; 539 | 540 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 541 | // we need this function. Return the position of the first occurrence of an 542 | // item in an array, or -1 if the item is not included in the array. 543 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 544 | // If the array is large and already in sort order, pass `true` 545 | // for **isSorted** to use binary search. 546 | _.indexOf = function(array, item, isSorted) { 547 | if (array == null) return -1; 548 | var i = 0, length = array.length; 549 | if (isSorted) { 550 | if (typeof isSorted == 'number') { 551 | i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); 552 | } else { 553 | i = _.sortedIndex(array, item); 554 | return array[i] === item ? i : -1; 555 | } 556 | } 557 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 558 | for (; i < length; i++) if (array[i] === item) return i; 559 | return -1; 560 | }; 561 | 562 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 563 | _.lastIndexOf = function(array, item, from) { 564 | if (array == null) return -1; 565 | var hasIndex = from != null; 566 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 567 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 568 | } 569 | var i = (hasIndex ? from : array.length); 570 | while (i--) if (array[i] === item) return i; 571 | return -1; 572 | }; 573 | 574 | // Generate an integer Array containing an arithmetic progression. A port of 575 | // the native Python `range()` function. See 576 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 577 | _.range = function(start, stop, step) { 578 | if (arguments.length <= 1) { 579 | stop = start || 0; 580 | start = 0; 581 | } 582 | step = arguments[2] || 1; 583 | 584 | var length = Math.max(Math.ceil((stop - start) / step), 0); 585 | var idx = 0; 586 | var range = new Array(length); 587 | 588 | while(idx < length) { 589 | range[idx++] = start; 590 | start += step; 591 | } 592 | 593 | return range; 594 | }; 595 | 596 | // Function (ahem) Functions 597 | // ------------------ 598 | 599 | // Reusable constructor function for prototype setting. 600 | var ctor = function(){}; 601 | 602 | // Create a function bound to a given object (assigning `this`, and arguments, 603 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 604 | // available. 605 | _.bind = function(func, context) { 606 | var args, bound; 607 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 608 | if (!_.isFunction(func)) throw new TypeError; 609 | args = slice.call(arguments, 2); 610 | return bound = function() { 611 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 612 | ctor.prototype = func.prototype; 613 | var self = new ctor; 614 | ctor.prototype = null; 615 | var result = func.apply(self, args.concat(slice.call(arguments))); 616 | if (Object(result) === result) return result; 617 | return self; 618 | }; 619 | }; 620 | 621 | // Partially apply a function by creating a version that has had some of its 622 | // arguments pre-filled, without changing its dynamic `this` context. 623 | _.partial = function(func) { 624 | var args = slice.call(arguments, 1); 625 | return function() { 626 | return func.apply(this, args.concat(slice.call(arguments))); 627 | }; 628 | }; 629 | 630 | // Bind all of an object's methods to that object. Useful for ensuring that 631 | // all callbacks defined on an object belong to it. 632 | _.bindAll = function(obj) { 633 | var funcs = slice.call(arguments, 1); 634 | if (funcs.length === 0) throw new Error("bindAll must be passed function names"); 635 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 636 | return obj; 637 | }; 638 | 639 | // Memoize an expensive function by storing its results. 640 | _.memoize = function(func, hasher) { 641 | var memo = {}; 642 | hasher || (hasher = _.identity); 643 | return function() { 644 | var key = hasher.apply(this, arguments); 645 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 646 | }; 647 | }; 648 | 649 | // Delays a function for the given number of milliseconds, and then calls 650 | // it with the arguments supplied. 651 | _.delay = function(func, wait) { 652 | var args = slice.call(arguments, 2); 653 | return setTimeout(function(){ return func.apply(null, args); }, wait); 654 | }; 655 | 656 | // Defers a function, scheduling it to run after the current call stack has 657 | // cleared. 658 | _.defer = function(func) { 659 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 660 | }; 661 | 662 | // Returns a function, that, when invoked, will only be triggered at most once 663 | // during a given window of time. Normally, the throttled function will run 664 | // as much as it can, without ever going more than once per `wait` duration; 665 | // but if you'd like to disable the execution on the leading edge, pass 666 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 667 | _.throttle = function(func, wait, options) { 668 | var context, args, result; 669 | var timeout = null; 670 | var previous = 0; 671 | options || (options = {}); 672 | var later = function() { 673 | previous = options.leading === false ? 0 : getTime(); 674 | timeout = null; 675 | result = func.apply(context, args); 676 | context = args = null; 677 | }; 678 | return function() { 679 | var now = getTime(); 680 | if (!previous && options.leading === false) previous = now; 681 | var remaining = wait - (now - previous); 682 | context = this; 683 | args = arguments; 684 | if (remaining <= 0) { 685 | clearTimeout(timeout); 686 | timeout = null; 687 | previous = now; 688 | result = func.apply(context, args); 689 | context = args = null; 690 | } else if (!timeout && options.trailing !== false) { 691 | timeout = setTimeout(later, remaining); 692 | } 693 | return result; 694 | }; 695 | }; 696 | 697 | // Returns a function, that, as long as it continues to be invoked, will not 698 | // be triggered. The function will be called after it stops being called for 699 | // N milliseconds. If `immediate` is passed, trigger the function on the 700 | // leading edge, instead of the trailing. 701 | _.debounce = function(func, wait, immediate) { 702 | var timeout, args, context, timestamp, result; 703 | return function() { 704 | context = this; 705 | args = arguments; 706 | timestamp = getTime(); 707 | var later = function() { 708 | var last = getTime() - timestamp; 709 | if (last < wait) { 710 | timeout = setTimeout(later, wait - last); 711 | } else { 712 | timeout = null; 713 | if (!immediate) { 714 | result = func.apply(context, args); 715 | context = args = null; 716 | } 717 | } 718 | }; 719 | var callNow = immediate && !timeout; 720 | if (!timeout) { 721 | timeout = setTimeout(later, wait); 722 | } 723 | if (callNow) { 724 | result = func.apply(context, args); 725 | context = args = null; 726 | } 727 | 728 | return result; 729 | }; 730 | }; 731 | 732 | // Returns a function that will be executed at most one time, no matter how 733 | // often you call it. Useful for lazy initialization. 734 | _.once = function(func) { 735 | var ran = false, memo; 736 | return function() { 737 | if (ran) return memo; 738 | ran = true; 739 | memo = func.apply(this, arguments); 740 | func = null; 741 | return memo; 742 | }; 743 | }; 744 | 745 | // Returns the first function passed as an argument to the second, 746 | // allowing you to adjust arguments, run code before and after, and 747 | // conditionally execute the original function. 748 | _.wrap = function(func, wrapper) { 749 | return _.partial(wrapper, func); 750 | }; 751 | 752 | // Returns a function that is the composition of a list of functions, each 753 | // consuming the return value of the function that follows. 754 | _.compose = function() { 755 | var funcs = arguments; 756 | return function() { 757 | var args = arguments; 758 | for (var i = funcs.length - 1; i >= 0; i--) { 759 | args = [funcs[i].apply(this, args)]; 760 | } 761 | return args[0]; 762 | }; 763 | }; 764 | 765 | // Returns a function that will only be executed after being called N times. 766 | _.after = function(times, func) { 767 | return function() { 768 | if (--times < 1) { 769 | return func.apply(this, arguments); 770 | } 771 | }; 772 | }; 773 | 774 | // Object Functions 775 | // ---------------- 776 | 777 | // Retrieve the names of an object's properties. 778 | // Delegates to **ECMAScript 5**'s native `Object.keys` 779 | _.keys = nativeKeys || function(obj) { 780 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 781 | var keys = []; 782 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 783 | return keys; 784 | }; 785 | 786 | // Retrieve the values of an object's properties. 787 | _.values = function(obj) { 788 | var keys = _.keys(obj); 789 | var length = keys.length; 790 | var values = new Array(length); 791 | for (var i = 0; i < length; i++) { 792 | values[i] = obj[keys[i]]; 793 | } 794 | return values; 795 | }; 796 | 797 | // Convert an object into a list of `[key, value]` pairs. 798 | _.pairs = function(obj) { 799 | var keys = _.keys(obj); 800 | var length = keys.length; 801 | var pairs = new Array(length); 802 | for (var i = 0; i < length; i++) { 803 | pairs[i] = [keys[i], obj[keys[i]]]; 804 | } 805 | return pairs; 806 | }; 807 | 808 | // Invert the keys and values of an object. The values must be serializable. 809 | _.invert = function(obj) { 810 | var result = {}; 811 | var keys = _.keys(obj); 812 | for (var i = 0, length = keys.length; i < length; i++) { 813 | result[obj[keys[i]]] = keys[i]; 814 | } 815 | return result; 816 | }; 817 | 818 | // Return a sorted list of the function names available on the object. 819 | // Aliased as `methods` 820 | _.functions = _.methods = function(obj) { 821 | var names = []; 822 | for (var key in obj) { 823 | if (_.isFunction(obj[key])) names.push(key); 824 | } 825 | return names.sort(); 826 | }; 827 | 828 | // Extend a given object with all the properties in passed-in object(s). 829 | _.extend = function(obj) { 830 | each(slice.call(arguments, 1), function(source) { 831 | if (source) { 832 | for (var prop in source) { 833 | obj[prop] = source[prop]; 834 | } 835 | } 836 | }); 837 | return obj; 838 | }; 839 | 840 | // Return a copy of the object only containing the whitelisted properties. 841 | _.pick = function(obj) { 842 | var copy = {}; 843 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 844 | each(keys, function(key) { 845 | if (key in obj) copy[key] = obj[key]; 846 | }); 847 | return copy; 848 | }; 849 | 850 | // Return a copy of the object without the blacklisted properties. 851 | _.omit = function(obj) { 852 | var copy = {}; 853 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 854 | for (var key in obj) { 855 | if (!_.contains(keys, key)) copy[key] = obj[key]; 856 | } 857 | return copy; 858 | }; 859 | 860 | // Fill in a given object with default properties. 861 | _.defaults = function(obj) { 862 | each(slice.call(arguments, 1), function(source) { 863 | if (source) { 864 | for (var prop in source) { 865 | if (obj[prop] === void 0) obj[prop] = source[prop]; 866 | } 867 | } 868 | }); 869 | return obj; 870 | }; 871 | 872 | // Create a (shallow-cloned) duplicate of an object. 873 | _.clone = function(obj) { 874 | if (!_.isObject(obj)) return obj; 875 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 876 | }; 877 | 878 | // Invokes interceptor with the obj, and then returns obj. 879 | // The primary purpose of this method is to "tap into" a method chain, in 880 | // order to perform operations on intermediate results within the chain. 881 | _.tap = function(obj, interceptor) { 882 | interceptor(obj); 883 | return obj; 884 | }; 885 | 886 | // Internal recursive comparison function for `isEqual`. 887 | var eq = function(a, b, aStack, bStack) { 888 | // Identical objects are equal. `0 === -0`, but they aren't identical. 889 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 890 | if (a === b) return a !== 0 || 1 / a == 1 / b; 891 | // A strict comparison is necessary because `null == undefined`. 892 | if (a == null || b == null) return a === b; 893 | // Unwrap any wrapped objects. 894 | if (a instanceof _) a = a._wrapped; 895 | if (b instanceof _) b = b._wrapped; 896 | // Compare `[[Class]]` names. 897 | var className = toString.call(a); 898 | if (className != toString.call(b)) return false; 899 | switch (className) { 900 | // Strings, numbers, dates, and booleans are compared by value. 901 | case '[object String]': 902 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 903 | // equivalent to `new String("5")`. 904 | return a == String(b); 905 | case '[object Number]': 906 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 907 | // other numeric values. 908 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 909 | case '[object Date]': 910 | case '[object Boolean]': 911 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 912 | // millisecond representations. Note that invalid dates with millisecond representations 913 | // of `NaN` are not equivalent. 914 | return +a == +b; 915 | // RegExps are compared by their source patterns and flags. 916 | case '[object RegExp]': 917 | return a.source == b.source && 918 | a.global == b.global && 919 | a.multiline == b.multiline && 920 | a.ignoreCase == b.ignoreCase; 921 | } 922 | if (typeof a != 'object' || typeof b != 'object') return false; 923 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 924 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 925 | var length = aStack.length; 926 | while (length--) { 927 | // Linear search. Performance is inversely proportional to the number of 928 | // unique nested structures. 929 | if (aStack[length] == a) return bStack[length] == b; 930 | } 931 | // Objects with different constructors are not equivalent, but `Object`s 932 | // from different frames are. 933 | var aCtor = a.constructor, bCtor = b.constructor; 934 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 935 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 936 | return false; 937 | } 938 | // Add the first object to the stack of traversed objects. 939 | aStack.push(a); 940 | bStack.push(b); 941 | var size = 0, result = true; 942 | // Recursively compare objects and arrays. 943 | if (className == '[object Array]') { 944 | // Compare array lengths to determine if a deep comparison is necessary. 945 | size = a.length; 946 | result = size == b.length; 947 | if (result) { 948 | // Deep compare the contents, ignoring non-numeric properties. 949 | while (size--) { 950 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 951 | } 952 | } 953 | } else { 954 | // Deep compare objects. 955 | for (var key in a) { 956 | if (_.has(a, key)) { 957 | // Count the expected number of properties. 958 | size++; 959 | // Deep compare each member. 960 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 961 | } 962 | } 963 | // Ensure that both objects contain the same number of properties. 964 | if (result) { 965 | for (key in b) { 966 | if (_.has(b, key) && !(size--)) break; 967 | } 968 | result = !size; 969 | } 970 | } 971 | // Remove the first object from the stack of traversed objects. 972 | aStack.pop(); 973 | bStack.pop(); 974 | return result; 975 | }; 976 | 977 | // Perform a deep comparison to check if two objects are equal. 978 | _.isEqual = function(a, b) { 979 | return eq(a, b, [], []); 980 | }; 981 | 982 | // Is a given array, string, or object empty? 983 | // An "empty" object has no enumerable own-properties. 984 | _.isEmpty = function(obj) { 985 | if (obj == null) return true; 986 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 987 | for (var key in obj) if (_.has(obj, key)) return false; 988 | return true; 989 | }; 990 | 991 | // Is a given value a DOM element? 992 | _.isElement = function(obj) { 993 | return !!(obj && obj.nodeType === 1); 994 | }; 995 | 996 | // Is a given value an array? 997 | // Delegates to ECMA5's native Array.isArray 998 | _.isArray = nativeIsArray || function(obj) { 999 | return toString.call(obj) == '[object Array]'; 1000 | }; 1001 | 1002 | // Is a given variable an object? 1003 | _.isObject = function(obj) { 1004 | return obj === Object(obj); 1005 | }; 1006 | 1007 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 1008 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 1009 | _['is' + name] = function(obj) { 1010 | return toString.call(obj) == '[object ' + name + ']'; 1011 | }; 1012 | }); 1013 | 1014 | // Define a fallback version of the method in browsers (ahem, IE), where 1015 | // there isn't any inspectable "Arguments" type. 1016 | if (!_.isArguments(arguments)) { 1017 | _.isArguments = function(obj) { 1018 | return !!(obj && _.has(obj, 'callee')); 1019 | }; 1020 | } 1021 | 1022 | // Optimize `isFunction` if appropriate. 1023 | if (typeof (/./) !== 'function') { 1024 | _.isFunction = function(obj) { 1025 | return typeof obj === 'function'; 1026 | }; 1027 | } 1028 | 1029 | // Is a given object a finite number? 1030 | _.isFinite = function(obj) { 1031 | return isFinite(obj) && !isNaN(parseFloat(obj)); 1032 | }; 1033 | 1034 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 1035 | _.isNaN = function(obj) { 1036 | return _.isNumber(obj) && obj != +obj; 1037 | }; 1038 | 1039 | // Is a given value a boolean? 1040 | _.isBoolean = function(obj) { 1041 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 1042 | }; 1043 | 1044 | // Is a given value equal to null? 1045 | _.isNull = function(obj) { 1046 | return obj === null; 1047 | }; 1048 | 1049 | // Is a given variable undefined? 1050 | _.isUndefined = function(obj) { 1051 | return obj === void 0; 1052 | }; 1053 | 1054 | // Shortcut function for checking if an object has a given property directly 1055 | // on itself (in other words, not on a prototype). 1056 | _.has = function(obj, key) { 1057 | return hasOwnProperty.call(obj, key); 1058 | }; 1059 | 1060 | // Utility Functions 1061 | // ----------------- 1062 | 1063 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1064 | // previous owner. Returns a reference to the Underscore object. 1065 | _.noConflict = function() { 1066 | root._ = previousUnderscore; 1067 | return this; 1068 | }; 1069 | 1070 | // Keep the identity function around for default iterators. 1071 | _.identity = function(value) { 1072 | return value; 1073 | }; 1074 | 1075 | // Run a function **n** times. 1076 | _.times = function(n, iterator, context) { 1077 | var accum = Array(Math.max(0, n)); 1078 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); 1079 | return accum; 1080 | }; 1081 | 1082 | // Return a random integer between min and max (inclusive). 1083 | _.random = function(min, max) { 1084 | if (max == null) { 1085 | max = min; 1086 | min = 0; 1087 | } 1088 | return min + Math.floor(Math.random() * (max - min + 1)); 1089 | }; 1090 | 1091 | // List of HTML entities for escaping. 1092 | var entityMap = { 1093 | escape: { 1094 | '&': '&', 1095 | '<': '<', 1096 | '>': '>', 1097 | '"': '"', 1098 | "'": ''' 1099 | } 1100 | }; 1101 | entityMap.unescape = _.invert(entityMap.escape); 1102 | 1103 | // Regexes containing the keys and values listed immediately above. 1104 | var entityRegexes = { 1105 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1106 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1107 | }; 1108 | 1109 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1110 | _.each(['escape', 'unescape'], function(method) { 1111 | _[method] = function(string) { 1112 | if (string == null) return ''; 1113 | return ('' + string).replace(entityRegexes[method], function(match) { 1114 | return entityMap[method][match]; 1115 | }); 1116 | }; 1117 | }); 1118 | 1119 | // If the value of the named `property` is a function then invoke it with the 1120 | // `object` as context; otherwise, return it. 1121 | _.result = function(object, property) { 1122 | if (object == null) return void 0; 1123 | var value = object[property]; 1124 | return _.isFunction(value) ? value.call(object) : value; 1125 | }; 1126 | 1127 | // Add your own custom functions to the Underscore object. 1128 | _.mixin = function(obj) { 1129 | each(_.functions(obj), function(name) { 1130 | var func = _[name] = obj[name]; 1131 | _.prototype[name] = function() { 1132 | var args = [this._wrapped]; 1133 | push.apply(args, arguments); 1134 | return result.call(this, func.apply(_, args)); 1135 | }; 1136 | }); 1137 | }; 1138 | 1139 | // Generate a unique integer id (unique within the entire client session). 1140 | // Useful for temporary DOM ids. 1141 | var idCounter = 0; 1142 | _.uniqueId = function(prefix) { 1143 | var id = ++idCounter + ''; 1144 | return prefix ? prefix + id : id; 1145 | }; 1146 | 1147 | // By default, Underscore uses ERB-style template delimiters, change the 1148 | // following template settings to use alternative delimiters. 1149 | _.templateSettings = { 1150 | evaluate : /<%([\s\S]+?)%>/g, 1151 | interpolate : /<%=([\s\S]+?)%>/g, 1152 | escape : /<%-([\s\S]+?)%>/g 1153 | }; 1154 | 1155 | // When customizing `templateSettings`, if you don't want to define an 1156 | // interpolation, evaluation or escaping regex, we need one that is 1157 | // guaranteed not to match. 1158 | var noMatch = /(.)^/; 1159 | 1160 | // Certain characters need to be escaped so that they can be put into a 1161 | // string literal. 1162 | var escapes = { 1163 | "'": "'", 1164 | '\\': '\\', 1165 | '\r': 'r', 1166 | '\n': 'n', 1167 | '\t': 't', 1168 | '\u2028': 'u2028', 1169 | '\u2029': 'u2029' 1170 | }; 1171 | 1172 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1173 | 1174 | // JavaScript micro-templating, similar to John Resig's implementation. 1175 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1176 | // and correctly escapes quotes within interpolated code. 1177 | _.template = function(text, data, settings) { 1178 | var render; 1179 | settings = _.defaults({}, settings, _.templateSettings); 1180 | 1181 | // Combine delimiters into one regular expression via alternation. 1182 | var matcher = new RegExp([ 1183 | (settings.escape || noMatch).source, 1184 | (settings.interpolate || noMatch).source, 1185 | (settings.evaluate || noMatch).source 1186 | ].join('|') + '|$', 'g'); 1187 | 1188 | // Compile the template source, escaping string literals appropriately. 1189 | var index = 0; 1190 | var source = "__p+='"; 1191 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1192 | source += text.slice(index, offset) 1193 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1194 | 1195 | if (escape) { 1196 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1197 | } 1198 | if (interpolate) { 1199 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1200 | } 1201 | if (evaluate) { 1202 | source += "';\n" + evaluate + "\n__p+='"; 1203 | } 1204 | index = offset + match.length; 1205 | return match; 1206 | }); 1207 | source += "';\n"; 1208 | 1209 | // If a variable is not specified, place data values in local scope. 1210 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1211 | 1212 | source = "var __t,__p='',__j=Array.prototype.join," + 1213 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1214 | source + "return __p;\n"; 1215 | 1216 | try { 1217 | render = new Function(settings.variable || 'obj', '_', source); 1218 | } catch (e) { 1219 | e.source = source; 1220 | throw e; 1221 | } 1222 | 1223 | if (data) return render(data, _); 1224 | var template = function(data) { 1225 | return render.call(this, data, _); 1226 | }; 1227 | 1228 | // Provide the compiled function source as a convenience for precompilation. 1229 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1230 | 1231 | return template; 1232 | }; 1233 | 1234 | // Add a "chain" function, which will delegate to the wrapper. 1235 | _.chain = function(obj) { 1236 | return _(obj).chain(); 1237 | }; 1238 | 1239 | // OOP 1240 | // --------------- 1241 | // If Underscore is called as a function, it returns a wrapped object that 1242 | // can be used OO-style. This wrapper holds altered versions of all the 1243 | // underscore functions. Wrapped objects may be chained. 1244 | 1245 | // Helper function to continue chaining intermediate results. 1246 | var result = function(obj) { 1247 | return this._chain ? _(obj).chain() : obj; 1248 | }; 1249 | 1250 | // Add all of the Underscore functions to the wrapper object. 1251 | _.mixin(_); 1252 | 1253 | // Add all mutator Array functions to the wrapper. 1254 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1255 | var method = ArrayProto[name]; 1256 | _.prototype[name] = function() { 1257 | var obj = this._wrapped; 1258 | method.apply(obj, arguments); 1259 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1260 | return result.call(this, obj); 1261 | }; 1262 | }); 1263 | 1264 | // Add all accessor Array functions to the wrapper. 1265 | each(['concat', 'join', 'slice'], function(name) { 1266 | var method = ArrayProto[name]; 1267 | _.prototype[name] = function() { 1268 | return result.call(this, method.apply(this._wrapped, arguments)); 1269 | }; 1270 | }); 1271 | 1272 | _.extend(_.prototype, { 1273 | 1274 | // Start chaining a wrapped Underscore object. 1275 | chain: function() { 1276 | this._chain = true; 1277 | return this; 1278 | }, 1279 | 1280 | // Extracts the result from a wrapped and chained object. 1281 | value: function() { 1282 | return this._wrapped; 1283 | } 1284 | 1285 | }); 1286 | 1287 | }).call(this); 1288 | -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Binding.js: -------------------------------------------------------------------------------- 1 | //================================================== 2 | // 创建监控属性或数组,自变量,由用户触发其改变 3 | // 1 基本数据结构 4 | // 2 数组结构 5 | // 3 对象结构 6 | //==================================================== 7 | Aaron.register('Binding', function(Directive) { 8 | 9 | // VMProto.setupObserver = function(name, val, valueType) { 10 | 11 | // var compiler = this, 12 | // bindings = compiler.bindings, 13 | // options = compiler.options, 14 | // observer = compiler.observer = new Emitter(compiler.vm) 15 | 16 | 17 | // var self = this; 18 | 19 | // var set = function() { 20 | // if (stopRepeatAssign) { 21 | // return //阻止重复赋值 22 | // } 23 | // //确定是新设置值 24 | // if (!isEqual(preValue, newValue)) { 25 | // self.originalModel[name] = newValue //更新$model中的值 26 | // //自身的依赖更新 27 | // self.notifySubscribers(accessor); 28 | // } 29 | // } 30 | 31 | // var get = function() { 32 | // self.collectSubscribers(accessor) //收集视图函数 33 | // return accessor.$vmodel || preValue //返回需要获取的值 34 | // } 35 | 36 | // function accessor(newValue) { 37 | // var vmodel = self.watchProperties.vmodel 38 | // var preValue = self.originalModel[name]; 39 | // if (arguments.length) { 40 | // set(vmodel, preValue) 41 | // } else { 42 | // get(vmodel, preValue); 43 | // } 44 | // }; 45 | 46 | // accessor[Aaron.subscribers] = [] //订阅者数组,保存所有的view依赖映射 47 | 48 | // //生成监控属性要区分内部值的类型 49 | // if (rchecktype.test(valueType)) { //复杂数据,通过递归处理 50 | // //复杂结构 51 | // } else { 52 | // //普通的基本类型 53 | // self.originals[name] = val; 54 | // } 55 | 56 | // return accessor; 57 | // } 58 | 59 | 60 | function Binding(name, val, valueType){ 61 | 62 | this.subscribers = [] //订阅者数组,保存所有的view依赖映射 63 | 64 | 65 | console.log(name, val, valueType) 66 | } 67 | 68 | 69 | BindPro = Binding.prototype; 70 | 71 | 72 | BindPro.set = function() { 73 | 74 | } 75 | 76 | 77 | BindPro.get = function() { 78 | 79 | } 80 | 81 | 82 | return Binding; 83 | 84 | }); -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Collection.js: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 监控数组(与ms-each配合使用) * 3 | **********************************************************************/ 4 | 5 | function Collection(model) { 6 | this._id = generateID(); 7 | this[subscribers] = [] 8 | this._model = model 9 | this._events = {} 10 | this._ = modelFactory({ 11 | 'length': model.length 12 | }) 13 | this._.subscribe('length', function(a, b) { 14 | 15 | }) 16 | aaObserver.call(this); 17 | } 18 | 19 | Collection.prototype = { 20 | 21 | //转化成对应的数据模型 22 | _convert: function(val) { 23 | var type = $.type(val) 24 | if (rchecktype.test(type)) { //如果是多维结构 25 | alert(1) 26 | val = val.$id ? val : modelFactory(val, val) 27 | } 28 | return val 29 | }, 30 | 31 | //数据,数据长度 32 | _add: function(array, pos) { 33 | //获取当前数组的长度 34 | var oldLength = this.length; 35 | var self = this; 36 | pos = typeof pos === "number" ? pos : oldLength; 37 | var added = []; 38 | //把数组中的每一个参数给再次分解 39 | $.each(array, function(i, arr) { 40 | added[i] = self._convert(arr) //多维结构继续分解 41 | }); 42 | // [].slice.apply(this, [pos, 0].concat(added)); 43 | }, 44 | 45 | push: function(model) { 46 | this._model = model; 47 | this._add.apply(this, arguments) 48 | console.log(this) 49 | }, 50 | } -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Compiler.js: -------------------------------------------------------------------------------- 1 | //=============================================== 2 | // 数据源转化工厂,元数据转转成视图模型对象 3 | // 对应多出 4 | // 1 set/get方法 5 | // 2 自定义事件能力 6 | //================================================ 7 | Aaron.register('Compiler', [ 8 | 'Utils', 9 | 'Emitter', 10 | 'Binding' 11 | ], function(Utils,Emitter,Binding) { 12 | 13 | var typeOf = Utils.typeOf; 14 | var isEqual = Utils.isEqual; 15 | var rchecktype = /^(?:array|object)$/i; //判断当前的类型只能是数组或者对象 16 | var withValue = Utils.withValue; 17 | var defProtectes = Utils.defProtectes; 18 | 19 | function Compiler(vm, name, options) { 20 | 21 | var compiler = this; 22 | 23 | compiler.options = options; 24 | 25 | //模型对象 26 | compiler.vm = vm; 27 | //原始模型数据 28 | compiler.originals = {}; 29 | //普通属性 30 | compiler.commons = {}; 31 | //监控属性,需要转化成set get访问控制器 32 | compiler.accessors = {}, 33 | //编译绑定 34 | compiler.bindings = Utils.hash() 35 | 36 | //===================================================== 37 | // 38 | // vm 设置 39 | // 40 | //====================================================== 41 | vm.$ = {} 42 | vm.$options = options 43 | vm.$event = null 44 | 45 | //解析对应的定义 46 | _.each(options, function(val, name) { 47 | compiler.resolveModel(name, val); 48 | }) 49 | 50 | //转成访问控制器 51 | defProtectes(compiler, withValue(compiler.accessingProperties)); 52 | 53 | //没有转化的函数,混入到新的vm对象中 54 | _.each(compiler.normalProperties, function(val, name) { 55 | compiler[name] = val 56 | }) 57 | 58 | 59 | compiler.id = Utils.UUID(); 60 | 61 | compiler[Aaron.subscribers] = [] 62 | 63 | console.log(compiler) 64 | 65 | return compiler; 66 | } 67 | 68 | 69 | var VMProto = Compiler.prototype; 70 | 71 | 72 | //================================================= 73 | // 74 | // 解析模型,转化成对应的set/get处理方法 75 | // 76 | // 1 监控属性 77 | // 2 计算属性 针对监控属性的加强 78 | // 79 | //================================================== 80 | VMProto.resolveModel = function(name, val) { 81 | 82 | var compiler = this; 83 | 84 | //缓存原始值 85 | compiler.originals[name] = val 86 | 87 | //得到值类型 88 | var valueType = typeOf(val); 89 | 90 | //如果是函数,不用监控,意味着这是事件回调句柄 91 | if (valueType === 'function') { 92 | return compiler.commons[name] = val 93 | } 94 | 95 | //如果值类型是对象,并且有get方法,为计算属性 96 | if (valueType === "object" && typeof val.get === "function" && Object.keys(val).length <= 2) { 97 | 98 | } else { 99 | //转化监控属性 100 | var accessor = compiler.setupObserver(name, val, valueType); 101 | } 102 | 103 | //保存监控处理 104 | compiler.accessors[name] = accessor; 105 | } 106 | 107 | //================================================== 108 | // 创建监控属性或数组,自变量,由用户触发其改变 109 | // 1 基本数据结构 110 | // 2 数组结构 111 | // 3 对象结构 112 | //==================================================== 113 | VMProto.setupObserver = function(name, val, valueType) { 114 | 115 | var compiler = this, 116 | bindings = compiler.bindings, 117 | options = compiler.options, 118 | observer = compiler.observer = new Emitter(compiler.vm) 119 | 120 | 121 | var self = this; 122 | 123 | var set = function() { 124 | if (stopRepeatAssign) { 125 | return //阻止重复赋值 126 | } 127 | //确定是新设置值 128 | if (!isEqual(preValue, newValue)) { 129 | self.originalModel[name] = newValue //更新$model中的值 130 | //自身的依赖更新 131 | self.notifySubscribers(accessor); 132 | } 133 | } 134 | 135 | var get = function() { 136 | self.collectSubscribers(accessor) //收集视图函数 137 | return accessor.$vmodel || preValue //返回需要获取的值 138 | } 139 | 140 | function accessor(newValue) { 141 | var vmodel = self.watchProperties.vmodel 142 | var preValue = self.originalModel[name]; 143 | if (arguments.length) { 144 | set(vmodel,preValue) 145 | } else { 146 | get(vmodel,preValue); 147 | } 148 | }; 149 | 150 | accessor[Aaron.subscribers] = [] //订阅者数组,保存所有的view依赖映射 151 | 152 | //生成监控属性要区分内部值的类型 153 | if (rchecktype.test(valueType)) { //复杂数据,通过递归处理 154 | //复杂结构 155 | } else { 156 | //普通的基本类型 157 | self.originals[name] = val; 158 | } 159 | 160 | return accessor; 161 | } 162 | 163 | 164 | //通知依赖于这个访问器的订阅者更新自身 165 | VMProto.notifySubscribers = function(accessor) { 166 | var list = accessor[subscribers] 167 | if (list && list.length) { 168 | var args = [].slice.call(arguments, 1) 169 | for (var i = list.length, fn; fn = list[--i];) { 170 | var el = fn.element 171 | fn.handler(fn.evaluator.apply(0, fn.args || []), el, fn) 172 | } 173 | } 174 | } 175 | 176 | 177 | //收集依赖于这个访问器的订阅者 178 | VMProto.collectSubscribers = function(accessor) { 179 | if (Registry[expose]) { //只有当注册了才收集 180 | var list = accessor[subscribers] 181 | list && ensure(list, Registry[expose]) //只有数组不存在此元素才push进去 182 | } 183 | } 184 | 185 | return Compiler; 186 | }); -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Config.js: -------------------------------------------------------------------------------- 1 | //=============================================== 2 | // 3 | // 全局配置文件 4 | // 5 | //================================================ 6 | Aaron.register('Config',function(Directive) { 7 | Aaron.root = document.documentElement; 8 | Aaron.Registry = {} //将函数曝光到此对象上,方便访问器收集依赖 9 | Aaron.expose = Date.now(); 10 | Aaron.subscribers = 'aaron-' + Aaron.expose; 11 | Aaron.stopRepeatAssign = false; 12 | Aaron.documentFragment = document.createDocumentFragment(); 13 | 14 | 15 | return { 16 | 'prefix': 'ao-' //命名私有前缀 17 | } 18 | }); -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Directive.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JsAaron/mvvm/d3b84d52a2cc0984dfbd4db505990eb28e2f5f6b/3.MVVM框架设计及实现/Directive.js -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Emitter.js: -------------------------------------------------------------------------------- 1 | Aaron.register('Emitter', function() { 2 | 3 | function Emitter(ctx) { 4 | this._ctx = ctx || this 5 | } 6 | 7 | var EmitterProto = Emitter.prototype 8 | 9 | EmitterProto.on = function(event, fn) { 10 | this._cbs = this._cbs || {}; 11 | (this._cbs[event] = this._cbs[event] || []) 12 | .push(fn) 13 | return this 14 | } 15 | 16 | EmitterProto.once = function(event, fn) { 17 | var self = this 18 | this._cbs = this._cbs || {} 19 | 20 | function on() { 21 | self.off(event, on) 22 | fn.apply(this, arguments) 23 | } 24 | 25 | on.fn = fn 26 | this.on(event, on) 27 | return this 28 | } 29 | 30 | EmitterProto.off = function(event, fn) { 31 | this._cbs = this._cbs || {} 32 | 33 | // all 34 | if (!arguments.length) { 35 | this._cbs = {} 36 | return this 37 | } 38 | 39 | // specific event 40 | var callbacks = this._cbs[event] 41 | if (!callbacks) return this 42 | 43 | // remove all handlers 44 | if (arguments.length === 1) { 45 | delete this._cbs[event] 46 | return this 47 | } 48 | 49 | // remove specific handler 50 | var cb 51 | for (var i = 0; i < callbacks.length; i++) { 52 | cb = callbacks[i] 53 | if (cb === fn || cb.fn === fn) { 54 | callbacks.splice(i, 1) 55 | break 56 | } 57 | } 58 | return this 59 | } 60 | 61 | EmitterProto.emit = function(event, a, b, c) { 62 | this._cbs = this._cbs || {} 63 | var callbacks = this._cbs[event] 64 | 65 | if (callbacks) { 66 | callbacks = callbacks.slice(0) 67 | for (var i = 0, len = callbacks.length; i < len; i++) { 68 | callbacks[i].call(this._ctx, a, b, c) 69 | } 70 | } 71 | 72 | return this 73 | } 74 | 75 | return Emitter 76 | }) -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Observer.js: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************** 3 | * 简单的自定义事件,观察者模式 4 | * @by Aaron 5 | * github:https://github.com/JsAaron/aaObserver 6 | * blog:http://www.cnblogs.com/aaronjs/ 7 | *****************************************************************/ 8 | Aaron.register('Observer', function() { 9 | 10 | var slice = Array.prototype.slice, 11 | nativeForEach = Array.prototype.forEach; 12 | 13 | function each(obj, callback, context) { 14 | if (obj == null) return; 15 | //如果支持本地forEach方法,并且是函数 16 | if (nativeForEach && obj.forEach === nativeForEach) { 17 | obj.forEach(callback, context); 18 | } else if (obj.length === +obj.length) { 19 | //for循环迭代 20 | for (var i = 0, l = obj.length; i < l; i++) { 21 | callback.call(context, obj[i], i, obj); 22 | } 23 | } 24 | }; 25 | 26 | function bind(event, fn) { 27 | var events = this.events = this.events || {}, 28 | parts = event.split(/\s+/), 29 | i = 0, 30 | num = parts.length, 31 | part; 32 | if (events[event] && events[event].length) return this; 33 | each(parts, function(part, index) { 34 | events[part] = events[part] || []; 35 | events[part].push(fn); 36 | }) 37 | return this; 38 | } 39 | 40 | function one(event, fn) { 41 | this.bind(event, function fnc() { 42 | fn.apply(this, slice.call(arguments)); 43 | this.unbind(event, fnc); 44 | }); 45 | return this; 46 | } 47 | 48 | function unbind(event, fn) { 49 | var events = this.events, 50 | eventName, i, parts, num; 51 | if (!events) return; 52 | parts = event.split(/\s+/); 53 | each(parts, function(eventName, index) { 54 | if (eventName in events !== false) { 55 | events[eventName].splice(events[eventName].indexOf(fn), 1); 56 | if (!events[eventName].length) { //修正没有事件直接删除空数组 57 | delete events[eventName]; 58 | } 59 | } 60 | }) 61 | return this; 62 | } 63 | 64 | function trigger(event) { 65 | var events = this.events, 66 | i, args, falg; 67 | if (!events || event in events === false) return; 68 | args = slice.call(arguments, 1); 69 | for (i = events[event].length - 1; i >= 0; i--) { 70 | falg = events[event][i].apply(this, args); 71 | } 72 | return falg; //修正带返回 73 | } 74 | 75 | return function() { 76 | this.subscribe = bind; 77 | this.remove = unbind; 78 | this.publish = trigger; 79 | this.one = one; 80 | return this; 81 | }; 82 | }); 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/Utils.js: -------------------------------------------------------------------------------- 1 | //=============================================== 2 | // 数据源转化工厂,元数据转转成视图模型对象 3 | // 对应多出 4 | // 1 set/get方法 5 | // 2 自定义事件能力 6 | //================================================ 7 | Aaron.register('Utils', [ 8 | 'Config' 9 | ], function(Config) { 10 | 11 | var global = this, 12 | objectPrototype = Object.prototype, 13 | arrayPrototype = Array.prototype, 14 | toString = objectPrototype.toString 15 | 16 | 17 | var utils = { 18 | 19 | slice: arrayPrototype.slice, 20 | 21 | //类型判断 22 | typeOf: function(value) { 23 | if (value === null) { 24 | return 'null'; 25 | } 26 | 27 | var type = typeof value; 28 | 29 | if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') { 30 | return type; 31 | } 32 | 33 | var typeToString = toString.call(value); 34 | 35 | switch(typeToString) { 36 | case '[object Array]': 37 | return 'array'; 38 | case '[object Date]': 39 | return 'date'; 40 | case '[object Boolean]': 41 | return 'boolean'; 42 | case '[object Number]': 43 | return 'number'; 44 | case '[object RegExp]': 45 | return 'regexp'; 46 | } 47 | 48 | if (type === 'function') { 49 | return 'function'; 50 | } 51 | 52 | if (type === 'object') { 53 | if (value.nodeType !== undefined) { 54 | if (value.nodeType === 3) { 55 | return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace'; 56 | } 57 | else { 58 | return 'element'; 59 | } 60 | } 61 | 62 | return 'object'; 63 | } 64 | }, 65 | 66 | UUID: function() { 67 | return "aaron-" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) 68 | }, 69 | 70 | /** 71 | * 创建一个没有原形的对象 72 | * 模式hash排列 73 | * @return {[type]} [description] 74 | */ 75 | hash: function () { 76 | return Object.create(null) 77 | }, 78 | 79 | ensure: function(target, item) { 80 | //只有当前数组不存在此元素时只添加它 81 | if (target.indexOf(item) === -1) { 82 | target.push(item) 83 | } 84 | return target; 85 | }, 86 | 87 | //转化一组 88 | defProtectes: function(object, values) { 89 | Object.defineProperties(object, values); 90 | }, 91 | 92 | withValue: function(access) { 93 | var descriptors = {} 94 | for (var i in access) { 95 | descriptors[i] = { 96 | get : access[i], 97 | set : access[i], 98 | enumerable : true, 99 | configurable : true 100 | } 101 | } 102 | return descriptors 103 | }, 104 | 105 | isEqual: function(v1, v2) { 106 | if (v1 === 0 && v2 === 0) { 107 | return 1 / v1 === 1 / v2 108 | } else if (v1 !== v1) { 109 | return v2 !== v2 110 | } else { 111 | return v1 === v2; 112 | } 113 | }, 114 | 115 | //得到属性节点,并且移除 116 | attr: function(el, type) { 117 | var attr = Config.prefix + type; 118 | var val = el.getAttribute(attr) 119 | //移除指令 120 | if (val !== null) { 121 | el.removeAttribute(attr) 122 | } 123 | return val 124 | }, 125 | 126 | } 127 | 128 | return utils; 129 | }); -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/ViewModel.js: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 前端MVVM框架的实现 3 | * 第一弹:基本双向绑定 4 | * 第二弹:DOM模块化模板引擎 5 | * 第三弹:引入AMD代码组织风格,分析文件结构 6 | * @by Aaron 7 | * 源码分析博客:http://www.cnblogs.com/aaronjs/ 8 | * 源码分析Github:https://github.com/JsAaron/aaMVVM 9 | * Avalon源码:https://github.com/RubyLouvre/avalon 10 | * DOM的处理前期全采用jQuery代替 11 | *****************************************************************/ 12 | 13 | Aaron.register('ViewModel', [ 14 | 'Utils', 15 | 'Config', 16 | 'Directive', 17 | 'Compiler' 18 | ],function(Utils, Config, Directive, Compiler) { 19 | 20 | var vmCache = {}; //缓存所有vm对象 21 | var interpolate = /\{\{(.*?)\}\}/; //匹配{{任何}} 22 | var slice = Utils.slice; 23 | var matchAttr = /ao-(\w+)-?(.*)/;//匹配属性 ao-css-width 24 | 25 | //模型对象 26 | var ViewModel = function(name, options) { 27 | 28 | //编译转化后的set get模型对象 29 | this.compiler = new Compiler(this, name, options); 30 | 31 | this.name = name; 32 | 33 | //查找到根节点 34 | this.el = this.rootElement(name); 35 | 36 | //建立绑定关系 37 | this.compile(this.el) 38 | 39 | vmCache[name] = this; 40 | }; 41 | 42 | //定义模型接口 43 | ViewModel.define = function(name, options) { 44 | if (!vmCache[name]) { 45 | return new ViewModel(name, options); 46 | } 47 | }; 48 | 49 | //实现数据视图绑定 50 | ViewModel.binding = function(name, node) { 51 | var vm; 52 | if (vm = vmCache[name]) { 53 | vm.compile(node); 54 | } 55 | } 56 | 57 | 58 | var ViewModelProto = ViewModel.prototype; 59 | 60 | /** 61 | * 找到根节点 62 | * @param {[type]} name [description] 63 | * @return {[type]} [description] 64 | */ 65 | ViewModelProto.rootElement = function(name){ 66 | return document.getElementById(name) 67 | } 68 | 69 | /** 70 | * 开始建立绑定关系 71 | * @param {[type]} node [description] 72 | * @return {[type]} [description] 73 | */ 74 | ViewModelProto.compile = function(node) { 75 | var nodeType = node.nodeType 76 | if (nodeType === 1) { 77 | this.compileElement(node) 78 | } else if (nodeType === 3 && interpolate.test(node.data)) { 79 | this.compileTextNode(node) 80 | } 81 | //如果当前元素还有子节点,递归 82 | if (node.hasChildNodes()) { 83 | slice.call(node.childNodes).forEach(function(node) { 84 | this.compile(node) 85 | }, this) 86 | } 87 | } 88 | 89 | /** 90 | * 解析元素节点 91 | * @return {[type]} [description] 92 | */ 93 | ViewModelProto.compileElement = function(node) { 94 | //保证这个节点上有属性 95 | if (node.hasAttributes()) { 96 | var prefix = Config.prefix, 97 | attrs = slice.call(node.attributes), 98 | i = attrs.length, 99 | attr, match, type, directive; 100 | 101 | while (i--) { //多个属性 102 | attr = attrs[i]; 103 | if (match = attr.name.match(matchAttr)) { //匹配ao-开头指令 104 | type = match[1]; 105 | //如果能找到对应的指令处理 106 | if (directive = Directive.createHandlers(type, attr.value, node, this)) { 107 | this.bindDirective(directive) 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * 解析文本节点 116 | * @return {[type]} [description] 117 | */ 118 | ViewModelProto.compileTextNode = function(node) { 119 | // console.log(node) 120 | } 121 | 122 | 123 | /** 124 | * 绑定指令 125 | * @return {[type]} [description] 126 | */ 127 | ViewModelProto.bindDirective = function(directive){ 128 | 129 | if (!directive) return; 130 | 131 | // console.log(directive) 132 | } 133 | 134 | 135 | 136 | 137 | // function parseExprProxy(code, scopes, data) { 138 | // parseExpr(code, scopes, data) 139 | // //如果存在求值函数 140 | // if (data.evaluator) { 141 | // //找到对应的处理句柄 142 | // data.handler = bindingExecutors[data.type]; 143 | // data.evaluator.toString = function() { 144 | // return data.type + " binding to eval(" + code + ")" 145 | // } 146 | // //方便调试 147 | // //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定 148 | // //将它移出订阅者列表 149 | // registerSubscriber(data) 150 | // } 151 | // } 152 | 153 | // //生成求值函数与 154 | // //视图刷新函数 155 | // function parseExpr(code, scopes, data) { 156 | // var dataType = data.type 157 | // var name = "vm" + expose; 158 | // var prefix = "var " + data.value + " = " + name + "." + data.value; 159 | // data.args = [scopes]; 160 | // //绑定类型 161 | // if (dataType === 'click') { 162 | // code = 'click' 163 | // code = code.replace("(", ".call(this,"); 164 | // code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") 165 | // var lastIndex = code.lastIndexOf("\nreturn") 166 | // var header = code.slice(0, lastIndex) 167 | // var footer = code.slice(lastIndex) 168 | // code = header + "\nif(MVVM.openComputedCollect) return ;" + footer; 169 | // var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 170 | // } else { 171 | // var code = "\nreturn " + data.value + ";"; 172 | // var fn = Function.apply(noop, [name].concat("'use strict';\n" + prefix + ";" + code)) 173 | // } 174 | // //生成求值函数 175 | // data.evaluator = fn; 176 | // } 177 | 178 | // /********************************************************************* 179 | // * 依赖收集与触发 * 180 | // **********************************************************************/ 181 | // function registerSubscriber(data) { 182 | // Registry[expose] = data //暴光此函数,方便collectSubscribers收集 183 | // MVVM.openComputedCollect = true //排除事件处理函数 184 | // var fn = data.evaluator 185 | // if (fn) { //如果是求值函数 186 | // data.handler(fn.apply(0, data.args), data.element, data) 187 | // } 188 | // MVVM.openComputedCollect = false 189 | // delete Registry[expose] 190 | // } 191 | 192 | // var bindingHandlers = { 193 | // css: function(data, vmodel) { 194 | // var text = data.value.trim(); 195 | // data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 196 | // parseExprProxy(text, vmodel, data) 197 | // }, 198 | // click: function(data, vmodel) { 199 | // var value = data.value 200 | // data.type = "on" 201 | // data.hasArgs = void 0 202 | // data.handlerName = "on" 203 | // parseExprProxy(value, vmodel, data) 204 | // }, 205 | // text: function(data, vmodel) { 206 | // parseExprProxy(data.value, vmodel, data) 207 | // } 208 | // } 209 | 210 | // //执行最终的处理代码 211 | // var bindingExecutors = { 212 | // //修改css 213 | // css: function(val, elem, data) { 214 | // var method = data.type, 215 | // attrName = data.param; 216 | // $(elem).css(attrName, val) 217 | // }, 218 | // on: function(val, elem, data) { 219 | // var fn = data.evaluator 220 | // var args = data.args 221 | // var vmodels = data.vmodels 222 | // var callback = function(e) { 223 | // return fn.apply(0, args).call(this, e) 224 | // } 225 | // elem.addEventListener('click', callback, false) 226 | // data.evaluator = data.handler = noop 227 | // }, 228 | // text: function(val, elem, data) { 229 | // if (data.nodeType === 3) { 230 | // data.node.data = val 231 | // } else { 232 | // $(elem).text(val) 233 | // } 234 | // } 235 | // } 236 | 237 | // var isEqual = Object.is || function(v1, v2) { 238 | // if (v1 === 0 && v2 === 0) { 239 | // return 1 / v1 === 1 / v2 240 | // } else if (v1 !== v1) { 241 | // return v2 !== v2 242 | // } else { 243 | // return v1 === v2; 244 | // } 245 | // } 246 | 247 | 248 | return { 249 | 'define' : ViewModel.define, 250 | 'binding' : ViewModel.binding 251 | } 252 | }, function(expose) { 253 | //初始化模块 254 | Aaron.define = expose.define; 255 | Aaron.binding = expose.binding; 256 | }) -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/directive/on.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // 3 | // 事件绑定处理 4 | // 5 | //======================================== 6 | Aaron.register('directives-on', function() { 7 | 8 | return { 9 | 10 | bind: function() { 11 | 12 | }, 13 | 14 | undate: function() { 15 | 16 | }, 17 | 18 | unbind: function() { 19 | 20 | } 21 | } 22 | 23 | }); 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MVVM学习之旅 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

图片的尺寸 {{r w }} * {{ h }} {{ t }}

26 |
27 | 28 | 43 | 44 | -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/lib/aaRequire.js: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 支持AMD,CMD模块加载方式 3 | * @by Aaron 4 | * github:https://github.com/JsAaron/aaronRequire 5 | * blog:http://www.cnblogs.com/aaronjs/ 6 | *****************************************************************/ 7 | Aaron = {}; 8 | ;(function(r) { 9 | Aaron.use = r.require; 10 | Aaron.register = r.define; 11 | })(function() { 12 | 13 | var objproto = Object.prototype, 14 | objtoString = objproto.toString, 15 | arrproto = Array.prototype, 16 | nativeForEach = arrproto.forEach, 17 | modules = {}, 18 | pushStack = {}; 19 | 20 | function each(obj, callback, context) { 21 | if (obj == null) return; 22 | //如果支持本地forEach方法,并且是函数 23 | if (nativeForEach && obj.forEach === nativeForEach) { 24 | obj.forEach(callback, context); 25 | } else if (obj.length === +obj.length) { 26 | //for循环迭代 27 | for (var i = 0, l = obj.length; i < l; i++) { 28 | if (callback.call(context, obj[i], i, obj) === breaker) return; 29 | } 30 | } 31 | }; 32 | 33 | function isFunction(it) { 34 | return objtoString.call(it) === '[object Function]'; 35 | } 36 | 37 | function isArray(it) { 38 | return objtoString.call(it) === '[object Array]'; 39 | } 40 | 41 | //导入模块 42 | var exp = { 43 | require: function(id, callback) { 44 | //数组形式 45 | //require(['domReady', 'App'], function(domReady, app) {}); 46 | if (isArray(id)) { 47 | if (id.length > 1) { 48 | return makeRequire(id, callback); 49 | } 50 | id = id[0]; 51 | } 52 | 53 | if (!modules[id]) { 54 | throw "module " + id + " not found"; 55 | } 56 | 57 | if (callback) { 58 | var module = build(modules[id]); 59 | callback(module) 60 | return module; 61 | } else { 62 | if (modules[id].factory) { 63 | return build(modules[id]); 64 | } 65 | return modules[id].exports; 66 | } 67 | }, 68 | //定义模块 69 | define: function(id, deps, factory, post) { //模块名,依赖列表,模块本身 70 | if (modules[id]) { 71 | throw "module " + id + " 模块已存在!"; 72 | } 73 | //存在依赖导入 74 | if (arguments.length > 2) { 75 | modules[id] = { 76 | id: id, 77 | deps: deps, 78 | factory: factory 79 | }; 80 | //后加载 81 | post && exp.require(id, function(exp) { 82 | post(exp) 83 | }) 84 | } else { 85 | factory = deps; 86 | modules[id] = { 87 | id: id, 88 | factory: factory 89 | }; 90 | } 91 | } 92 | } 93 | 94 | //解析依赖关系 95 | function parseDeps(module) { 96 | var deps = module['deps'], 97 | temp = []; 98 | each(deps, function(id, index) { 99 | temp.push(build(modules[id])) 100 | }) 101 | return temp; 102 | } 103 | 104 | function build(module) { 105 | var depsList, existMod, 106 | factory = module['factory'], 107 | id = module['id']; 108 | 109 | if (existMod = pushStack[id]) { //去重复执行 110 | return existMod; 111 | } 112 | 113 | //接口点,将数据或方法定义在其上则将其暴露给外部调用。 114 | module.exports = {}; 115 | 116 | //去重 117 | delete module.factory; 118 | 119 | if (module['deps']) { 120 | //依赖数组列表 121 | depsList = parseDeps(module); 122 | module.exports = factory.apply(module, depsList); 123 | } else { 124 | // exports 支持直接 return 或 modulejs.exports 方式 125 | module.exports = factory(exp.require, module.exports, module) || module.exports; 126 | } 127 | 128 | pushStack[id] = module.exports; 129 | 130 | return module.exports; 131 | } 132 | 133 | //解析require模块 134 | function makeRequire(ids, callback) { 135 | var r = ids.length, 136 | shim = {}; 137 | each(ids, function(name) { 138 | shim[name] = build(modules[name]) 139 | }) 140 | if (callback) { 141 | callback.call(null, shim); 142 | } else { 143 | shim = null; 144 | } 145 | } 146 | 147 | return exp; 148 | }()); 149 | -------------------------------------------------------------------------------- /3.MVVM框架设计及实现/lib/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 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 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | //use the faster Date.now if available. 24 | var getTime = (Date.now || function() { 25 | return new Date().getTime(); 26 | }); 27 | 28 | // Create quick reference variables for speed access to core prototypes. 29 | var 30 | push = ArrayProto.push, 31 | slice = ArrayProto.slice, 32 | concat = ArrayProto.concat, 33 | toString = ObjProto.toString, 34 | hasOwnProperty = ObjProto.hasOwnProperty; 35 | 36 | // All **ECMAScript 5** native function implementations that we hope to use 37 | // are declared here. 38 | var 39 | nativeForEach = ArrayProto.forEach, 40 | nativeMap = ArrayProto.map, 41 | nativeReduce = ArrayProto.reduce, 42 | nativeReduceRight = ArrayProto.reduceRight, 43 | nativeFilter = ArrayProto.filter, 44 | nativeEvery = ArrayProto.every, 45 | nativeSome = ArrayProto.some, 46 | nativeIndexOf = ArrayProto.indexOf, 47 | nativeLastIndexOf = ArrayProto.lastIndexOf, 48 | nativeIsArray = Array.isArray, 49 | nativeKeys = Object.keys, 50 | nativeBind = FuncProto.bind; 51 | 52 | // Create a safe reference to the Underscore object for use below. 53 | var _ = function(obj) { 54 | if (obj instanceof _) return obj; 55 | if (!(this instanceof _)) return new _(obj); 56 | this._wrapped = obj; 57 | }; 58 | 59 | // Export the Underscore object for **Node.js**, with 60 | // backwards-compatibility for the old `require()` API. If we're in 61 | // the browser, add `_` as a global object via a string identifier, 62 | // for Closure Compiler "advanced" mode. 63 | if (typeof exports !== 'undefined') { 64 | if (typeof module !== 'undefined' && module.exports) { 65 | exports = module.exports = _; 66 | } 67 | exports._ = _; 68 | } else { 69 | root._ = _; 70 | } 71 | 72 | // Current version. 73 | _.VERSION = '1.5.2'; 74 | 75 | // Collection Functions 76 | // -------------------- 77 | 78 | // The cornerstone, an `each` implementation, aka `forEach`. 79 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 80 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 81 | var each = _.each = _.forEach = function(obj, iterator, context) { 82 | if (obj == null) return; 83 | if (nativeForEach && obj.forEach === nativeForEach) { 84 | obj.forEach(iterator, context); 85 | } else if (obj.length === +obj.length) { 86 | for (var i = 0, length = obj.length; i < length; i++) { 87 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 88 | } 89 | } else { 90 | var keys = _.keys(obj); 91 | for (var i = 0, length = keys.length; i < length; i++) { 92 | if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; 93 | } 94 | } 95 | }; 96 | 97 | // Return the results of applying the iterator to each element. 98 | // Delegates to **ECMAScript 5**'s native `map` if available. 99 | _.map = _.collect = function(obj, iterator, context) { 100 | var results = []; 101 | if (obj == null) return results; 102 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 103 | each(obj, function(value, index, list) { 104 | results.push(iterator.call(context, value, index, list)); 105 | }); 106 | return results; 107 | }; 108 | 109 | var reduceError = 'Reduce of empty array with no initial value'; 110 | 111 | // **Reduce** builds up a single result from a list of values, aka `inject`, 112 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 113 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 114 | var initial = arguments.length > 2; 115 | if (obj == null) obj = []; 116 | if (nativeReduce && obj.reduce === nativeReduce) { 117 | if (context) iterator = _.bind(iterator, context); 118 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 119 | } 120 | each(obj, function(value, index, list) { 121 | if (!initial) { 122 | memo = value; 123 | initial = true; 124 | } else { 125 | memo = iterator.call(context, memo, value, index, list); 126 | } 127 | }); 128 | if (!initial) throw new TypeError(reduceError); 129 | return memo; 130 | }; 131 | 132 | // The right-associative version of reduce, also known as `foldr`. 133 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 134 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 135 | var initial = arguments.length > 2; 136 | if (obj == null) obj = []; 137 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 138 | if (context) iterator = _.bind(iterator, context); 139 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 140 | } 141 | var length = obj.length; 142 | if (length !== +length) { 143 | var keys = _.keys(obj); 144 | length = keys.length; 145 | } 146 | each(obj, function(value, index, list) { 147 | index = keys ? keys[--length] : --length; 148 | if (!initial) { 149 | memo = obj[index]; 150 | initial = true; 151 | } else { 152 | memo = iterator.call(context, memo, obj[index], index, list); 153 | } 154 | }); 155 | if (!initial) throw new TypeError(reduceError); 156 | return memo; 157 | }; 158 | 159 | // Return the first value which passes a truth test. Aliased as `detect`. 160 | _.find = _.detect = function(obj, iterator, context) { 161 | var result; 162 | any(obj, function(value, index, list) { 163 | if (iterator.call(context, value, index, list)) { 164 | result = value; 165 | return true; 166 | } 167 | }); 168 | return result; 169 | }; 170 | 171 | // Return all the elements that pass a truth test. 172 | // Delegates to **ECMAScript 5**'s native `filter` if available. 173 | // Aliased as `select`. 174 | _.filter = _.select = function(obj, iterator, context) { 175 | var results = []; 176 | if (obj == null) return results; 177 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 178 | each(obj, function(value, index, list) { 179 | if (iterator.call(context, value, index, list)) results.push(value); 180 | }); 181 | return results; 182 | }; 183 | 184 | // Return all the elements for which a truth test fails. 185 | _.reject = function(obj, iterator, context) { 186 | return _.filter(obj, function(value, index, list) { 187 | return !iterator.call(context, value, index, list); 188 | }, context); 189 | }; 190 | 191 | // Determine whether all of the elements match a truth test. 192 | // Delegates to **ECMAScript 5**'s native `every` if available. 193 | // Aliased as `all`. 194 | _.every = _.all = function(obj, iterator, context) { 195 | iterator || (iterator = _.identity); 196 | var result = true; 197 | if (obj == null) return result; 198 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 199 | each(obj, function(value, index, list) { 200 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 201 | }); 202 | return !!result; 203 | }; 204 | 205 | // Determine if at least one element in the object matches a truth test. 206 | // Delegates to **ECMAScript 5**'s native `some` if available. 207 | // Aliased as `any`. 208 | var any = _.some = _.any = function(obj, iterator, context) { 209 | iterator || (iterator = _.identity); 210 | var result = false; 211 | if (obj == null) return result; 212 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 213 | each(obj, function(value, index, list) { 214 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 215 | }); 216 | return !!result; 217 | }; 218 | 219 | // Determine if the array or object contains a given value (using `===`). 220 | // Aliased as `include`. 221 | _.contains = _.include = function(obj, target) { 222 | if (obj == null) return false; 223 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 224 | return any(obj, function(value) { 225 | return value === target; 226 | }); 227 | }; 228 | 229 | // Invoke a method (with arguments) on every item in a collection. 230 | _.invoke = function(obj, method) { 231 | var args = slice.call(arguments, 2); 232 | var isFunc = _.isFunction(method); 233 | return _.map(obj, function(value) { 234 | return (isFunc ? method : value[method]).apply(value, args); 235 | }); 236 | }; 237 | 238 | // Convenience version of a common use case of `map`: fetching a property. 239 | _.pluck = function(obj, key) { 240 | return _.map(obj, function(value){ return value[key]; }); 241 | }; 242 | 243 | // Convenience version of a common use case of `filter`: selecting only objects 244 | // containing specific `key:value` pairs. 245 | _.where = function(obj, attrs, first) { 246 | if (_.isEmpty(attrs)) return first ? void 0 : []; 247 | return _[first ? 'find' : 'filter'](obj, function(value) { 248 | for (var key in attrs) { 249 | if (attrs[key] !== value[key]) return false; 250 | } 251 | return true; 252 | }); 253 | }; 254 | 255 | // Convenience version of a common use case of `find`: getting the first object 256 | // containing specific `key:value` pairs. 257 | _.findWhere = function(obj, attrs) { 258 | return _.where(obj, attrs, true); 259 | }; 260 | 261 | // Return the maximum element or (element-based computation). 262 | // Can't optimize arrays of integers longer than 65,535 elements. 263 | // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) 264 | _.max = function(obj, iterator, context) { 265 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 266 | return Math.max.apply(Math, obj); 267 | } 268 | if (!iterator && _.isEmpty(obj)) return -Infinity; 269 | var result = {computed : -Infinity, value: -Infinity}; 270 | each(obj, function(value, index, list) { 271 | var computed = iterator ? iterator.call(context, value, index, list) : value; 272 | computed > result.computed && (result = {value : value, computed : computed}); 273 | }); 274 | return result.value; 275 | }; 276 | 277 | // Return the minimum element (or element-based computation). 278 | _.min = function(obj, iterator, context) { 279 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 280 | return Math.min.apply(Math, obj); 281 | } 282 | if (!iterator && _.isEmpty(obj)) return Infinity; 283 | var result = {computed : Infinity, value: Infinity}; 284 | each(obj, function(value, index, list) { 285 | var computed = iterator ? iterator.call(context, value, index, list) : value; 286 | computed < result.computed && (result = {value : value, computed : computed}); 287 | }); 288 | return result.value; 289 | }; 290 | 291 | // Shuffle an array, using the modern version of the 292 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 293 | _.shuffle = function(obj) { 294 | var rand; 295 | var index = 0; 296 | var shuffled = []; 297 | each(obj, function(value) { 298 | rand = _.random(index++); 299 | shuffled[index - 1] = shuffled[rand]; 300 | shuffled[rand] = value; 301 | }); 302 | return shuffled; 303 | }; 304 | 305 | // Sample **n** random values from a collection. 306 | // If **n** is not specified, returns a single random element. 307 | // The internal `guard` argument allows it to work with `map`. 308 | _.sample = function(obj, n, guard) { 309 | if (n == null || guard) { 310 | if (obj.length !== +obj.length) obj = _.values(obj); 311 | return obj[_.random(obj.length - 1)]; 312 | } 313 | return _.shuffle(obj).slice(0, Math.max(0, n)); 314 | }; 315 | 316 | // An internal function to generate lookup iterators. 317 | var lookupIterator = function(value) { 318 | return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 319 | }; 320 | 321 | // Sort the object's values by a criterion produced by an iterator. 322 | _.sortBy = function(obj, value, context) { 323 | var iterator = value == null ? _.identity : lookupIterator(value); 324 | return _.pluck(_.map(obj, function(value, index, list) { 325 | return { 326 | value: value, 327 | index: index, 328 | criteria: iterator.call(context, value, index, list) 329 | }; 330 | }).sort(function(left, right) { 331 | var a = left.criteria; 332 | var b = right.criteria; 333 | if (a !== b) { 334 | if (a > b || a === void 0) return 1; 335 | if (a < b || b === void 0) return -1; 336 | } 337 | return left.index - right.index; 338 | }), 'value'); 339 | }; 340 | 341 | // An internal function used for aggregate "group by" operations. 342 | var group = function(behavior) { 343 | return function(obj, value, context) { 344 | var result = {}; 345 | var iterator = value == null ? _.identity : lookupIterator(value); 346 | each(obj, function(value, index) { 347 | var key = iterator.call(context, value, index, obj); 348 | behavior(result, key, value); 349 | }); 350 | return result; 351 | }; 352 | }; 353 | 354 | // Groups the object's values by a criterion. Pass either a string attribute 355 | // to group by, or a function that returns the criterion. 356 | _.groupBy = group(function(result, key, value) { 357 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 358 | }); 359 | 360 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 361 | // when you know that your index values will be unique. 362 | _.indexBy = group(function(result, key, value) { 363 | result[key] = value; 364 | }); 365 | 366 | // Counts instances of an object that group by a certain criterion. Pass 367 | // either a string attribute to count by, or a function that returns the 368 | // criterion. 369 | _.countBy = group(function(result, key) { 370 | _.has(result, key) ? result[key]++ : result[key] = 1; 371 | }); 372 | 373 | // Use a comparator function to figure out the smallest index at which 374 | // an object should be inserted so as to maintain order. Uses binary search. 375 | _.sortedIndex = function(array, obj, iterator, context) { 376 | iterator = iterator == null ? _.identity : lookupIterator(iterator); 377 | var value = iterator.call(context, obj); 378 | var low = 0, high = array.length; 379 | while (low < high) { 380 | var mid = (low + high) >>> 1; 381 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 382 | } 383 | return low; 384 | }; 385 | 386 | // Safely create a real, live array from anything iterable. 387 | _.toArray = function(obj) { 388 | if (!obj) return []; 389 | if (_.isArray(obj)) return slice.call(obj); 390 | if (obj.length === +obj.length) return _.map(obj, _.identity); 391 | return _.values(obj); 392 | }; 393 | 394 | // Return the number of elements in an object. 395 | _.size = function(obj) { 396 | if (obj == null) return 0; 397 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 398 | }; 399 | 400 | // Array Functions 401 | // --------------- 402 | 403 | // Get the first element of an array. Passing **n** will return the first N 404 | // values in the array. Aliased as `head` and `take`. The **guard** check 405 | // allows it to work with `_.map`. 406 | _.first = _.head = _.take = function(array, n, guard) { 407 | if (array == null) return void 0; 408 | return (n == null) || guard ? array[0] : slice.call(array, 0, n); 409 | }; 410 | 411 | // Returns everything but the last entry of the array. Especially useful on 412 | // the arguments object. Passing **n** will return all the values in 413 | // the array, excluding the last N. The **guard** check allows it to work with 414 | // `_.map`. 415 | _.initial = function(array, n, guard) { 416 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 417 | }; 418 | 419 | // Get the last element of an array. Passing **n** will return the last N 420 | // values in the array. The **guard** check allows it to work with `_.map`. 421 | _.last = function(array, n, guard) { 422 | if (array == null) return void 0; 423 | if ((n == null) || guard) { 424 | return array[array.length - 1]; 425 | } else { 426 | return slice.call(array, Math.max(array.length - n, 0)); 427 | } 428 | }; 429 | 430 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 431 | // Especially useful on the arguments object. Passing an **n** will return 432 | // the rest N values in the array. The **guard** 433 | // check allows it to work with `_.map`. 434 | _.rest = _.tail = _.drop = function(array, n, guard) { 435 | return slice.call(array, (n == null) || guard ? 1 : n); 436 | }; 437 | 438 | // Trim out all falsy values from an array. 439 | _.compact = function(array) { 440 | return _.filter(array, _.identity); 441 | }; 442 | 443 | // Internal implementation of a recursive `flatten` function. 444 | var flatten = function(input, shallow, output) { 445 | if (shallow && _.every(input, _.isArray)) { 446 | return concat.apply(output, input); 447 | } 448 | each(input, function(value) { 449 | if (_.isArray(value) || _.isArguments(value)) { 450 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 451 | } else { 452 | output.push(value); 453 | } 454 | }); 455 | return output; 456 | }; 457 | 458 | // Flatten out an array, either recursively (by default), or just one level. 459 | _.flatten = function(array, shallow) { 460 | return flatten(array, shallow, []); 461 | }; 462 | 463 | // Return a version of the array that does not contain the specified value(s). 464 | _.without = function(array) { 465 | return _.difference(array, slice.call(arguments, 1)); 466 | }; 467 | 468 | // Produce a duplicate-free version of the array. If the array has already 469 | // been sorted, you have the option of using a faster algorithm. 470 | // Aliased as `unique`. 471 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 472 | if (_.isFunction(isSorted)) { 473 | context = iterator; 474 | iterator = isSorted; 475 | isSorted = false; 476 | } 477 | var initial = iterator ? _.map(array, iterator, context) : array; 478 | var results = []; 479 | var seen = []; 480 | each(initial, function(value, index) { 481 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 482 | seen.push(value); 483 | results.push(array[index]); 484 | } 485 | }); 486 | return results; 487 | }; 488 | 489 | // Produce an array that contains the union: each distinct element from all of 490 | // the passed-in arrays. 491 | _.union = function() { 492 | return _.uniq(_.flatten(arguments, true)); 493 | }; 494 | 495 | // Produce an array that contains every item shared between all the 496 | // passed-in arrays. 497 | _.intersection = function(array) { 498 | var rest = slice.call(arguments, 1); 499 | return _.filter(_.uniq(array), function(item) { 500 | return _.every(rest, function(other) { 501 | return _.indexOf(other, item) >= 0; 502 | }); 503 | }); 504 | }; 505 | 506 | // Take the difference between one array and a number of other arrays. 507 | // Only the elements present in just the first array will remain. 508 | _.difference = function(array) { 509 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 510 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 511 | }; 512 | 513 | // Zip together multiple lists into a single array -- elements that share 514 | // an index go together. 515 | _.zip = function() { 516 | var length = _.max(_.pluck(arguments, "length").concat(0)); 517 | var results = new Array(length); 518 | for (var i = 0; i < length; i++) { 519 | results[i] = _.pluck(arguments, '' + i); 520 | } 521 | return results; 522 | }; 523 | 524 | // Converts lists into objects. Pass either a single array of `[key, value]` 525 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 526 | // the corresponding values. 527 | _.object = function(list, values) { 528 | if (list == null) return {}; 529 | var result = {}; 530 | for (var i = 0, length = list.length; i < length; i++) { 531 | if (values) { 532 | result[list[i]] = values[i]; 533 | } else { 534 | result[list[i][0]] = list[i][1]; 535 | } 536 | } 537 | return result; 538 | }; 539 | 540 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 541 | // we need this function. Return the position of the first occurrence of an 542 | // item in an array, or -1 if the item is not included in the array. 543 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 544 | // If the array is large and already in sort order, pass `true` 545 | // for **isSorted** to use binary search. 546 | _.indexOf = function(array, item, isSorted) { 547 | if (array == null) return -1; 548 | var i = 0, length = array.length; 549 | if (isSorted) { 550 | if (typeof isSorted == 'number') { 551 | i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); 552 | } else { 553 | i = _.sortedIndex(array, item); 554 | return array[i] === item ? i : -1; 555 | } 556 | } 557 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 558 | for (; i < length; i++) if (array[i] === item) return i; 559 | return -1; 560 | }; 561 | 562 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 563 | _.lastIndexOf = function(array, item, from) { 564 | if (array == null) return -1; 565 | var hasIndex = from != null; 566 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 567 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 568 | } 569 | var i = (hasIndex ? from : array.length); 570 | while (i--) if (array[i] === item) return i; 571 | return -1; 572 | }; 573 | 574 | // Generate an integer Array containing an arithmetic progression. A port of 575 | // the native Python `range()` function. See 576 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 577 | _.range = function(start, stop, step) { 578 | if (arguments.length <= 1) { 579 | stop = start || 0; 580 | start = 0; 581 | } 582 | step = arguments[2] || 1; 583 | 584 | var length = Math.max(Math.ceil((stop - start) / step), 0); 585 | var idx = 0; 586 | var range = new Array(length); 587 | 588 | while(idx < length) { 589 | range[idx++] = start; 590 | start += step; 591 | } 592 | 593 | return range; 594 | }; 595 | 596 | // Function (ahem) Functions 597 | // ------------------ 598 | 599 | // Reusable constructor function for prototype setting. 600 | var ctor = function(){}; 601 | 602 | // Create a function bound to a given object (assigning `this`, and arguments, 603 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 604 | // available. 605 | _.bind = function(func, context) { 606 | var args, bound; 607 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 608 | if (!_.isFunction(func)) throw new TypeError; 609 | args = slice.call(arguments, 2); 610 | return bound = function() { 611 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 612 | ctor.prototype = func.prototype; 613 | var self = new ctor; 614 | ctor.prototype = null; 615 | var result = func.apply(self, args.concat(slice.call(arguments))); 616 | if (Object(result) === result) return result; 617 | return self; 618 | }; 619 | }; 620 | 621 | // Partially apply a function by creating a version that has had some of its 622 | // arguments pre-filled, without changing its dynamic `this` context. 623 | _.partial = function(func) { 624 | var args = slice.call(arguments, 1); 625 | return function() { 626 | return func.apply(this, args.concat(slice.call(arguments))); 627 | }; 628 | }; 629 | 630 | // Bind all of an object's methods to that object. Useful for ensuring that 631 | // all callbacks defined on an object belong to it. 632 | _.bindAll = function(obj) { 633 | var funcs = slice.call(arguments, 1); 634 | if (funcs.length === 0) throw new Error("bindAll must be passed function names"); 635 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 636 | return obj; 637 | }; 638 | 639 | // Memoize an expensive function by storing its results. 640 | _.memoize = function(func, hasher) { 641 | var memo = {}; 642 | hasher || (hasher = _.identity); 643 | return function() { 644 | var key = hasher.apply(this, arguments); 645 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 646 | }; 647 | }; 648 | 649 | // Delays a function for the given number of milliseconds, and then calls 650 | // it with the arguments supplied. 651 | _.delay = function(func, wait) { 652 | var args = slice.call(arguments, 2); 653 | return setTimeout(function(){ return func.apply(null, args); }, wait); 654 | }; 655 | 656 | // Defers a function, scheduling it to run after the current call stack has 657 | // cleared. 658 | _.defer = function(func) { 659 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 660 | }; 661 | 662 | // Returns a function, that, when invoked, will only be triggered at most once 663 | // during a given window of time. Normally, the throttled function will run 664 | // as much as it can, without ever going more than once per `wait` duration; 665 | // but if you'd like to disable the execution on the leading edge, pass 666 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 667 | _.throttle = function(func, wait, options) { 668 | var context, args, result; 669 | var timeout = null; 670 | var previous = 0; 671 | options || (options = {}); 672 | var later = function() { 673 | previous = options.leading === false ? 0 : getTime(); 674 | timeout = null; 675 | result = func.apply(context, args); 676 | context = args = null; 677 | }; 678 | return function() { 679 | var now = getTime(); 680 | if (!previous && options.leading === false) previous = now; 681 | var remaining = wait - (now - previous); 682 | context = this; 683 | args = arguments; 684 | if (remaining <= 0) { 685 | clearTimeout(timeout); 686 | timeout = null; 687 | previous = now; 688 | result = func.apply(context, args); 689 | context = args = null; 690 | } else if (!timeout && options.trailing !== false) { 691 | timeout = setTimeout(later, remaining); 692 | } 693 | return result; 694 | }; 695 | }; 696 | 697 | // Returns a function, that, as long as it continues to be invoked, will not 698 | // be triggered. The function will be called after it stops being called for 699 | // N milliseconds. If `immediate` is passed, trigger the function on the 700 | // leading edge, instead of the trailing. 701 | _.debounce = function(func, wait, immediate) { 702 | var timeout, args, context, timestamp, result; 703 | return function() { 704 | context = this; 705 | args = arguments; 706 | timestamp = getTime(); 707 | var later = function() { 708 | var last = getTime() - timestamp; 709 | if (last < wait) { 710 | timeout = setTimeout(later, wait - last); 711 | } else { 712 | timeout = null; 713 | if (!immediate) { 714 | result = func.apply(context, args); 715 | context = args = null; 716 | } 717 | } 718 | }; 719 | var callNow = immediate && !timeout; 720 | if (!timeout) { 721 | timeout = setTimeout(later, wait); 722 | } 723 | if (callNow) { 724 | result = func.apply(context, args); 725 | context = args = null; 726 | } 727 | 728 | return result; 729 | }; 730 | }; 731 | 732 | // Returns a function that will be executed at most one time, no matter how 733 | // often you call it. Useful for lazy initialization. 734 | _.once = function(func) { 735 | var ran = false, memo; 736 | return function() { 737 | if (ran) return memo; 738 | ran = true; 739 | memo = func.apply(this, arguments); 740 | func = null; 741 | return memo; 742 | }; 743 | }; 744 | 745 | // Returns the first function passed as an argument to the second, 746 | // allowing you to adjust arguments, run code before and after, and 747 | // conditionally execute the original function. 748 | _.wrap = function(func, wrapper) { 749 | return _.partial(wrapper, func); 750 | }; 751 | 752 | // Returns a function that is the composition of a list of functions, each 753 | // consuming the return value of the function that follows. 754 | _.compose = function() { 755 | var funcs = arguments; 756 | return function() { 757 | var args = arguments; 758 | for (var i = funcs.length - 1; i >= 0; i--) { 759 | args = [funcs[i].apply(this, args)]; 760 | } 761 | return args[0]; 762 | }; 763 | }; 764 | 765 | // Returns a function that will only be executed after being called N times. 766 | _.after = function(times, func) { 767 | return function() { 768 | if (--times < 1) { 769 | return func.apply(this, arguments); 770 | } 771 | }; 772 | }; 773 | 774 | // Object Functions 775 | // ---------------- 776 | 777 | // Retrieve the names of an object's properties. 778 | // Delegates to **ECMAScript 5**'s native `Object.keys` 779 | _.keys = nativeKeys || function(obj) { 780 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 781 | var keys = []; 782 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 783 | return keys; 784 | }; 785 | 786 | // Retrieve the values of an object's properties. 787 | _.values = function(obj) { 788 | var keys = _.keys(obj); 789 | var length = keys.length; 790 | var values = new Array(length); 791 | for (var i = 0; i < length; i++) { 792 | values[i] = obj[keys[i]]; 793 | } 794 | return values; 795 | }; 796 | 797 | // Convert an object into a list of `[key, value]` pairs. 798 | _.pairs = function(obj) { 799 | var keys = _.keys(obj); 800 | var length = keys.length; 801 | var pairs = new Array(length); 802 | for (var i = 0; i < length; i++) { 803 | pairs[i] = [keys[i], obj[keys[i]]]; 804 | } 805 | return pairs; 806 | }; 807 | 808 | // Invert the keys and values of an object. The values must be serializable. 809 | _.invert = function(obj) { 810 | var result = {}; 811 | var keys = _.keys(obj); 812 | for (var i = 0, length = keys.length; i < length; i++) { 813 | result[obj[keys[i]]] = keys[i]; 814 | } 815 | return result; 816 | }; 817 | 818 | // Return a sorted list of the function names available on the object. 819 | // Aliased as `methods` 820 | _.functions = _.methods = function(obj) { 821 | var names = []; 822 | for (var key in obj) { 823 | if (_.isFunction(obj[key])) names.push(key); 824 | } 825 | return names.sort(); 826 | }; 827 | 828 | // Extend a given object with all the properties in passed-in object(s). 829 | _.extend = function(obj) { 830 | each(slice.call(arguments, 1), function(source) { 831 | if (source) { 832 | for (var prop in source) { 833 | obj[prop] = source[prop]; 834 | } 835 | } 836 | }); 837 | return obj; 838 | }; 839 | 840 | // Return a copy of the object only containing the whitelisted properties. 841 | _.pick = function(obj) { 842 | var copy = {}; 843 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 844 | each(keys, function(key) { 845 | if (key in obj) copy[key] = obj[key]; 846 | }); 847 | return copy; 848 | }; 849 | 850 | // Return a copy of the object without the blacklisted properties. 851 | _.omit = function(obj) { 852 | var copy = {}; 853 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 854 | for (var key in obj) { 855 | if (!_.contains(keys, key)) copy[key] = obj[key]; 856 | } 857 | return copy; 858 | }; 859 | 860 | // Fill in a given object with default properties. 861 | _.defaults = function(obj) { 862 | each(slice.call(arguments, 1), function(source) { 863 | if (source) { 864 | for (var prop in source) { 865 | if (obj[prop] === void 0) obj[prop] = source[prop]; 866 | } 867 | } 868 | }); 869 | return obj; 870 | }; 871 | 872 | // Create a (shallow-cloned) duplicate of an object. 873 | _.clone = function(obj) { 874 | if (!_.isObject(obj)) return obj; 875 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 876 | }; 877 | 878 | // Invokes interceptor with the obj, and then returns obj. 879 | // The primary purpose of this method is to "tap into" a method chain, in 880 | // order to perform operations on intermediate results within the chain. 881 | _.tap = function(obj, interceptor) { 882 | interceptor(obj); 883 | return obj; 884 | }; 885 | 886 | // Internal recursive comparison function for `isEqual`. 887 | var eq = function(a, b, aStack, bStack) { 888 | // Identical objects are equal. `0 === -0`, but they aren't identical. 889 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 890 | if (a === b) return a !== 0 || 1 / a == 1 / b; 891 | // A strict comparison is necessary because `null == undefined`. 892 | if (a == null || b == null) return a === b; 893 | // Unwrap any wrapped objects. 894 | if (a instanceof _) a = a._wrapped; 895 | if (b instanceof _) b = b._wrapped; 896 | // Compare `[[Class]]` names. 897 | var className = toString.call(a); 898 | if (className != toString.call(b)) return false; 899 | switch (className) { 900 | // Strings, numbers, dates, and booleans are compared by value. 901 | case '[object String]': 902 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 903 | // equivalent to `new String("5")`. 904 | return a == String(b); 905 | case '[object Number]': 906 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 907 | // other numeric values. 908 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 909 | case '[object Date]': 910 | case '[object Boolean]': 911 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 912 | // millisecond representations. Note that invalid dates with millisecond representations 913 | // of `NaN` are not equivalent. 914 | return +a == +b; 915 | // RegExps are compared by their source patterns and flags. 916 | case '[object RegExp]': 917 | return a.source == b.source && 918 | a.global == b.global && 919 | a.multiline == b.multiline && 920 | a.ignoreCase == b.ignoreCase; 921 | } 922 | if (typeof a != 'object' || typeof b != 'object') return false; 923 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 924 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 925 | var length = aStack.length; 926 | while (length--) { 927 | // Linear search. Performance is inversely proportional to the number of 928 | // unique nested structures. 929 | if (aStack[length] == a) return bStack[length] == b; 930 | } 931 | // Objects with different constructors are not equivalent, but `Object`s 932 | // from different frames are. 933 | var aCtor = a.constructor, bCtor = b.constructor; 934 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 935 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 936 | return false; 937 | } 938 | // Add the first object to the stack of traversed objects. 939 | aStack.push(a); 940 | bStack.push(b); 941 | var size = 0, result = true; 942 | // Recursively compare objects and arrays. 943 | if (className == '[object Array]') { 944 | // Compare array lengths to determine if a deep comparison is necessary. 945 | size = a.length; 946 | result = size == b.length; 947 | if (result) { 948 | // Deep compare the contents, ignoring non-numeric properties. 949 | while (size--) { 950 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 951 | } 952 | } 953 | } else { 954 | // Deep compare objects. 955 | for (var key in a) { 956 | if (_.has(a, key)) { 957 | // Count the expected number of properties. 958 | size++; 959 | // Deep compare each member. 960 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 961 | } 962 | } 963 | // Ensure that both objects contain the same number of properties. 964 | if (result) { 965 | for (key in b) { 966 | if (_.has(b, key) && !(size--)) break; 967 | } 968 | result = !size; 969 | } 970 | } 971 | // Remove the first object from the stack of traversed objects. 972 | aStack.pop(); 973 | bStack.pop(); 974 | return result; 975 | }; 976 | 977 | // Perform a deep comparison to check if two objects are equal. 978 | _.isEqual = function(a, b) { 979 | return eq(a, b, [], []); 980 | }; 981 | 982 | // Is a given array, string, or object empty? 983 | // An "empty" object has no enumerable own-properties. 984 | _.isEmpty = function(obj) { 985 | if (obj == null) return true; 986 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 987 | for (var key in obj) if (_.has(obj, key)) return false; 988 | return true; 989 | }; 990 | 991 | // Is a given value a DOM element? 992 | _.isElement = function(obj) { 993 | return !!(obj && obj.nodeType === 1); 994 | }; 995 | 996 | // Is a given value an array? 997 | // Delegates to ECMA5's native Array.isArray 998 | _.isArray = nativeIsArray || function(obj) { 999 | return toString.call(obj) == '[object Array]'; 1000 | }; 1001 | 1002 | // Is a given variable an object? 1003 | _.isObject = function(obj) { 1004 | return obj === Object(obj); 1005 | }; 1006 | 1007 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 1008 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 1009 | _['is' + name] = function(obj) { 1010 | return toString.call(obj) == '[object ' + name + ']'; 1011 | }; 1012 | }); 1013 | 1014 | // Define a fallback version of the method in browsers (ahem, IE), where 1015 | // there isn't any inspectable "Arguments" type. 1016 | if (!_.isArguments(arguments)) { 1017 | _.isArguments = function(obj) { 1018 | return !!(obj && _.has(obj, 'callee')); 1019 | }; 1020 | } 1021 | 1022 | // Optimize `isFunction` if appropriate. 1023 | if (typeof (/./) !== 'function') { 1024 | _.isFunction = function(obj) { 1025 | return typeof obj === 'function'; 1026 | }; 1027 | } 1028 | 1029 | // Is a given object a finite number? 1030 | _.isFinite = function(obj) { 1031 | return isFinite(obj) && !isNaN(parseFloat(obj)); 1032 | }; 1033 | 1034 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 1035 | _.isNaN = function(obj) { 1036 | return _.isNumber(obj) && obj != +obj; 1037 | }; 1038 | 1039 | // Is a given value a boolean? 1040 | _.isBoolean = function(obj) { 1041 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 1042 | }; 1043 | 1044 | // Is a given value equal to null? 1045 | _.isNull = function(obj) { 1046 | return obj === null; 1047 | }; 1048 | 1049 | // Is a given variable undefined? 1050 | _.isUndefined = function(obj) { 1051 | return obj === void 0; 1052 | }; 1053 | 1054 | // Shortcut function for checking if an object has a given property directly 1055 | // on itself (in other words, not on a prototype). 1056 | _.has = function(obj, key) { 1057 | return hasOwnProperty.call(obj, key); 1058 | }; 1059 | 1060 | // Utility Functions 1061 | // ----------------- 1062 | 1063 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1064 | // previous owner. Returns a reference to the Underscore object. 1065 | _.noConflict = function() { 1066 | root._ = previousUnderscore; 1067 | return this; 1068 | }; 1069 | 1070 | // Keep the identity function around for default iterators. 1071 | _.identity = function(value) { 1072 | return value; 1073 | }; 1074 | 1075 | // Run a function **n** times. 1076 | _.times = function(n, iterator, context) { 1077 | var accum = Array(Math.max(0, n)); 1078 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); 1079 | return accum; 1080 | }; 1081 | 1082 | // Return a random integer between min and max (inclusive). 1083 | _.random = function(min, max) { 1084 | if (max == null) { 1085 | max = min; 1086 | min = 0; 1087 | } 1088 | return min + Math.floor(Math.random() * (max - min + 1)); 1089 | }; 1090 | 1091 | // List of HTML entities for escaping. 1092 | var entityMap = { 1093 | escape: { 1094 | '&': '&', 1095 | '<': '<', 1096 | '>': '>', 1097 | '"': '"', 1098 | "'": ''' 1099 | } 1100 | }; 1101 | entityMap.unescape = _.invert(entityMap.escape); 1102 | 1103 | // Regexes containing the keys and values listed immediately above. 1104 | var entityRegexes = { 1105 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1106 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1107 | }; 1108 | 1109 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1110 | _.each(['escape', 'unescape'], function(method) { 1111 | _[method] = function(string) { 1112 | if (string == null) return ''; 1113 | return ('' + string).replace(entityRegexes[method], function(match) { 1114 | return entityMap[method][match]; 1115 | }); 1116 | }; 1117 | }); 1118 | 1119 | // If the value of the named `property` is a function then invoke it with the 1120 | // `object` as context; otherwise, return it. 1121 | _.result = function(object, property) { 1122 | if (object == null) return void 0; 1123 | var value = object[property]; 1124 | return _.isFunction(value) ? value.call(object) : value; 1125 | }; 1126 | 1127 | // Add your own custom functions to the Underscore object. 1128 | _.mixin = function(obj) { 1129 | each(_.functions(obj), function(name) { 1130 | var func = _[name] = obj[name]; 1131 | _.prototype[name] = function() { 1132 | var args = [this._wrapped]; 1133 | push.apply(args, arguments); 1134 | return result.call(this, func.apply(_, args)); 1135 | }; 1136 | }); 1137 | }; 1138 | 1139 | // Generate a unique integer id (unique within the entire client session). 1140 | // Useful for temporary DOM ids. 1141 | var idCounter = 0; 1142 | _.uniqueId = function(prefix) { 1143 | var id = ++idCounter + ''; 1144 | return prefix ? prefix + id : id; 1145 | }; 1146 | 1147 | // By default, Underscore uses ERB-style template delimiters, change the 1148 | // following template settings to use alternative delimiters. 1149 | _.templateSettings = { 1150 | evaluate : /<%([\s\S]+?)%>/g, 1151 | interpolate : /<%=([\s\S]+?)%>/g, 1152 | escape : /<%-([\s\S]+?)%>/g 1153 | }; 1154 | 1155 | // When customizing `templateSettings`, if you don't want to define an 1156 | // interpolation, evaluation or escaping regex, we need one that is 1157 | // guaranteed not to match. 1158 | var noMatch = /(.)^/; 1159 | 1160 | // Certain characters need to be escaped so that they can be put into a 1161 | // string literal. 1162 | var escapes = { 1163 | "'": "'", 1164 | '\\': '\\', 1165 | '\r': 'r', 1166 | '\n': 'n', 1167 | '\t': 't', 1168 | '\u2028': 'u2028', 1169 | '\u2029': 'u2029' 1170 | }; 1171 | 1172 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1173 | 1174 | // JavaScript micro-templating, similar to John Resig's implementation. 1175 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1176 | // and correctly escapes quotes within interpolated code. 1177 | _.template = function(text, data, settings) { 1178 | var render; 1179 | settings = _.defaults({}, settings, _.templateSettings); 1180 | 1181 | // Combine delimiters into one regular expression via alternation. 1182 | var matcher = new RegExp([ 1183 | (settings.escape || noMatch).source, 1184 | (settings.interpolate || noMatch).source, 1185 | (settings.evaluate || noMatch).source 1186 | ].join('|') + '|$', 'g'); 1187 | 1188 | // Compile the template source, escaping string literals appropriately. 1189 | var index = 0; 1190 | var source = "__p+='"; 1191 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1192 | source += text.slice(index, offset) 1193 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1194 | 1195 | if (escape) { 1196 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1197 | } 1198 | if (interpolate) { 1199 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1200 | } 1201 | if (evaluate) { 1202 | source += "';\n" + evaluate + "\n__p+='"; 1203 | } 1204 | index = offset + match.length; 1205 | return match; 1206 | }); 1207 | source += "';\n"; 1208 | 1209 | // If a variable is not specified, place data values in local scope. 1210 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1211 | 1212 | source = "var __t,__p='',__j=Array.prototype.join," + 1213 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1214 | source + "return __p;\n"; 1215 | 1216 | try { 1217 | render = new Function(settings.variable || 'obj', '_', source); 1218 | } catch (e) { 1219 | e.source = source; 1220 | throw e; 1221 | } 1222 | 1223 | if (data) return render(data, _); 1224 | var template = function(data) { 1225 | return render.call(this, data, _); 1226 | }; 1227 | 1228 | // Provide the compiled function source as a convenience for precompilation. 1229 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1230 | 1231 | return template; 1232 | }; 1233 | 1234 | // Add a "chain" function, which will delegate to the wrapper. 1235 | _.chain = function(obj) { 1236 | return _(obj).chain(); 1237 | }; 1238 | 1239 | // OOP 1240 | // --------------- 1241 | // If Underscore is called as a function, it returns a wrapped object that 1242 | // can be used OO-style. This wrapper holds altered versions of all the 1243 | // underscore functions. Wrapped objects may be chained. 1244 | 1245 | // Helper function to continue chaining intermediate results. 1246 | var result = function(obj) { 1247 | return this._chain ? _(obj).chain() : obj; 1248 | }; 1249 | 1250 | // Add all of the Underscore functions to the wrapper object. 1251 | _.mixin(_); 1252 | 1253 | // Add all mutator Array functions to the wrapper. 1254 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1255 | var method = ArrayProto[name]; 1256 | _.prototype[name] = function() { 1257 | var obj = this._wrapped; 1258 | method.apply(obj, arguments); 1259 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1260 | return result.call(this, obj); 1261 | }; 1262 | }); 1263 | 1264 | // Add all accessor Array functions to the wrapper. 1265 | each(['concat', 'join', 'slice'], function(name) { 1266 | var method = ArrayProto[name]; 1267 | _.prototype[name] = function() { 1268 | return result.call(this, method.apply(this._wrapped, arguments)); 1269 | }; 1270 | }); 1271 | 1272 | _.extend(_.prototype, { 1273 | 1274 | // Start chaining a wrapped Underscore object. 1275 | chain: function() { 1276 | this._chain = true; 1277 | return this; 1278 | }, 1279 | 1280 | // Extracts the result from a wrapped and chained object. 1281 | value: function() { 1282 | return this._wrapped; 1283 | } 1284 | 1285 | }); 1286 | 1287 | }).call(this); 1288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 前端MVVM框架设计及实现 2 | ====================== 3 | 4 | 11月结束jQuery教程,启动MVVM框架 5 | 6 | 7 | 分析流程
8 | 9 | MVVM框架设计及实现(一): 实现最基本监控属于及双向绑定
10 | 11 | MVVM框架设计及实现(二): 指令
12 | 13 | MVVM框架设计及实现(三): 重构代码,加入AMD组织结构
14 | 15 | 16 | 17 | 分析博客
18 | 19 | http://www.cnblogs.com/Aaronjs/
20 | --------------------------------------------------------------------------------