├── 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 |
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 |
--------------------------------------------------------------------------------