RubyLouvre / mass-Framework

一个模块化,面向大规模开发的javascript框架
MIT License
653 stars 535 forks source link

avalon v4的早期实现: #57

Open RubyLouvre opened 11 years ago

RubyLouvre commented 11 years ago
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);
});
RubyLouvre commented 11 years ago
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>scope</title>
        <style>
            .my-class{
                color: yellow;
                background:orange;
            }
            .your-class {
                line-height:3;
                width:200px;
                height:100px;
            }
            .his-class{
                text-indent:2em;
                font-size:14px;
            }
            .single{
                width:200px;
                height:100px;
                background:lightgreen;
            }
        </style>
    </head>
    <body>
        <p>First name: <input ms-model="firstName" /></p>
        <p>Last name: <input ms-model="lastName" ms-disabled='false' disabled='disabled' /></p>
        <h2>Hello, <input ms-event="change" ms-model="fullName">!</h2>
        <div>{{firstName +" | "+ lastName }}</div>
        <div>{{firstName | uppercase | aaa }}</div>
        <a href='{{2252}}/{{lastName}}' title='{{firstName}}'>这是链接</a>
        <div id="eee">Normal: {{1 + 8}}</div>
        <div ms-skip>Ignored: {{1 + 2}}</div>
        <hr/>
        <ul ms-each-el='array' id="array">
            <li>这是动态生成的{{el}}</li>
        </ul>
        <div ms-if="bool">sssss</div>
        <form name="myForm" >
            <input type="radio" ms-model="color" value="red">  <br/>
            <input type="radio" ms-model="color" value="green">  <br/>
            <input type="radio" ms-model="color" value="blue">  <br/>
            <input type="checkbox"  ms-model="vehicle" value="bike"><br>
            <input type="checkbox"  ms-model="vehicle" value="car"> <br>
            <input type="checkbox"  ms-model="vehicle" value="ship">
            <p> I have a {{vehicle}}</p>
            <tt>color = {{color}}</tt><br/>
        </form>
        <select ms-model="select">
            <option>
                test0
            </option>
            <option>
                test1
            </option>
            <option>
                test2
            </option>
        </select>{{select}}
        <div ms-class="{ 'my-class':true, 'your-class':true, 'his-class':true }">xxxxxxxxx</div>
        <script src="mass.js" type="text/javascript"></script>
        <script type="text/javascript">
            require("more/avalon2", function() {
            })
        </script>
    </body>
</html>
liuxf2010 commented 11 years ago

1019行:!stopScan[elem.tagName]

应该要toLowerCase()一下吧?