define("mvvm", "$event,$css,$attr".split(","), function($) {
var prefix = "ms-";
var avalon = $.avalon = {
models: {},
filters: {
uppercase: function(str) {
return str.toUpperCase()
},
lowercase: function(str) {
return str.toLowerCase();
},
number: function(str) {
return isFinite(str) ? str : "";
},
aaa: function(str) {
return str + "AAA"
}
}
};
var blank = " ";
var obsevers = {};
var Publish = {};//将函数放到发布对象上,让依赖它的函数
var expando = new Date - 0;
var subscribers = "$" + expando;
/*********************************************************************
* View *
**********************************************************************/
var regOpenTag = /([^{]*)\{\{/;
var regCloseTag = /([^}]*)\}\}/;
function hasExpr(value) {
var index = value.indexOf("{{");
return index !== -1 && index < value.indexOf("}}");
}
function forEach(obj, fn) {
if (obj) {//不能传个null, undefined进来
var isArray = isFinite(obj.length), i = 0
if (isArray) {
for (var n = obj.length; i < n; i++) {
fn(i, obj[i]);
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i)) {
fn(i, obj[i]);
}
}
}
}
}
//eval一个或多个表达式
function watchView(text, scope, scopes, data, callback, tokens) {
var updateView, target, filters = data.filters;
var scopeList = [scope].concat(scopes);
if (!filters) {
for (var i = 0, obj; obj = scopeList[i++]; ) {
if (obj.hasOwnProperty(text)) {
target = obj;//如果能在作用域上直接找到,我们就不需要eval了
break;
}
}
}
if (target) {
updateView = function() {
callback(target[text]);
};
} else {
updateView = function() {
if (tokens) {
var val = tokens.map(function(obj) {
return obj.expr ? evalExpr(obj.value, scopeList, data) : obj.value;
}).join("");
} else {
val = evalExpr(text, scopeList, data);
}
callback(val);
};
}
Publish[ expando ] = updateView;
updateView();
delete Publish[ expando ];
}
function evalExpr(text, scopeList, data) {
console.log(text)
var uniq = {
$occoecatio: 1
}, names = [], args = [];
scopeList.forEach(function(scope) {
scope.$occoecatio = true;
forEach(scope, function(key, val) {
if (!uniq[key]) {
names.push(key);
args.push(val);
uniq[key] = 1;
}
});
delete scope.$occoecatio;
});
if (data.compileFn) {
console.log(data.compileFn+"")
args.push(avalon.filters)
return data.compileFn.apply(data.compileFn, args);
}
if (data.filters) {
var random = new Date - 0, textBuffer = [], fargs;
textBuffer.push("var ret", random, "=", text, "\r\n");
for (var i = 0, f; f = data.filters[i++]; ) {
var start = f.indexOf("(");
if (start !== -1) {
fargs = f.slice(start + 1, f.lastIndexOf(")")).trim();
fargs = "," + fargs;
f = f.slice(0, start).trim();
} else {
fargs = "";
}
textBuffer.push(" if(filters", random, ".", f, "){\r\n\ttry{ret", random,
" = filters", random, ".", f, "(ret", random, fargs, ")}catch(e){};\r\n}\r\n");
}
textBuffer.push("\treturn ret", random);
text = textBuffer.join("");
names.push("filters" + random);
args.push(avalon.filters);
delete data.filters;//释放内存
} else {
text = "\treturn " + text;
}
try {
var fn = Function.apply(Function, names.concat(text));
var val = fn.apply(fn, args);
data.compileFn = fn;//缓存,防止二次编译
} catch (e) {
data.compileFn = function() {
return "";
};
val = "";
}
uniq = textBuffer = names = null;//释放内存
return val;
}
var bindingHandlers = avalon.bindingHandlers = {
//将模型中的字段与input, textarea的value值关联在一起
"model": function(data, scope, scopes) {
var element = data.element;
var tagName = element.tagName;
if (typeof modelBinding[tagName] === "function") {
var array = [scope].concat(scopes);
var name = data.node.value, model;
array.forEach(function(obj) {
if (!model && obj.hasOwnProperty(name)) {
model = obj;
}
});
model = model || {};
modelBinding[tagName](element, model, name);
}
},
//抽取innerText中插入表达式,置换成真实数据放在它原来的位置
//<div>{{firstName}} + java</div>,如果model.firstName为ruby, 那么变成
//<div>ruby + java</div>
"text": function(data, scope, scopes) {
var node = data.node;
watchView(data.value, scope, scopes, data, function(val) {
node.nodeValue = val;
});
},
//控制元素显示或隐藏
"toggle": function(data, scope, scopes) {
var element = $(data.element);
watchView(data.value, scope, scopes, data, function(val) {
element.toggle(!!val);
});
},
//这是一个字符串属性绑定的范本, 方便你在title, alt, src, href添加插值表达式
//<a href="{{url.hostname}}/{{url.pathname}}.html">
"href": function(data, scope, scopes) {
//如果没有则说明是使用ng-href的形式
var text = data.value.trim();
var node = data.node;
var simple = node.name.indexOf(prefix) === 0;
var name = data.type;
if (!simple && /^\{\{([^}]+)\}\}$/.test(text)) {
simple = true;
text = RegExp.$1;
}
watchView(text, scope, scopes, data, function(val) {
data.element[name] = val;
}, simple ? null : scanExpr(data.value));
},
//这是一个布尔属性绑定的范本,布尔属性插值要求整个都是一个插值表达式,用{{}}包起来
//布尔属性在IE下无法取得原来的字符串值,变成一个布尔,因此需要用ng-disabled
//text.slice(2, text.lastIndexOf("}}"))
"disabled": function(data, scope, scopes) {
var element = data.element, name = data.type,
propName = $.propMap[name] || name;
watchView(data.value, scope, scopes, data, function(val) {
element[propName] = !!val;
});
},
//切换类名,有三种形式
//1、ms-class-xxx="flag" 根据flag的值决定是添加或删除类名xxx
//2、ms-class=obj obj为一个{xxx:true, yyy:false}的对象,根据其值添加或删除其键名
//3、ms-class=str str是一个类名或多个类名的集合,全部添加
//http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
"class": function(data, scope, scopes) {
var element = $(data.element);
watchView(data.value, scope, scopes, data, function(val) {
if (data.args) {//第一种形式
element.toggleClass(data.args.join(""), !!val);
} else if (typeof val === "string") {
element.addClass(val);
} else if (val && typeof val === "object") {
forEach(val, function(cls, flag) {
if (flag) {
element.addClass(cls);
} else {
element.removeClass(cls);
}
});
}
});
},
//控制流程绑定
"skip": function() {
arguments[3].stopBinding = true;
},
"if": function(data, scope, scopes) {
var element = data.element;
var fragment = element.ownerDocument.createDocumentFragment();
watchView(data.value, scope, scopes, data, function(val) {
if (val) {
while (fragment.firstChild) {
element.appendChild(fragment.firstChild);
}
} else {
while (element.firstChild) {
fragment.appendChild(element.firstChild);
}
}
});
},
"each": function(data, scope, scopes, flags) {
var args = data.args, itemName = args[0] || "$data", indexName = args[1] || "$index";
var parent = data.element;
var scopeList = [scope].concat(scopes);
var list = evalExpr(data.value, scopeList, data);
var doc = parent.ownerDocument;
var fragment = doc.createDocumentFragment();
while (parent.firstChild) {
fragment.appendChild(parent.firstChild);
}
function updateListView(method, args, len) {
var listName = list.name;
switch (method) {
case "push":
$.each(args, function(index, item) {
updateView(len + index, item);
});
break;
case "unshift" :
list.insertBefore = parent.firstChild;
$.each(args, function(index, item) {
updateView(index, item);
});
resetIndex(parent, listName);
delete list.insertBefore;
break;
case "pop":
var node = findIndex(parent, listName, len - 1);
if (node) {
removeView(parent, listName, node);
}
break;
case "shift":
removeView(parent, listName, 0, parent.firstChild);
resetIndex(parent, listName);
break;
case "clear":
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
break;
case "splice":
var start = args[0], second = args[1], adds = [].slice.call(args, 2);
var deleteCount = second >= 0 ? second : len - start;
var node = findIndex(parent, listName, start);
if (node) {
removeViews(parent, listName, node, deleteCount);
resetIndex(parent, listName);
if (adds.length) {
node = findIndex(parent, listName, start);
list.insertBefore = node;
$.each(adds, function(index, item) {
updateView(index, item);
});
resetIndex(parent, listName);
delete list.insertBefore;
}
}
break;
case "reverse":
case "sort":
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
$.each(list, function(index, item) {
updateView(index, item);
});
break;
}
}
var isList = Array.isArray(list[ subscribers ] || {});
if (isList) {
list[ subscribers ].push(updateListView);
}
function updateView(index, item, clone, insertBefore) {
var newScope = {}, textNodes = [];
newScope[itemName] = item;
newScope[indexName] = index;
if (isList) {
var comment = doc.createComment(list.name + index);
if (list.insertBefore) {
parent.insertBefore(comment, list.insertBefore);
} else {
parent.appendChild(comment);
}
}
for (var node = fragment.firstChild; node; node = node.nextSibling) {
clone = node.cloneNode(true);
if (clone.nodeType === 1) {
scanTag(clone, newScope, scopeList, doc);//扫描元素节点
} else if (clone.nodeType === 3) {
textNodes.push(clone);
}
if (list.insertBefore) {
parent.insertBefore(clone, list.insertBefore);
} else {
parent.appendChild(clone);
}
}
for (var i = 0; node = textNodes[i++]; ) {
scanText(node, newScope, scopeList, doc);//扫描文本节点
}
}
forEach(list, updateView);
flags.stopBinding = true;
}
};
//重置所有路标
function resetIndex(elem, name) {
var index = 0;
for (var node = elem.firstChild; node; node = node.nextSibling) {
if (node.nodeType === 8) {
if (node.nodeValue.indexOf(name) === 0) {
if (node.nodeValue !== name + index) {
node.nodeValue = name + index;
}
index++;
}
}
}
}
function removeView(elem, name, node) {
var nodes = [], doc = elem.ownerDocument, view = doc.createDocumentFragment();
for (var check = node; check; check = check.nextSibling) {
//如果到达下一个路标,则断开,将收集到的节点放到文档碎片与下一个路标返回
if (check.nodeType === 8 && check.nodeValue.indexOf(name) === 0
&& check !== node) {
break
}
nodes.push(check);
}
for (var i = 0; node = nodes[i++]; ) {
view.appendChild(node);
}
return [view, check];
}
function removeViews(elem, name, node, number) {
var ret = [];
do {
var array = removeView(elem, name, node);
if (array[1]) {
node = array[1];
ret.push(array[0]);
} else {
break
}
} while (ret.length !== number);
return ret;
}
function findIndex(elem, name, target) {
var index = 0;
for (var node = elem.firstChild; node; node = node.nextSibling) {
if (node.nodeType === 8) {
if (node.nodeValue.indexOf(name) === 0) {
if (node.nodeValue == name + target) {
return node;
}
index++;
}
}
}
}
//循环绑定其他布尔属性
var bools = "autofocus,autoplay,async,checked,controls,declare,defer,"
+ "contenteditable,ismap,loop,multiple,noshade,open,noresize,readonly,selected";
bools.replace($.rword, function(name) {
bindingHandlers[name] = bindingHandlers.disabled;
});
//建议不要直接在src属性上修改,因此这样会发出无效的请求,使用ms-src
"title, alt, src".replace($.rword, function(name) {
bindingHandlers[name] = bindingHandlers.href;
});
var modelBinding = bindingHandlers.model;
//如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起
//字段变,value就变;value变,字段也跟着变。默认是绑定input事件,
//我们也可以使用ng-event="change"改成change事件
modelBinding.INPUT = function(element, model, name) {
if (element.name === void 0) {
element.name = name;
}
var type = element.type, ok;
function updateModel() {
model[name] = element.value;
}
function updateView() {
element.value = model[name];
}
if (/^(password|textarea|text)$/.test(type)) {
ok = true;
updateModel = function() {
model[name] = element.value;
};
updateView = function() {
element.value = model[name];
};
var event = element.attributes[prefix + "event"] || {};
event = event.value;
if (event === "change") {
$.bind(element, event, updateModel);
} else {
if (window.addEventListener) { //先执行W3C
element.addEventListener("input", updateModel, false);
} else {
element.attachEvent("onpropertychange", updateModel);
}
if (window.VBArray && window.addEventListener) { //IE9
element.attachEvent("onkeydown", function(e) {
var key = e.keyCode;
if (key === 8 || key === 46) {
updateModel(); //处理回退与删除
}
});
element.attachEvent("oncut", updateModel); //处理粘贴
}
}
} else if (type === "radio") {
ok = true;
updateView = function() {
element.checked = model[name] === element.value;
};
$.bind(element, "click", updateModel);//IE6-8
} else if (type === "checkbox") {
ok = true;
updateModel = function() {
if (element.checked) {
$.Array.ensure(model[name], element.value);
} else {
$.Array.remove(model[name], element.value);
}
};
updateView = function() {
element.checked = !!~model[name].indexOf(element.value);
};
$.bind(element, "click", updateModel);//IE6-8
}
Publish[ expando ] = updateView;
updateView();
delete Publish[ expando ];
};
modelBinding.SELECT = function(element, model, name) {
var select = $(element);
function updateModel() {
model[name] = select.val();
}
function updateView() {
select.val(model[name]);
}
$.bind(element, "change", updateModel);
Publish[ expando ] = updateView;
updateView();
delete Publish[ expando ];
};
modelBinding.TEXTAREA = modelBinding.INPUT;
/*********************************************************************
* Collection *
**********************************************************************/
//http://msdn.microsoft.com/en-us/library/windows/apps/hh700774.aspx
//http://msdn.microsoft.com/zh-cn/magazine/jj651576.aspx
//Data bindings 数据/界面绑定
//Compatibility 兼容其他
//Extensibility 可扩充性
//No direct DOM manipulations 不直接对DOM操作
function Collection(list, name) {
var collection = list.concat();
collection[ subscribers ] = [];
collection.name = "#" + name;
String("push,pop,shift,unshift,splice,sort,reverse").replace($.rword, function(method) {
var nativeMethod = collection[ method ];
collection[ method ] = function() {
var len = this.length;
var ret = nativeMethod.apply(this, arguments);
notifySubscribers(this, method, arguments, len);
return ret;
};
});
collection.clear = function() {
this.length = 0;
notifySubscribers(this, "clear", []);
return this;
};
collection.sortBy = function(fn, scope) {
var ret = $.Array.sortBy(this, fn, scope);
notifySubscribers(this, "sort", []);
return ret;
};
collection.ensure = function(el) {
var len = this.length;
var ret = $.Array.ensure(this, el);
if (this.length > len) {
notifySubscribers(this, "push", [el], len);
}
return ret;
};
collection.update = function() {//强制刷新页面
notifySubscribers(this, "sort", []);
return this;
};
collection.removeAt = function(index) {//移除指定索引上的元素
this.splice(index, 1);
};
collection.remove = function(item) {//移除第一个等于给定值的元素
var index = this.indexOf(item);
if (index !== -1) {
this.removeAt(index);
}
};
return collection;
}
/*********************************************************************
* Subscription *
**********************************************************************/
/*
为简单起见,我们把双向绑定链分成三层, 顶层, 中层, 底层。顶层是updateView, updateListView等需要撷取底层的值来更新自身的局部刷新函数, 中层是监控数组与依赖于其他属性的计算监控属性,底层是监控属性。高层总是依赖于低层,但高层该如何知道它是依赖哪些底层呢?
在emberjs中,作为计算监控属性的fullName通过property方法,得知自己是依赖于firstName, lastName。
App.Person = Ember.Object.extend({
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') +
" " + this.get('lastName');
}.property('firstName', 'lastName')
});
在knockout中,用了一个取巧方法,将所有要监控的属性转换为一个函数。当fullName第一次求值时,它将自己的名字放到一个地方X,值为一个数组。然后函数体内的firstName与lastName在自身求值时,也会访问X,发现上面有数组时,就放进去。当fullName执行完毕,就得知它依赖于哪个了,并从X删掉数组。
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.computed(function() {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
详见 subscribables/observable.js subscribables/dependentObservable.js
*/
//http://www.cnblogs.com/whitewolf/archive/2012/07/07/2580630.html
function getSubscribers(accessor) {
if (typeof accessor === "string") {
return obsevers[accessor] || (obsevers[accessor] = []);
} else {
return accessor[ subscribers ];
}
}
function collectSubscribers(accessor) {//收集依赖于这个域的函数
if (Publish[ expando ]) {
var list = getSubscribers(accessor);
$.Array.ensure(list, Publish[ expando ]);
}
}
function notifySubscribers(accessor) {//通知依赖于这个域的函数们更新自身
var list = getSubscribers(accessor);
if (list && list.length) {
var args = [].slice.call(arguments, 1);
var safelist = list.concat();
for (var i = 0, fn; fn = safelist[i++]; ) {
if (typeof fn === "function") {
fn.apply(0, args); //强制重新计算自身
}
}
}
}
/*********************************************************************
* Model *
**********************************************************************/
$.model = function(name, obj) {
name = name || "root";
if (avalon.models[name]) {
$.error('已经存在"' + name + '"模块');
} else {
var model = modelFactory(name, obj, $.skipArray || []);
model.$modelName = name;
return avalon.models[name] = model
}
};
var startWithDollar = /^\$/;
function modelFactory(name, obj, skipArray) {
var model = {}, first = [], second = [];
forEach(obj, function(key, val) {
//如果不在忽略列表内,并且没有以$开头($开头的属性名留着框架使用)
if (skipArray.indexOf(key) === -1 && !startWithDollar.test(key)) {
//相依赖的computed
var accessor = name + key, old;
if (Array.isArray(val) && !val[subscribers]) {
model[key] = Collection(val, accessor);
} else if (typeof val === "object") {
if ("set" in val && Object.keys(val).length <= 2) {
Object.defineProperty(model, key, {
set: function(neo) {
if (typeof val.set === "function") {
val.set.call(model, neo); //通知底层改变
} else {
obj[key] = neo;
}
if (old !== neo) {
old = neo;
notifySubscribers(accessor); //通知顶层改变
}
},
//get方法肯定存在,那么肯定在这里告诉它的依赖,把它的setter放到依赖的订阅列表中
get: function() {
var flagDelete = false;
if (!obsevers[accessor]) {
flagDelete = true;
Publish[ expando ] = function() {
notifySubscribers(accessor); //通知顶层改变
};
obsevers[accessor] = [];
}
old = val.get.call(model);
obj[name] = old;
if (flagDelete) {
delete Publish[ expando ];
}
return old;
},
enumerable: true
});
second.push(key);
} else {
}
} else if (typeof val !== "function") {
Object.defineProperty(model, key, {
set: function(neo) {
if (obj[key] !== neo) {
obj[key] = neo;
//通知中层,顶层改变
notifySubscribers(accessor);
}
},
get: function() {
//如果中层把方法放在Publish[ expando ]中
if (!obj.$occoecatio){//为了防止它在不合适的时候收集订阅者,添加$occoecatio标识让它瞎掉
collectSubscribers(accessor);
}
return obj[key];
},
enumerable: true
});
first.push(key);
}
}
});
first.forEach(function(key) {
model[key] = obj[key];
});
second.forEach(function(key) {
first = model[key];
});
return model;
}
/*********************************************************************
* Scan *
**********************************************************************/
function scanTag(elem, scope, scopes, doc) {
scopes = scopes || [];
var flags = {};
scanAttr(elem, scope, scopes, flags);//扫描特点节点
if (flags.stopBinding) {//是否要停止扫描
return false;
}
if (flags.newScope) {//更换作用域, 复制父作用域堆栈,防止互相影响
scopes = scopes.slice(0);
scope = flags.newScope;
}
if (elem.canHaveChildren === false || !stopScan[elem.tagName]) {
var textNodes = [];
for (var node = elem.firstChild; node; node = node.nextSibling) {
if (node.nodeType === 1) {
scanTag(node, scope, scopes, doc);//扫描元素节点
} else if (node.nodeType === 3) {
textNodes.push(node);
}
}
for (var i = 0; node = textNodes[i++]; ) {//延后执行
scanText(node, scope, scopes, doc);//扫描文本节点
}
}
}
var stopScan = $.oneObject("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr,script,style".toUpperCase());
//扫描元素节点中直属的文本节点,并进行抽取
function scanText(textNode, scope, scopes, doc) {
var bindings = extractTextBindings(textNode, doc);
if (bindings.length) {
executeBindings(bindings, scope, scopes);
}
}
function scanExpr(value) {
var tokens = [];
if (hasExpr(value)) {
//抽取{{ }} 里面的语句,并以它们为定界符,拆分原来的文本
do {
value = value.replace(regOpenTag, function(a, b) {
if (b) {
tokens.push({
value: b,
expr: false
});
}
return "";
});
value = value.replace(regCloseTag, function(a, b) {
if (b) {
var filters = []
if (b.indexOf("|") > 0) {
b = b.replace(/\|\s*(\w+)\s*(\([^)]+\))?/g, function(c, d, e) {
filters.push(d + e)
return ""
});
}
tokens.push({
value: b,
expr: true,
filters: filters.length ? filters : void 0
});
}
return "";
});
} while (hasExpr(value));
if (value) {
tokens.push({
value: value,
expr: false
});
}
}
return tokens;
}
function scanAttr(el, scope, scopes, flags) {
var bindings = [];
for (var i = 0, attr; attr = el.attributes[i++]; ) {
if (attr.specified) {
var isBinding = false, remove = false;
if (attr.name.indexOf(prefix) !== -1) {//如果是以指定前缀命名的
var type = attr.name.replace(prefix, "");
if (type.indexOf("-") > 0) {
var args = type.split("-");
type = args.shift();
}
remove = true;
isBinding = typeof bindingHandlers[type] === "function";
} else if (bindingHandlers[attr.name] && hasExpr(attr.value)) {
type = attr.name; //如果只是普通属性,但其值是个插值表达式
isBinding = true;
}
if (isBinding) {
bindings.push({
type: type,
args: args,
element: el,
node: attr,
remove: remove,
value: attr.nodeValue
});
}
if (!flags.newScope && type === "controller") {//更换作用域
var temp = avalon.models[attr.value];
if (typeof temp === "object" && temp !== scope) {
scopes.unshift(scope);
flags.newScope = scope = temp;
}
}
}
}
executeBindings(bindings, scope, scopes, flags);
}
function executeBindings(bindings, scope, scopes, flags) {
bindings.forEach(function(data) {
bindingHandlers[data.type](data, scope, scopes, flags);
if (data.remove) {//移除数据绑定,防止被二次解析
data.element.removeAttribute(data.node.name);
}
});
}
function extractTextBindings(textNode, doc) {
var bindings = [], tokens = scanExpr(textNode.nodeValue);
if (tokens.length) {
var fragment = doc.createDocumentFragment();
while (tokens.length) {//将文本转换为文本节点,并替换原来的文本节点
var token = tokens.shift();
var node = doc.createTextNode(token.value);
if (token.expr) {
bindings.push({
type: "text",
node: node,
element: textNode.parentNode,
value: token.value,
filters: token.filters
}); //收集带有插值表达式的文本
}
fragment.appendChild(node);
}
textNode.parentNode.replaceChild(fragment, textNode);
}
return bindings;
}
var model = $.model("app", {
firstName: "xxx",
lastName: "oooo",
bool: false,
array: [1, 2, 3, 4, 5, 6, 7, 8],
select: "test1",
color: "green",
vehicle: ["car"],
fullName: {
set: function(val) {
var array = val.split(" ");
this.firstName = array[0] || "";
this.lastName = array[1] || "";
},
get: function() {
return this.firstName + " " + this.lastName;
}
}
});
$.model("son", {
firstName: "yyyy"
});
$.model("aaa", {
firstName: "6666"
});
scanTag(document.body, model, [], document);
setTimeout(function() {
model.firstName = "setTimeout";
}, 2000);
setTimeout(function() {
model.array.reverse()
// console.log(obsevers.applastName.join("\r\n"))
}, 3000);
});
define("mvvm", "$event,$css,$attr".split(","), function($) { var prefix = "ms-"; var avalon = $.avalon = { models: {}, filters: { uppercase: function(str) { return str.toUpperCase() }, lowercase: function(str) { return str.toLowerCase(); }, number: function(str) { return isFinite(str) ? str : ""; }, aaa: function(str) { return str + "AAA" } } }; var blank = " "; var obsevers = {}; var Publish = {};//将函数放到发布对象上,让依赖它的函数 var expando = new Date - 0; var subscribers = "$" + expando; /********************************************************************* * View * **********************************************************************/ var regOpenTag = /([^{]*)\{\{/; var regCloseTag = /([^}]*)\}\}/; function hasExpr(value) { var index = value.indexOf("{{"); return index !== -1 && index < value.indexOf("}}"); } function forEach(obj, fn) { if (obj) {//不能传个null, undefined进来 var isArray = isFinite(obj.length), i = 0 if (isArray) { for (var n = obj.length; i < n; i++) { fn(i, obj[i]); } } else { for (i in obj) { if (obj.hasOwnProperty(i)) { fn(i, obj[i]); } } } } } //eval一个或多个表达式 function watchView(text, scope, scopes, data, callback, tokens) { var updateView, target, filters = data.filters; var scopeList = [scope].concat(scopes); if (!filters) { for (var i = 0, obj; obj = scopeList[i++]; ) { if (obj.hasOwnProperty(text)) { target = obj;//如果能在作用域上直接找到,我们就不需要eval了 break; } } } if (target) { updateView = function() { callback(target[text]); }; } else { updateView = function() { if (tokens) { var val = tokens.map(function(obj) { return obj.expr ? evalExpr(obj.value, scopeList, data) : obj.value; }).join(""); } else { val = evalExpr(text, scopeList, data); } callback(val); }; } Publish[ expando ] = updateView; updateView(); delete Publish[ expando ]; } function evalExpr(text, scopeList, data) { console.log(text) var uniq = { $occoecatio: 1 }, names = [], args = []; scopeList.forEach(function(scope) { scope.$occoecatio = true; forEach(scope, function(key, val) { if (!uniq[key]) { names.push(key); args.push(val); uniq[key] = 1; } }); delete scope.$occoecatio; }); if (data.compileFn) { console.log(data.compileFn+"") args.push(avalon.filters) return data.compileFn.apply(data.compileFn, args); } if (data.filters) { var random = new Date - 0, textBuffer = [], fargs; textBuffer.push("var ret", random, "=", text, "\r\n"); for (var i = 0, f; f = data.filters[i++]; ) { var start = f.indexOf("("); if (start !== -1) { fargs = f.slice(start + 1, f.lastIndexOf(")")).trim(); fargs = "," + fargs; f = f.slice(0, start).trim(); } else { fargs = ""; } textBuffer.push(" if(filters", random, ".", f, "){\r\n\ttry{ret", random, " = filters", random, ".", f, "(ret", random, fargs, ")}catch(e){};\r\n}\r\n"); } textBuffer.push("\treturn ret", random); text = textBuffer.join(""); names.push("filters" + random); args.push(avalon.filters); delete data.filters;//释放内存 } else { text = "\treturn " + text; } try { var fn = Function.apply(Function, names.concat(text)); var val = fn.apply(fn, args); data.compileFn = fn;//缓存,防止二次编译 } catch (e) { data.compileFn = function() { return ""; }; val = ""; } uniq = textBuffer = names = null;//释放内存 return val; } var bindingHandlers = avalon.bindingHandlers = { //将模型中的字段与input, textarea的value值关联在一起 "model": function(data, scope, scopes) { var element = data.element; var tagName = element.tagName; if (typeof modelBinding[tagName] === "function") { var array = [scope].concat(scopes); var name = data.node.value, model; array.forEach(function(obj) { if (!model && obj.hasOwnProperty(name)) { model = obj; } }); model = model || {}; modelBinding[tagName](element, model, name); } }, //抽取innerText中插入表达式,置换成真实数据放在它原来的位置 //<div>{{firstName}} + java</div>,如果model.firstName为ruby, 那么变成 //<div>ruby + java</div> "text": function(data, scope, scopes) { var node = data.node; watchView(data.value, scope, scopes, data, function(val) { node.nodeValue = val; }); }, //控制元素显示或隐藏 "toggle": function(data, scope, scopes) { var element = $(data.element); watchView(data.value, scope, scopes, data, function(val) { element.toggle(!!val); }); }, //这是一个字符串属性绑定的范本, 方便你在title, alt, src, href添加插值表达式 //<a href="{{url.hostname}}/{{url.pathname}}.html"> "href": function(data, scope, scopes) { //如果没有则说明是使用ng-href的形式 var text = data.value.trim(); var node = data.node; var simple = node.name.indexOf(prefix) === 0; var name = data.type; if (!simple && /^\{\{([^}]+)\}\}$/.test(text)) { simple = true; text = RegExp.$1; } watchView(text, scope, scopes, data, function(val) { data.element[name] = val; }, simple ? null : scanExpr(data.value)); }, //这是一个布尔属性绑定的范本,布尔属性插值要求整个都是一个插值表达式,用{{}}包起来 //布尔属性在IE下无法取得原来的字符串值,变成一个布尔,因此需要用ng-disabled //text.slice(2, text.lastIndexOf("}}")) "disabled": function(data, scope, scopes) { var element = data.element, name = data.type, propName = $.propMap[name] || name; watchView(data.value, scope, scopes, data, function(val) { element[propName] = !!val; }); }, //切换类名,有三种形式 //1、ms-class-xxx="flag" 根据flag的值决定是添加或删除类名xxx //2、ms-class=obj obj为一个{xxx:true, yyy:false}的对象,根据其值添加或删除其键名 //3、ms-class=str str是一个类名或多个类名的集合,全部添加 //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html "class": function(data, scope, scopes) { var element = $(data.element); watchView(data.value, scope, scopes, data, function(val) { if (data.args) {//第一种形式 element.toggleClass(data.args.join(""), !!val); } else if (typeof val === "string") { element.addClass(val); } else if (val && typeof val === "object") { forEach(val, function(cls, flag) { if (flag) { element.addClass(cls); } else { element.removeClass(cls); } }); } }); }, //控制流程绑定 "skip": function() { arguments[3].stopBinding = true; }, "if": function(data, scope, scopes) { var element = data.element; var fragment = element.ownerDocument.createDocumentFragment(); watchView(data.value, scope, scopes, data, function(val) { if (val) { while (fragment.firstChild) { element.appendChild(fragment.firstChild); } } else { while (element.firstChild) { fragment.appendChild(element.firstChild); } } }); }, "each": function(data, scope, scopes, flags) { var args = data.args, itemName = args[0] || "$data", indexName = args[1] || "$index"; var parent = data.element; var scopeList = [scope].concat(scopes); var list = evalExpr(data.value, scopeList, data); var doc = parent.ownerDocument; var fragment = doc.createDocumentFragment(); while (parent.firstChild) { fragment.appendChild(parent.firstChild); } function updateListView(method, args, len) { var listName = list.name; switch (method) { case "push": $.each(args, function(index, item) { updateView(len + index, item); }); break; case "unshift" : list.insertBefore = parent.firstChild; $.each(args, function(index, item) { updateView(index, item); }); resetIndex(parent, listName); delete list.insertBefore; break; case "pop": var node = findIndex(parent, listName, len - 1); if (node) { removeView(parent, listName, node); } break; case "shift": removeView(parent, listName, 0, parent.firstChild); resetIndex(parent, listName); break; case "clear": while (parent.firstChild) { parent.removeChild(parent.firstChild); } break; case "splice": var start = args[0], second = args[1], adds = [].slice.call(args, 2); var deleteCount = second >= 0 ? second : len - start; var node = findIndex(parent, listName, start); if (node) { removeViews(parent, listName, node, deleteCount); resetIndex(parent, listName); if (adds.length) { node = findIndex(parent, listName, start); list.insertBefore = node; $.each(adds, function(index, item) { updateView(index, item); }); resetIndex(parent, listName); delete list.insertBefore; } } break; case "reverse": case "sort": while (parent.firstChild) { parent.removeChild(parent.firstChild); } $.each(list, function(index, item) { updateView(index, item); }); break; } } var isList = Array.isArray(list[ subscribers ] || {}); if (isList) { list[ subscribers ].push(updateListView); } function updateView(index, item, clone, insertBefore) { var newScope = {}, textNodes = []; newScope[itemName] = item; newScope[indexName] = index; if (isList) { var comment = doc.createComment(list.name + index); if (list.insertBefore) { parent.insertBefore(comment, list.insertBefore); } else { parent.appendChild(comment); } } for (var node = fragment.firstChild; node; node = node.nextSibling) { clone = node.cloneNode(true); if (clone.nodeType === 1) { scanTag(clone, newScope, scopeList, doc);//扫描元素节点 } else if (clone.nodeType === 3) { textNodes.push(clone); } if (list.insertBefore) { parent.insertBefore(clone, list.insertBefore); } else { parent.appendChild(clone); } } for (var i = 0; node = textNodes[i++]; ) { scanText(node, newScope, scopeList, doc);//扫描文本节点 } } forEach(list, updateView); flags.stopBinding = true; } }; //重置所有路标 function resetIndex(elem, name) { var index = 0; for (var node = elem.firstChild; node; node = node.nextSibling) { if (node.nodeType === 8) { if (node.nodeValue.indexOf(name) === 0) { if (node.nodeValue !== name + index) { node.nodeValue = name + index; } index++; } } } } function removeView(elem, name, node) { var nodes = [], doc = elem.ownerDocument, view = doc.createDocumentFragment(); for (var check = node; check; check = check.nextSibling) { //如果到达下一个路标,则断开,将收集到的节点放到文档碎片与下一个路标返回 if (check.nodeType === 8 && check.nodeValue.indexOf(name) === 0 && check !== node) { break } nodes.push(check); } for (var i = 0; node = nodes[i++]; ) { view.appendChild(node); } return [view, check]; } function removeViews(elem, name, node, number) { var ret = []; do { var array = removeView(elem, name, node); if (array[1]) { node = array[1]; ret.push(array[0]); } else { break } } while (ret.length !== number); return ret; } function findIndex(elem, name, target) { var index = 0; for (var node = elem.firstChild; node; node = node.nextSibling) { if (node.nodeType === 8) { if (node.nodeValue.indexOf(name) === 0) { if (node.nodeValue == name + target) { return node; } index++; } } } } //循环绑定其他布尔属性 var bools = "autofocus,autoplay,async,checked,controls,declare,defer," + "contenteditable,ismap,loop,multiple,noshade,open,noresize,readonly,selected"; bools.replace($.rword, function(name) { bindingHandlers[name] = bindingHandlers.disabled; }); //建议不要直接在src属性上修改,因此这样会发出无效的请求,使用ms-src "title, alt, src".replace($.rword, function(name) { bindingHandlers[name] = bindingHandlers.href; }); var modelBinding = bindingHandlers.model; //如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起 //字段变,value就变;value变,字段也跟着变。默认是绑定input事件, //我们也可以使用ng-event="change"改成change事件 modelBinding.INPUT = function(element, model, name) { if (element.name === void 0) { element.name = name; } var type = element.type, ok; function updateModel() { model[name] = element.value; } function updateView() { element.value = model[name]; } if (/^(password|textarea|text)$/.test(type)) { ok = true; updateModel = function() { model[name] = element.value; }; updateView = function() { element.value = model[name]; }; var event = element.attributes[prefix + "event"] || {}; event = event.value; if (event === "change") { $.bind(element, event, updateModel); } else { if (window.addEventListener) { //先执行W3C element.addEventListener("input", updateModel, false); } else { element.attachEvent("onpropertychange", updateModel); } if (window.VBArray && window.addEventListener) { //IE9 element.attachEvent("onkeydown", function(e) { var key = e.keyCode; if (key === 8 || key === 46) { updateModel(); //处理回退与删除 } }); element.attachEvent("oncut", updateModel); //处理粘贴 } } } else if (type === "radio") { ok = true; updateView = function() { element.checked = model[name] === element.value; }; $.bind(element, "click", updateModel);//IE6-8 } else if (type === "checkbox") { ok = true; updateModel = function() { if (element.checked) { $.Array.ensure(model[name], element.value); } else { $.Array.remove(model[name], element.value); } }; updateView = function() { element.checked = !!~model[name].indexOf(element.value); }; $.bind(element, "click", updateModel);//IE6-8 } Publish[ expando ] = updateView; updateView(); delete Publish[ expando ]; }; modelBinding.SELECT = function(element, model, name) { var select = $(element); function updateModel() { model[name] = select.val(); } function updateView() { select.val(model[name]); } $.bind(element, "change", updateModel); Publish[ expando ] = updateView; updateView(); delete Publish[ expando ]; }; modelBinding.TEXTAREA = modelBinding.INPUT; /********************************************************************* * Collection * **********************************************************************/ //http://msdn.microsoft.com/en-us/library/windows/apps/hh700774.aspx //http://msdn.microsoft.com/zh-cn/magazine/jj651576.aspx //Data bindings 数据/界面绑定 //Compatibility 兼容其他 //Extensibility 可扩充性 //No direct DOM manipulations 不直接对DOM操作 function Collection(list, name) { var collection = list.concat(); collection[ subscribers ] = []; collection.name = "#" + name; String("push,pop,shift,unshift,splice,sort,reverse").replace($.rword, function(method) { var nativeMethod = collection[ method ]; collection[ method ] = function() { var len = this.length; var ret = nativeMethod.apply(this, arguments); notifySubscribers(this, method, arguments, len); return ret; }; }); collection.clear = function() { this.length = 0; notifySubscribers(this, "clear", []); return this; }; collection.sortBy = function(fn, scope) { var ret = $.Array.sortBy(this, fn, scope); notifySubscribers(this, "sort", []); return ret; }; collection.ensure = function(el) { var len = this.length; var ret = $.Array.ensure(this, el); if (this.length > len) { notifySubscribers(this, "push", [el], len); } return ret; }; collection.update = function() {//强制刷新页面 notifySubscribers(this, "sort", []); return this; }; collection.removeAt = function(index) {//移除指定索引上的元素 this.splice(index, 1); }; collection.remove = function(item) {//移除第一个等于给定值的元素 var index = this.indexOf(item); if (index !== -1) { this.removeAt(index); } }; return collection; } /********************************************************************* * Subscription * **********************************************************************/ /* 为简单起见,我们把双向绑定链分成三层, 顶层, 中层, 底层。顶层是updateView, updateListView等需要撷取底层的值来更新自身的局部刷新函数, 中层是监控数组与依赖于其他属性的计算监控属性,底层是监控属性。高层总是依赖于低层,但高层该如何知道它是依赖哪些底层呢? 在emberjs中,作为计算监控属性的fullName通过property方法,得知自己是依赖于firstName, lastName。 App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + " " + this.get('lastName'); }.property('firstName', 'lastName') }); 在knockout中,用了一个取巧方法,将所有要监控的属性转换为一个函数。当fullName第一次求值时,它将自己的名字放到一个地方X,值为一个数组。然后函数体内的firstName与lastName在自身求值时,也会访问X,发现上面有数组时,就放进去。当fullName执行完毕,就得知它依赖于哪个了,并从X删掉数组。 var ViewModel = function(first, last) { this.firstName = ko.observable(first); this.lastName = ko.observable(last); this.fullName = ko.computed(function() { // Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName. return this.firstName() + " " + this.lastName(); }, this); }; 详见 subscribables/observable.js subscribables/dependentObservable.js */ //http://www.cnblogs.com/whitewolf/archive/2012/07/07/2580630.html function getSubscribers(accessor) { if (typeof accessor === "string") { return obsevers[accessor] || (obsevers[accessor] = []); } else { return accessor[ subscribers ]; } } function collectSubscribers(accessor) {//收集依赖于这个域的函数 if (Publish[ expando ]) { var list = getSubscribers(accessor); $.Array.ensure(list, Publish[ expando ]); } } function notifySubscribers(accessor) {//通知依赖于这个域的函数们更新自身 var list = getSubscribers(accessor); if (list && list.length) { var args = [].slice.call(arguments, 1); var safelist = list.concat(); for (var i = 0, fn; fn = safelist[i++]; ) { if (typeof fn === "function") { fn.apply(0, args); //强制重新计算自身 } } } } /********************************************************************* * Model * **********************************************************************/ $.model = function(name, obj) { name = name || "root"; if (avalon.models[name]) { $.error('已经存在"' + name + '"模块'); } else { var model = modelFactory(name, obj, $.skipArray || []); model.$modelName = name; return avalon.models[name] = model } }; var startWithDollar = /^\$/; function modelFactory(name, obj, skipArray) { var model = {}, first = [], second = []; forEach(obj, function(key, val) { //如果不在忽略列表内,并且没有以$开头($开头的属性名留着框架使用) if (skipArray.indexOf(key) === -1 && !startWithDollar.test(key)) { //相依赖的computed var accessor = name + key, old; if (Array.isArray(val) && !val[subscribers]) { model[key] = Collection(val, accessor); } else if (typeof val === "object") { if ("set" in val && Object.keys(val).length <= 2) { Object.defineProperty(model, key, { set: function(neo) { if (typeof val.set === "function") { val.set.call(model, neo); //通知底层改变 } else { obj[key] = neo; } if (old !== neo) { old = neo; notifySubscribers(accessor); //通知顶层改变 } }, //get方法肯定存在,那么肯定在这里告诉它的依赖,把它的setter放到依赖的订阅列表中 get: function() { var flagDelete = false; if (!obsevers[accessor]) { flagDelete = true; Publish[ expando ] = function() { notifySubscribers(accessor); //通知顶层改变 }; obsevers[accessor] = []; } old = val.get.call(model); obj[name] = old; if (flagDelete) { delete Publish[ expando ]; } return old; }, enumerable: true }); second.push(key); } else { } } else if (typeof val !== "function") { Object.defineProperty(model, key, { set: function(neo) { if (obj[key] !== neo) { obj[key] = neo; //通知中层,顶层改变 notifySubscribers(accessor); } }, get: function() { //如果中层把方法放在Publish[ expando ]中 if (!obj.$occoecatio){//为了防止它在不合适的时候收集订阅者,添加$occoecatio标识让它瞎掉 collectSubscribers(accessor); } return obj[key]; }, enumerable: true }); first.push(key); } } }); first.forEach(function(key) { model[key] = obj[key]; }); second.forEach(function(key) { first = model[key]; }); return model; } /********************************************************************* * Scan * **********************************************************************/ function scanTag(elem, scope, scopes, doc) { scopes = scopes || []; var flags = {}; scanAttr(elem, scope, scopes, flags);//扫描特点节点 if (flags.stopBinding) {//是否要停止扫描 return false; } if (flags.newScope) {//更换作用域, 复制父作用域堆栈,防止互相影响 scopes = scopes.slice(0); scope = flags.newScope; } if (elem.canHaveChildren === false || !stopScan[elem.tagName]) { var textNodes = []; for (var node = elem.firstChild; node; node = node.nextSibling) { if (node.nodeType === 1) { scanTag(node, scope, scopes, doc);//扫描元素节点 } else if (node.nodeType === 3) { textNodes.push(node); } } for (var i = 0; node = textNodes[i++]; ) {//延后执行 scanText(node, scope, scopes, doc);//扫描文本节点 } } } var stopScan = $.oneObject("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr,script,style".toUpperCase()); //扫描元素节点中直属的文本节点,并进行抽取 function scanText(textNode, scope, scopes, doc) { var bindings = extractTextBindings(textNode, doc); if (bindings.length) { executeBindings(bindings, scope, scopes); } } function scanExpr(value) { var tokens = []; if (hasExpr(value)) { //抽取{{ }} 里面的语句,并以它们为定界符,拆分原来的文本 do { value = value.replace(regOpenTag, function(a, b) { if (b) { tokens.push({ value: b, expr: false }); } return ""; }); value = value.replace(regCloseTag, function(a, b) { if (b) { var filters = [] if (b.indexOf("|") > 0) { b = b.replace(/\|\s*(\w+)\s*(\([^)]+\))?/g, function(c, d, e) { filters.push(d + e) return "" }); } tokens.push({ value: b, expr: true, filters: filters.length ? filters : void 0 }); } return ""; }); } while (hasExpr(value)); if (value) { tokens.push({ value: value, expr: false }); } } return tokens; } function scanAttr(el, scope, scopes, flags) { var bindings = []; for (var i = 0, attr; attr = el.attributes[i++]; ) { if (attr.specified) { var isBinding = false, remove = false; if (attr.name.indexOf(prefix) !== -1) {//如果是以指定前缀命名的 var type = attr.name.replace(prefix, ""); if (type.indexOf("-") > 0) { var args = type.split("-"); type = args.shift(); } remove = true; isBinding = typeof bindingHandlers[type] === "function"; } else if (bindingHandlers[attr.name] && hasExpr(attr.value)) { type = attr.name; //如果只是普通属性,但其值是个插值表达式 isBinding = true; } if (isBinding) { bindings.push({ type: type, args: args, element: el, node: attr, remove: remove, value: attr.nodeValue }); } if (!flags.newScope && type === "controller") {//更换作用域 var temp = avalon.models[attr.value]; if (typeof temp === "object" && temp !== scope) { scopes.unshift(scope); flags.newScope = scope = temp; } } } } executeBindings(bindings, scope, scopes, flags); } function executeBindings(bindings, scope, scopes, flags) { bindings.forEach(function(data) { bindingHandlers[data.type](data, scope, scopes, flags); if (data.remove) {//移除数据绑定,防止被二次解析 data.element.removeAttribute(data.node.name); } }); } function extractTextBindings(textNode, doc) { var bindings = [], tokens = scanExpr(textNode.nodeValue); if (tokens.length) { var fragment = doc.createDocumentFragment(); while (tokens.length) {//将文本转换为文本节点,并替换原来的文本节点 var token = tokens.shift(); var node = doc.createTextNode(token.value); if (token.expr) { bindings.push({ type: "text", node: node, element: textNode.parentNode, value: token.value, filters: token.filters }); //收集带有插值表达式的文本 } fragment.appendChild(node); } textNode.parentNode.replaceChild(fragment, textNode); } return bindings; } var model = $.model("app", { firstName: "xxx", lastName: "oooo", bool: false, array: [1, 2, 3, 4, 5, 6, 7, 8], select: "test1", color: "green", vehicle: ["car"], fullName: { set: function(val) { var array = val.split(" "); this.firstName = array[0] || ""; this.lastName = array[1] || ""; }, get: function() { return this.firstName + " " + this.lastName; } } }); $.model("son", { firstName: "yyyy" }); $.model("aaa", { firstName: "6666" }); scanTag(document.body, model, [], document); setTimeout(function() { model.firstName = "setTimeout"; }, 2000); setTimeout(function() { model.array.reverse() // console.log(obsevers.applastName.join("\r\n")) }, 3000); });