Giter VIP home page Giter VIP logo

avalon's People

Contributors

aimwhy avatar ascoders avatar bigqiang avatar bluedarker avatar clinyong avatar codetalks-new avatar csbun avatar dengzhizhi avatar dolymood avatar dsonet avatar exolution avatar ilife5 avatar imeditron avatar jsaaron avatar lihuanghe avatar lizhengqiang avatar michaeljayt avatar nagasun avatar pinghe avatar qiangtou avatar rockyzh avatar rubylouvre avatar shirlyfe avatar tubo70 avatar vincentlws avatar web3d avatar xxapp avatar yoshiyukisakura avatar zhiyan avatar zspcn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

avalon's Issues

isArrayLike

原来的isArrayLike出自jQuery,依赖两个比较复杂的辅助方法 getType与isWindow,性能够次

    function isArraylike(obj) {
        var length = obj.length,
                type = getType(obj)

        if (avalon.isWindow(obj)) {
            return false
        }

        if (obj.nodeType === 1 && length) {
            return true
        }

        return type === "array" || type !== "function" &&
                (length === 0 ||
                        typeof length === "number" && length > 0 && (length - 1) in obj)
    }

改成

    function isArrayLike(obj) {
        if (obj) {
            var n = obj.length
            if (+n === n && !(n % 1) && n >= 0) {
                if ({}.propertyIsEnumerable.call(obj, 'length')) {
                    return Array.isArray(obj) || typeof obj !== "function" && /^\s?function/.test(obj.item || obj.callee)
                }
                return true
            }
        }
        return false
    }

只让节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象通过

重构scanExpr

原来的scanExpr在IE10下有时解释出错,决定放弃正则,使用纯字符串方法处理。

//v0.84
  function scanExpr(value) {
        var tokens = [],
                left
        if (rexpr.test(value)) {
            do {
                value.replace(rexpr, function(a, b) {
                    left = RegExp.leftContext
                    value = RegExp.rightContext
                    if (left) {
                        tokens.push({
                            value: left,
                            expr: false
                        })
                    }
                    if (b) {
                        var leach = []
                        if (b.indexOf("|") > 0) { // 注意排除短路与
                            b = b.replace(rfilters, function(c, d, e) {
                                leach.push(d + (e || ""))
                                return c.charAt(0)
                            })
                        }
                        tokens.push({
                            value: b,
                            expr: true,
                            filters: leach.length ? leach : void 0
                        })

                    }
                    return ""
                });
            } while (value.indexOf(closeTag) > -1);

            if (value) {
                tokens.push({
                    value: value,
                    expr: false
                })
            }
        }

        return tokens
    }

新的实现:

function scanExpr(str) {
        var tokens = [], value, start = 0, stop
        if (rexpr.test(str)) {
            do {
                var stop = str.indexOf(openTag, start)
                if (stop == -1) {
                    break
                }
                value = str.slice(start, stop)
                if (value) {// {{ 左边的文本
                    tokens.push({
                        value: value,
                        expr: false
                    })
                }
                start = stop + openTag.length
                stop = str.indexOf(closeTag, start)
                if (stop == -1) {
                    break
                }
                value = str.slice(start, stop)
                if (value) {//{{ }} 之间的表达式
                    var leach = []
                    if (value.indexOf("|") > 0) { // 注意排除短路与
                        value = value.replace(rfilters, function(c, d, e) {
                            leach.push(d + (e || ""))
                            return c.charAt(0)
                        })
                    }
                    tokens.push({
                        value: value,
                        expr: true,
                        filters: leach.length ? leach : void 0
                    })
                }
                start = stop + closeTag.length;
            } while (1);
            value = str.slice(start);
            if (value) { //}} 右边的文本
                tokens.push({
                    value: value,
                    expr: false
                })
            }
        }
        return tokens
    }

avalon支持CSS3 keyframe动画结束事件了

你可以在JS中使用 avalon.bind(el, "animationed" , callback),框架就帮你做好适配
或直接在视图中使用ms-animationend绑定

CSS3 keyframe动画可运行于IE10, chrome4+, safari5+, firefox5+, opera12+

<!DOCTYPE html>
<html>
    <head>
        <title>by 司徒正美</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <script src="page.js"></script>
        <script type="text/javascript">


        </script>
        <style>

            .panels div:nth-child(1){
                background:green;
            }
            .panels div:nth-child(2){
                background:blue;
            }
            .panels div:nth-child(3){
                background:violet;
            }
            .panels  div:nth-child(4){
                background:red;
            }
            .parent{
                width:800px;
                height:400px;
                position: relative;
                 overflow: hidden;
            }
            .pt-perspective {
                position: relative;
                width: 100%;
                height: 100%;
                -webkit-perspective: 1200px;
                -moz-perspective: 1200px;
                perspective: 1200px;
            }

            .pt-page {
                width: 100%;
                height: 100%;
                position: absolute;
                top: 0;
                left: 0;
                visibility: hidden;
                overflow: hidden;
                -webkit-backface-visibility: hidden;
                -moz-backface-visibility: hidden;
                backface-visibility: hidden;
                -webkit-transform: translate3d(0, 0, 0);
                -moz-transform: translate3d(0, 0, 0);
                transform: translate3d(0, 0, 0);
                -webkit-transform-style: preserve-3d;
                -moz-transform-style: preserve-3d;
                transform-style: preserve-3d;
            }
            .pt-page-current {
                visibility: visible;
                z-index: 1;
            }
            .pt-page-moveToLeft {
                -webkit-animation: moveToLeft .6s ease both;
                -moz-animation: moveToLeft .6s ease both;
                animation: moveToLeft .6s ease both;

                -webkit-animation-fill-mode: backwards;
                -moz-animation-fill-mode: backwards;
                animation-fill-mode: backwards;
            }

            .pt-page-moveFromRight {
                -webkit-animation: moveFromRight .6s ease both;
                -moz-animation: moveFromRight .6s ease both;
                animation: moveFromRight .6s ease both;
                -webkit-animation-fill-mode: backwards;
                -moz-animation-fill-mode: backwards;
                animation-fill-mode: backwards;
            }

            @-webkit-keyframes moveToLeft {
                to { -webkit-transform: translateX(-100%); }
            }
            @-moz-keyframes moveToLeft {
                to { -moz-transform: translateX(-100%); }
            }
            @keyframes moveToLeft {
                to { transform: translateX(-100%); }
            }

            @-webkit-keyframes moveFromRight {
                from { -webkit-transform: translateX(100%); }
            }
            @-moz-keyframes moveFromRight {
                from { -moz-transform: translateX(100%); }
            }
            @keyframes moveFromRight {
                from { transform: translateX(100%); }
            }

        </style>
    </head>
    <body ms-controller="apphopeui">

        <div class="tabs" ms-each-elem="panels">
            <button type="button" ms-click="changePanelIndex">按钮{{$index}}</button>
        </div>
        <br/>
        <div class="parent">
            <div class="panels pt-perspective" ms-each-el="panels">
                <div class="pt-page" 
                    ms-class-pt-page-current="0 == $index" 
                    ms-animationend="removeClass" >
                    面板{{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}} 司徒正美  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                </div>
            </div>
        </div>
    </body>
</html>

JS中, 我们需要巧妙利用$watch方法取得前后两个要切换的页面的索引值

avalon.ready(function() {

     var outClass, inClass, pages, lock = 0, lastIndex = 0
    avalon.define("apphopeui", function(vm) {
        vm.panels = [0, 1, 2, 3]
        vm.currentPageIndex = 0;

        vm.changePanelIndex = function() {
             if (!lock) {
                        lock = 1
                        var index = this.$vmodel.$index;
                        if (lastIndex !== index) {
                            lastIndex = index
                            vm.currentPageIndex = index;
                        } else {
                            lock = 0
                        }
             }
        }
        vm.removeClass = function() {
                   lock++
                    var className = this.animClass;
                    if (className === outClass) {
                        this.classList.remove("pt-page-current")
                    }
                    var el = this
                    className.replace(/[\w-]+/g, function(c) {
                        el.classList.remove(c)
                    })

                    if (lock === 3) {
                        lock = 0;
                    }
        }
        vm.$watch("currentPageIndex", function(next, curr) {
                     if (next > curr) {
                        outClass = 'pt-page-moveToLeft';
                         inClass = 'pt-page-moveFromRight';
                    } else {
                          inClass = 'pt-page-moveFromRight';
                         outClass = 'pt-page-moveToLeft';
                    }
                    var currPage = pages[curr]
                    var nextPage = pages[next]

                    currPage.animClass = outClass;
                    outClass.replace(/[\w-]+/g, function(c) {
                        currPage.classList.add(c)
                    })

                    currPage.classList.add("pt-page-current")

                    nextPage.animClass = inClass
                    inClass.replace(/[\w-]+/g, function(c) {
                        nextPage.classList.add(c)
                    })
                    nextPage.classList.add("pt-page-current")
        })

    })

    avalon.scan()
    avalon.nextTick(function() {//因为页面只有一个切换板做模板,只有扫描后才动态生成四个
        pages = document.querySelectorAll(".panels>div")
    })
})



av性能问题

正妹,发现chrome 26.0.1410.64 m 版本下,创建的vm数组有太多元素的话会,打开新的tab页时会导致浏览器崩溃。

ms-include 有bug

文档扫描时你把···{{}}···这样的标签全部给删光了,但是在<script type="text/avalon">这种标签应该直接Pass,直到include的时候才进行初始化。

比如

    <script type="text/avalon" id="index">
        <div ms-controller="index">
            <h2>{{message}}</h2>
            <input ms-model="message">
        </div>
    </script>
        var model = avalon.define("index", function(vm){
            vm.message = "index";
        }); 

通过

<div  ms-include="'index'"></div>

进行引入

在Chrome中出现的DOM是

<div>
        <div>
            <h2>message</h2>
            <input>
        </div>
    </div>

IE6-8是

<div>
        <div>
            <h2>index</h2>
            <input>
        </div>
    </div>

貌似IE是正确的,但是当我在input 中进行输入时,<h2>标签里面的内容没有发生任何改变。

$watch 兼容性问题

vm.$watch('name',function(a,b){console.log(a,b)}); 


IE7报SCRIPT16389: 未指明的错误。 

HTML插值绑定有BUG

photo.js

vm.monument = [{title:"",descriptions:""}];//结构为数组型,内Object数量会变动(但是这个并不是BUG的来源),descriptions是HTML格式的数据

photo.html

<div ms-each-md="monument" class="row">
  <div class="span4">
    <h2>{{md.title}}</h2>
    <p ms-visible="md.ishowDetail" ms-html="md.descriptions"></p> <!-- 使用ms-html -->
    <p ms-visible="md.ishowDetail">{{md.descriptions|html}}</p> <!-- 直接插入 -->
  </div>
</div>

初次显示很OK,但是当vm.monument进行变动时:

vm.monument = [{title:"",descriptions:""},{title:"",descriptions:""}];

如果使用{{md.descriptions|html}} 会抛出异常:

Uncaught Error: NotFoundError: DOM Exception 8 avalon.js:1575
(anonymous function) avalon.js:1575
updateView avalon.js:1285
notifySubscribers avalon.js:1041
accessor avalon.js:881
updateViewModel avalon.js:803
Collection.collection.set avalon.js:1894
updateViewModel avalon.js:798
accessor avalon.js:871
routerHash photo.js:376
avalon.Router.navigate avalon.router.js:375
self.checkUrl avalon.router.js:124

ms-each 嵌套循环和$last bug

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title></head>
<body>
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="news_table" style="border-collapse: collapse;">
                                    <thead>
                                        <tr id="news_head">
                                            <td>
                                                <div>
                                                    栏目1</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目2</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目3</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目4</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                        </tr>
                                    </thead>
                                    <tbody ms-controller="market.news" ms-each-item="news">
                                        <tr ms-class="last_row:$last" ms-each-data="item">
                                            <td>
                                                {{data.Title|truncate(14,"……")}}<p class="news_date">
                                                    {{data.StartDate|truncate(9,"")}} 至 {{data.EndDate|truncate(9,"")}} /$last</p>
                                            </td>                                            
                                        </tr>                                        
                                    </tbody>
                                </table>

<script type="text/javascript">
var $info = avalon.define("market.news", function ($v) {
                $v.news = [];
            });
            $info.news.push([{
                Title:1,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            },
            {
                Title: 2,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },
            {
                Title: 3,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },{
                Title:4,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            }]);

$info.news.push([{
                Title:1,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            },
            {
                Title: 2,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },
            {
                Title: 3,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },{
                Title:4,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            }]);
            avalon.scan();
</script>
</bodu>
</html>

异步赋值失败

var model = avalon.define("showBox", function(vm) {

    vm.array = {};
    /* 异步赋值失败 */
    setTimeout(function(){
        var data = [{"Enable":"1","Index":"0","Name":"标题"}];
        vm.array = data;
    })
});

赋值后获取不到任何属性
去掉setTimeout就可以正常使用。

影响到ajax获取数据的赋值操作

计算属性的$watch不触发BUG

这是杭州-东灵提的BUG

<!DOCTYPE HTML>
<html id="html">
<head>
  <meta charset="utf-8">
  <title>测试用例-yuewang</title>
</head>
<body>
  <div ms-controller="test">
    <button ms-click="one">测试1</button>
    <button ms-click="two">测试2</button>
    <button ms-click="one">测试3</button>
    <br>test1: {{test1}}
    <br>test2: {{test2}}
    <br>依次点击测试1->测试2->测试3,会发现watch事件没有触发
  </div>

  <script src="avalon.mobile.js"></script>
  <script>
  // 这里的逻辑是test2的数据来自test1,test1的数据来自test0
 var model = avalon.define("test", function(vm) {
    vm.test0 = false;//false, false false   --> true true true
    vm.test1 = {
      set: function(val) {
        this.test0 = val;
        console.log(this.test0)
      },
      get: function() {
        return this.test0;
      }
    };
    vm.test2 = false;
    vm.$watch('test1', function(val) {
      console.log(222);
      vm.test2 = val;
    });
    vm.one = function() {
      console.log(111);
      vm.test1 = !vm.test1;
      console.log(333);
    };
    vm.two = function() {
      vm.test0 = !vm.test0;

    };
  });
  </script>
</body>
</html>

没修之前,最后结果是true, false,现在好了,为 true, true

理解 $scope 和 $json

  1. vm.$json ==> ( vm.normal || vm.pure || vm.source || vm.static || vm.original || vm.native || vm.proto ) 是否更好理解?
  2. 临时的vm 是不是也有 this.$scope.$json呢?
  3. 在dom中需要自定义 ms-each-el 用el来引用这没问题
    但在绑定事件里面,能不能用this.$scope.el ==> this.vmthis 来脱离和自定义名字el的关系?
  4. this.vm ==> this.$scope 让每一层的vm 都还是叫做 vm 是否更好?
  5. 最后,我这样子想 有什么问题吗?

ms-if很严重的BUG

版本 v0.73

在ms-each中,如果配合ms-if,由于元素未被PUSH入DOM中,replace无效。

Uncaught Error: NotFoundError: DOM Exception 8     avalon.js:1529

ms-data绑定bug

avalon 0.9 独立版 by 司徒正美 2013.7.20

ms-data-xxx="/mod/list/"

当值中最后为 / 时 报错

代码:

<!DOCTYPE HTML>
<html id="html">
    <head>
        <meta charset="utf-8">
        <title>测试用例</title>
    </head>
    <body>
        <div ms-controller="test">
           <input type="text" ms-duplex="name" ms-data-path="/xxx/as/">
        </div>

        <script src="avalon.js"></script>
        <script>


            avalon.define("test", function(vm) {

               vm.name='';

                vm.$watch('name',function(a,b){console.log(a,b)});

            });
            avalon.scan();
        </script>
    </body>
</html>

全新的ms-with绑定,用于遍历对象

语法为 ms-with="obj" 子元素里面用$key, $val分别引用键名,键值
例子:

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type='text/javascript' src="avalon.js"></script>
        <script>
            var a = avalon.define("xxx", function(vm) {
                vm.obj = {
                    aaa: "xxx",
                    bbb: "yyy",
                    ccc: "zzz"
                }
                vm.first = "司徒正美"
            })
            setTimeout(function() {
                a.obj.aaa = "7777777777"
                a.first = "清风火忌"
            }, 1000)
            setTimeout(function() {
                a.obj.bbb = "8888888"
            }, 3000)
        </script>
    </head>
    <body ms-controller="xxx">
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
        <hr/>
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
        <hr/>
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
    </body>
</html>

require 冲突bug

<!DOCTYPE html>
<html>

<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="jquery-2.0.3.min.js" type="text/javascript"></script>
    <script src="avalon.js" type="text/javascript"></script>
    <script src="main.js" type="text/javascript"></script>

</head>
<body>
    <div ms-controller="main" ms-click="click">
        <p>
            <a href="#" >点击我</a>
        </p>
    </div>
    <script type="text/javascript">
    $.ajaxSetup({
        headers:{ajaxRequest:true},
        beforeSend:function(o){
            avalon.log(typeof o)
            avalon.log(typeof o.id)
        },
        complete:function(data){
            avalon.log('ajax 成功执行啦,阿门!')
        }
    })
    $('body').bind("click",function(e){
            alert("document");
            avalon.log(typeof e.target.$vmodel)
            $.post('./h.js',{},function(res){
                avalon.log(typeof res)
            })
        });

    avalon.require('jquery',function(){
         avalon.log('加载jq了啊……')
    })

    </script>
</body>
</html>

IE8无法运行

很奇怪的情况,在IEtest下,IE6、7、8并没有报错。
但是在XP的 原生IE8 下error了,错误信息如下(只是单纯引入文件就报错,v0.8.3):

网页错误详细信息

用户代理: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E)
时间戳: Sun, 7 Jul 2013 07:28:28 UTC


消息: 缺少 ')'
行: 79
字符: 5
代码: 0
URI: file:///E:/Gaubee/develop/avalon/avalon.js

移除elem.canHaveChildren === false判定

在IE中,它有一个叫canHaveChildren的属性,对于半闭合的元素类型,如HR,IMG, BR, INPUT等返回false,但在IE9+起,它都统统返回true,因此没有意义了,移除此判定!

avalon.draggable有关滚动条的处理

if(p.scroll)
        {
            s = this.getScroll(curX, curY);
            if((s[0] != 0) || (s[1] != 0))
            {
                window.scrollTo(window.scrollX + s[0], window.scrollY + s[1]);
            }
        }

    this.getScroll = function(pX, pY)
    {
        //read window variables
        var sX = window.scrollX;
        var sY = window.scrollY;

        var wX = window.innerWidth;
        var wY = window.innerHeight;

        //set contants      
        var scroll_amount = 10; //how many pixels to scroll
        var scroll_sensitivity = 100; //how many pixels from border to start scrolling from.

        var delX = 0;
        var delY = 0;       

        //process vertical y scroll
        if(pY - sY < scroll_sensitivity)
        {
            delY = -scroll_amount;
        }
        else
        if((sY + wY) - pY < scroll_sensitivity)
        {
            delY = scroll_amount;
        }

        //process horizontal x scroll
        if(pX - sX < scroll_sensitivity)
        {
            delX = -scroll_amount;
        }
        else
        if((sX + wX) - pX < scroll_sensitivity)
        {
            delX = scroll_amount;
        }

        return [delX, delY]
    }
http://www.gotproject.com/blog/post2.html

tr visible colspan miss

<title></title> <script src="avalon.js" type="text/javascript"></script> <style type="text/css"> .grid{ border:1 solid #000;
}
</style>
A B C
$index
<script type="text/javascript"> var $a = avalon; $a.ready(function(){
    var $vm = $a.define('grid',function(vm){
        vm.data = [
            {show:true},
            {show:false},
        ]
    });
    setTimeout(function(){
        $vm.data[1].show = true;
    },2000);
    $a.scan();
});

</script>

ms-visible 可以支持简单动画吗?

ms-visible的时候能不能加些动画?
比如:ms-visible-500="show"
vm.show = true;
效果类似 $(dom).show(500);
vm.show = false;
效果类似 $(dom).hide(500);

firefox 19-20.0.1 ms-visible tr colspan miss

<!DOCTYPE html>
<html>

<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="avalon.js" type="text/javascript"></script>
    <style type="text/css">
    .grid{
        border:1 solid #000;

    }
    </style>
</head> 
<body>
    <div ms-controller="grid" >
        <table cellpadding="0" border="1" cellspacing="0">
            <thead>
                <tr>
                    <td>A</td>
                    <td>B</td>
                    <td>C</td>
                </tr>
            </thead>
            <tbody ms-each-item="data">
                <tr ms-visible="item.show">
                    <td colspan="3">
                        $index
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <script type="text/javascript">
    var $a = avalon;
    $a.ready(function(){

        var $vm = $a.define('grid',function(vm){
            vm.data = [
                {show:true},
                {show:false},
            ]
        });
        setTimeout(function(){
            $vm.data[1].show = true;
        },2000);
        $a.scan();
    });

    </script>
</body>
</html>

利用ms-include与监控数组实现一个树

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <script>
            avalon.define("tree", function(vm) {
                vm.tree = [
                    {text: "aaa", subtree: [
                            {text: 1111, subtree: []},
                            {text: 2222, subtree: [
                                    {text: 777, subtree: []}
                                ]},
                            {text: 3333, subtree: [
                                    {text: 8888, subtree: []},
                                    {text: 999, subtree: []}
                                ]}
                        ]},
                    {text: "bbb", subtree: [
                            {text: 4444, subtree: []},
                            {text: 5555, subtree: []},
                            {text: 6666, subtree: []}
                        ]},
                    {text: "ccc", subtree: []},
                    {text: "ddd", subtree: []},
                    {text: "eee", subtree: []},
                    {text: "fff", subtree: []}
                ]

            })
        </script>
    </head>
    <body ms-controller="tree">
        <script type="avalon" id="tmpl">
            <ul ms-each-el="el.subtree">
            <li>{{el.text}}<div ms-include="'tmpl'" ms-visible="el.subtree.length" ></div></li>
            </ul>
        </script>
        <ul ms-each-el="tree">
            <li>{{el.text}}<div ms-include="'tmpl'" ms-visible="el.subtree.length" ></div></li>
        </ul>
    </body>
</html>

关键是如何递归调用自己

ms-class绑定的新语法

之前ms-class-class1-class2-class3="prop",每次只能绑定一个类名,这个类名为"class1-class2-class3",prop为VM的属性或表达式,并且类名不能为大写(因为属性名一律被小写化了)

现在引入一种新语法,为

ms-class="aaa bbb ccc: true"  ---> addClass("aaa bbb ccc")
ms-class="xx yyy  zzzz"       ---> addClass("xx yyy zzzz")
ms-class="xxx yyy{{index}}"  --> vm.index = 200 , addClass("xxx yyy200")
ms-class="xxx yyy{{index}}: 0"--> vm.index = 200 , removeClass("xxx yyy200")
ms-class="aaa bbb: prop > 100" --> vm.prop = 1,   , removeClass("aaa bbb")
ms-class="{{1?'aaa': 'bbb'}}"  ---> addClass("aaa")
ms-class="{{1?'aaa aaaa': 'bbb bbbb bbbbb'}}" ---> addClass("aaa aaaa")

当ms-class后面不接东西,那么就进行新语法分支
属性值 变划分为两部分,以冒号分开,后一部分是可选 的
如果没有后一部分,为addClass操作,
有则根据后面部分的真假决定addClass 还是removeClass

改回0.82的scanNodes

在0.85中,发现一个BUG,可能是重构了ms-each绑定引发的,导致循环生成节点在旧式IE下卡死。于是使用nextTick进行解围。见下:

//0.85
    function scanNodes(parent, vmodels, callback) {
        var tags = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            if (node.nodeType === 1) {
                tags.push(node)
            } else if (node.nodeType === 3) {
                scanText(node, vmodels)
            }
        }
        callback && callback();
        tags.forEach(function(node) {
            if (W3C) {
                scanTag(node, vmodels) //扫描元素节点
            } else {
                avalon.nextTick(function() {
                    scanTag(node, vmodels) //扫描元素节点
                })
            }
        })
    }

随着对ms-each不断优化,决定换回0.82的版本,因为它的体积更小

//0.82
    function scanNodes(parent, vmodels, callback) {
        var nodes = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            nodes.push(node);
        }
        callback && callback();
        for (var i = 0; node = nodes[i++]; ) {
            if (node.nodeType === 1) {
                scanTag(node, vmodels) //扫描元素节点
            } else if (node.nodeType === 3) {
                scanText(node, vmodels) //扫描文本节点
            }
        }
    }

由于在新的ms-each绑定中不需要回调,因此第三个参数可略去

//0.9 pre
    function scanNodes(parent, vmodels) {
        var nodes = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            nodes.push(node);
        }
        for (var i = 0; node = nodes[i++]; ) {
            if (node.nodeType === 1) {
                scanTag(node, vmodels) //扫描元素节点
            } else if (node.nodeType === 3) {
                scanText(node, vmodels) //扫描文本节点
            }
        }
    }

源代码架构

你好,在阅读您的代码时遇到一些障碍,在阅读您的博客后,已经了解了基本**,能否提供下源代码的架构图,或者更详细些的资料,谢谢

ms-each生成结果有问题

我的测试代码很简单:

<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<ol ms-controller="function">
    <li ms-each-item="numbers">{{item}}</li>
</ol>

</div>
<script>
avalon.ready(function()
{
    avalon.define("function", function(vm) {
        vm.numbers = [1,2,3,4]
    });
    avalon.scan();
});
</script>
</body>
</html>

但是结果不是我想要的生成4行,而是只有一行,并且数字都写到一块去了。

image

不知道是为什么。我使用了最新的git版本试了也不行,是我哪里写得不对吗?

全新的parser

为了解决soom提出的BUG, 由于短路与或短路或导致一开始无法进入某些分支,就无法取得其依赖关系。另外,之前的使用with,效率有点低。

<!DOCTYPE HTML>
<html id="html">
<head>
  <meta charset="utf-8">
  <title>测试用例</title>
</head>
<body>
  <div ms-controller="test">
    <button ms-click="one">测试1</button>
    <button ms-click="two">测试2</button>
    <br>test1: {{test1}}
    <br>test2: {{test2}}
    <br>上边两个变量变true的时候,下面的表达式是false的
    <br>test1 && test2, result: {{test1 && test2}} and {{test2 && test1}}
    <br>ps: {{test2 || test1}}
  </div>

  <script src="avalon.mobile.js"></script>
  <script>
  avalon.define("test", function(vm) {
    vm.test1 = false;
    vm.test2 = false;
    vm.one = function() {
      vm.test1 = false;
      vm.test2 = false;

      vm.test1 = true;
      vm.test2 = true;
    };
    vm.two = function() {
      vm.test1 = false;
      vm.test2 = false;

      vm.test2 = true;
      vm.test1 = true;
    };
  });
  avalon.scan();
  </script>
</body>
</html>

新的parser, 设法取得里面的变量,然后把所有赋值语句放在前面,从而解决这问题

    var KEYWORDS =
            // 关键字
            'break,case,catch,continue,debugger,default,delete,do,else,false'
            + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
            + ',throw,true,try,typeof,var,void,while,with'

            // 保留字
            + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
            + ',final,float,goto,implements,import,int,interface,long,native'
            + ',package,private,protected,public,short,static,super,synchronized'
            + ',throws,transient,volatile'

            // ECMA 5 - use strict
            + ',arguments,let,yield'

            + ',undefined';
    var REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g;
    var SPLIT_RE = /[^\w$]+/g;
    var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
    var NUMBER_RE = /\b\d[^,]*/g;
    var BOUNDARY_RE = /^,+|,+$/g;
    var getVariables = function(code) {
        code = code
                .replace(REMOVE_RE, '')

                .replace(SPLIT_RE, ',')


                .replace(KEYWORDS_RE, '')
                .replace(NUMBER_RE, '')
                .replace(BOUNDARY_RE, '');

        code = code ? code.split(/,+/) : [];

        return code;
    };


    function addScope(vars, scope, index) {
        var ret = [], prefix = " = vm" + index + "."
        for (var i = vars.length, name; name = vars[--i]; ) {
            name = vars[i]
            if (scope.hasOwnProperty(name)) {
                ret.push(name + prefix + name)
                vars.splice(i, 1)
            }
        }
        return ret

    }
    function getValueFunction(code, scopes) {
        var vars = getVariables(code), ret, variables = [], uniq = {};
        vars = vars.filter(function(el){
            if(!uniq[el]){
                uniq[el] = 1
                return true;
            }
            return false
        })
        var args = []
        for (var i = 0, scope, n = scopes.length; i < n; i++) {
            if (vars.length) {
                args.push("vm" + i)
                variables.push.apply(variables, addScope(vars, scopes[i], i))
            }
        }
        var pre = variables.join(", ")
        if (pre) {
            pre = "var " + pre
        }
        args.push(pre + "\nreturn " +code)
        return Function.apply(Function,args)
    }
    var obj = {test1:23, test2:"sdfsd"}

 var fn = getValueFunction("test1 && test2 ",[obj])  

 console.log(fn+"")

 fn(obj)

生成的求值函数为:

function anonymous(vm0) {
      var test2 = vm0.test2, test1 = vm0.test1
      return test1 && test2 
}

如果存在过滤器,那么应该生成

    function anonymous(vm0, filters123143213) {
        var test1= vm0.test1
        var ret123456 = test1
        if (filters123143213.html) {
            ret123456 = filters123143213.html(ret123456)
        }
        return ret123456
    }

ms-class 一个不合预期的地方

测试

    <div ms-controller="test">
        <!-- 例1 -->
        <div ms-class="a: isA"></div>
        <!-- 例2 -->
        <div ms-class="{{isA ? 'a' : 'b'}}"></div>  
    </div>
    <script>
        var test = avalon.define('test', function(vm){
            vm.isA = true
        })
        setTimeout(function() {
            test.isA = false
        }, 1000)        
    </script>

结果,例1符合预期;例2预期得到 class="b" ,结果是 class="a b"

类型转换时存在Bug

源代码

    var monumentsVModel = avalon.define("monumentsDetail", [], function(vm) {
        vm.monument = false;
        vm.title = "相册集";
        vm.allMonument = res;
    });

这里vm.monument是在路由的时候进行绑定,开始默认为false,意思是 空数据集,相对性的有ms-visible="monumentDetail" 来判定是否要显示模块。

在路由判定绑定数据后,vm.monument 转为嵌套式的Array类型,数据示例如下:

{
        name: "普罗旺斯",
        details: [{
                title: "城市简介",
                descriptions: "普罗旺斯(Provence)\n是罗马帝国的一个行省,英文简称PACA,现为法国东南部的一个地区,毗邻地中海,和意大利接壤。从阿尔卑斯山经里昂南流的隆河(Rhone),在普罗旺斯附近分为两大支流,然后注入地中海。普罗旺斯是世界闻名的薰衣草故乡、旅游胜地。\n普罗旺斯是欧洲的“骑士之城”,位于法国东南部,[1]毗邻地中海和意大利,从地中海沿岸延伸到内陆的丘陵地区,中间有大河“Rhone”流过。自古就以靓丽的阳光和蔚蓝的天空,迷人的地中海和心醉的薰衣草,令世人惊艳。是中世纪重要文学体裁骑士抒情诗的发源地。历史上的普罗旺斯地域范围变化很大,古罗马时期普罗旺斯行省北至阿尔卑斯山,南抵比利牛斯山脉,包括整个法国南部。18世纪末法国大革命时,普罗旺斯成为5个行政省份之一。到了1960年代,法国被重新划分为22个大区,普罗旺斯属于普罗旺斯-阿尔卑斯-蓝色海岸大区。\n浪漫之城如果有人说普罗旺斯是彻底的浪漫,大概也不过分,因为这里除了很久流传的浪漫爱情传奇,还有因《马赛曲》而闻名的马赛,因《基督山伯爵》而为众人皆知的依夫岛,还有儒雅的大学城艾克斯和阿维尼翁,回味久远的中世纪山庄,街边舒适的小咖啡馆。\n普罗旺斯历史上有包括塞尚、凡高、莫奈、毕加索、夏卡尔等人均在普罗旺斯展开艺术生命的新阶段,蔚蓝海岸的享乐主义风气,也吸引了美国作家费兹杰罗、英国作家劳伦斯、科学家赫胥黎、德国诗人尼采等人前来朝圣,当然,还囊括了《山居岁月》,将普罗旺斯推向巅峰的彼得·梅尔。\n"
            }, {
                title: "地理位置",
                descriptions: "普罗旺斯位于法国南部,从诞生之日起,就谨慎地保守着她的秘密,直到英国人彼得·梅尔的到来,普罗旺斯许久以来独特生活风格的面纱才渐渐揭开。在梅尔的笔下“普罗旺斯”已不再是一个单纯的地域名称,更代表了一种简单无忧、轻松慵懒的生活方式,一种宠辱不惊,闲看庭前花开花落,去留无意,漫随天外云卷云舒的闲适意境。如果旅行是为了摆脱生活的桎梏,普罗旺斯会让你忘掉一切。\n整个普罗旺斯地区因极富变化而拥有不同寻常的魅力——天气阴晴不定,时而暖风和煦,时而海风狂野,地势跌宕起伏,平原广阔,峰岭险峻,寂寞的峡谷,苍凉的古堡,蜿蜒的山脉和活泼的都会,全都在这片法国的大地上演绎万种风情。7~8月间的薰衣草迎风绽放,浓艳的色彩装饰翠绿的山谷,微微辛辣的香味混合着被晒焦的青草芬芳,交织成法国南部最令人难忘的气息。\n"
            }, {
                title: "气候环境",
                descriptions: "气候状况\n整个普罗旺斯地区因极富变化而拥有不同寻常的魅力--天气阴晴不定,时而暖风和煦,时而冷风狂野,地势跌宕起伏,平原广阔,峰岭险峻,寂寞的峡谷,苍凉的古堡,蜿蜒的山脉和活泼的都会--全都在这片法国的大地上演绎万种风情。7-8月间的薰衣草迎风绽放,浓艳的色彩装饰翠绿的山谷,微微辛辣的香味混合着被晒焦的青草芬芳,交织成法国南部最令人难忘的气息。在美食方面,普罗旺斯最大的优势在于农产品丰富,新鲜的蔬菜水果、橄榄油、大蒜、海鲜、香料组合成食客的天堂。\n普罗旺斯地区属地中海气候,夏季干燥,冬季温和,每年日照达到300天以上。夏季通常为7月到9月,白天气温一般都在30度以上。冬季(12月-2月)气温通常在10-15度左右。尽管普罗旺斯南北气候有所差异,但总体上来说常年适合旅游,尤其是春夏秋三季的旺季。普罗旺斯冬季的风非常著名,尤其是冬季从阿尔卑斯山脉吹来的风,顺着Rhone山谷畅通无阻,有时风速甚至可以达到每小时100公里。\n地理状况\n普罗旺斯(Provence)位于法国南部。最初的普罗旺斯北起阿尔卑斯山,南到比利牛斯山脉,包括法国的整个南部区域。罗马帝国时期,普罗旺斯就被列为其所属的省份。18世纪末大革命时期,法国被分成5个不同的行政省份,普罗旺斯是其中之一。到了20世纪60年代,行政省份又被重新组合划分成22个大区,于是有了现在的普罗旺斯-阿尔卑斯大区。在温文尔雅的大学名城艾克斯、教皇之城亚维农的前后,还有那些逃过世纪变迁的中世纪小村落和古老的山镇。\n普罗旺斯出品优质葡萄美酒,其中20$Cts%$为高级和顶级酒种。由于地中海阳光充足,普罗旺斯的葡萄含有较多的糖分,这些糖转变为酒精,使普罗旺斯酒的酒精度比北方的酒高出2度。略带橙黄色的干桃红酒是最具特色的。常见的红酒有:Cotes de Provence, Coteaux d $Cts'$Aix en Provence, Bandol。普罗旺斯地理位置  普罗旺斯地理位置\n从地中海沿岸延伸到内陆的丘陵地区,中间有大河“Rhone”流过,很多历史城镇,自古就以靓丽的阳光和蔚蓝的天空,迷人的地中海和心醉的薰衣草,令世人惊艳。\n薰衣草的传说\n话说普罗旺斯的村里有个少女,一个人独自在寒冷的山中采著含苞待放的花朵,但是却遇到了一位来自远方但受伤的旅人,少女一看到这位青年,整颗心便被他那风度翩翩的笑容给俘虏了!\n於是少女便将他请到家中,也不管家人的反对,坚持要照顾他直到痊愈,而过了几天後,青年旅人的伤也已经康复,但两人的恋情却急速蔓延,已经到了难分难舍的地步。\n不久後的某日,青年旅人向少女告别离去,而正处於热恋中的少女却坚持要随青年离去,虽然亲人们极力挽留,但她还是坚持要和青年一起到开满玫瑰花的故乡!就在少女临走的前一刻,村子里的老太太给了她一束薰衣草,要她用这束薰衣草来试探青年旅人的真心,因为...传说薰衣草的香气能让不洁之物现形.\n正当旅人牵起她的手准备远行时,少女便将藏在大衣里的薰衣草丢掷在青年的身上,没想到,青年的身上发出一阵紫色的轻烟之後,就随著风烟消云散了!而少女在山谷中还彷佛隐隐的听到青年爽朗的笑声,就这样,留下了少女一人孤形影单......\n没过多久,少女竟也不见踪影,只留下一句“其实我就是你想远行的心”.\n有人认为她和青年一样幻化成轻烟消失在山谷中,也有人说,她循著玫瑰花香去寻找青年了......\n"
            }, {
                title: "风景名胜",
                descriptions: "薰衣草观赏:\n吕贝隆山区(Luberon)Sault修道院的花田是该区最著名的薰衣草观赏地,也是《山居岁月》一书的故事背大片的薰衣草  大片的薰衣草\n景,号称全法国最美丽的山谷之一。山上有一座12世纪的修道院,塞南克修道院前方有一大片的薰衣草花田,是由院里的修道士栽种的,有不同颜色的薰衣草。\n施米雅那山区(Simiane-la-Rotonde)的施米雅那是一座极具特色的山城,山顶矗立着一座建于12至13世纪的城堡罗通德,环绕着一大片的薰衣草花田。站在施米雅那城镇里,随处可见到紫色花田,无边无际地蔓延。\n周边小城游:\nLuberon(吕贝隆)是沃克里兹省的南部地区,彼得·梅尔的《普罗旺斯的一年》中所写的地方就在这里。Roussillon(鲁西庸)是座彩色的村庄,桃红、鲜橙、明黄的房子像天使的玩具,随意散落在村中。\nGordes是座岩石山庄,Gordes村里还有一座薰衣草博物馆,门口一辆老式的薰衣草压油机。博物馆里展示了薰衣草农田里的各种用具。\n艾克斯市是画家保尔·塞尚的故乡,自中世纪就是一座大学城,也是著名的“泉城”。这里是罗马普罗旺斯的古都。该市以独特的烹饪、玫瑰红葡萄酒,以及特贝的语言--普罗旺斯方言闻名。在奥郎日,你可以坐在罗马时代的圆形露天剧场看戏;在阿尔,你可以坐在咖啡厅里消磨一个下午。这里每年7月,还会举办一个很时髦石头城的国际摄影节,在石头古巷和小广场上,展览当今缔造潮流的大摄影师和风流人物。\n波城古堡游:\n波城古堡是指位于亚耳附近地区的波城·普罗旺斯(Les Baux de Provence)的古城塞遗迹。波城这里曾经是被诗人米斯特拉称为“鹫族”的英勇的波城一族驻守的城塞,后来经历了无数次战波城古堡  波城古堡\n争硝烟的洗礼,波城古堡于路易13世在位期间毁于战火,现在保留的是当年的古堡废墟可供游人参观。\n波城古堡的入口处是波城历史博物馆(Musée d'Histoire des Baux),展示城堡当年鼎盛时期的历史资料与文物,站在城堡顶端,环顾四方,亚耳古城等周边风光尽收眼底,据说北方的地狱谷(Va d'Enfer)是触发但丁撰写《神曲·地狱篇》的地方。\n"
            }
        ]
    }

这里相对定的HTML代码如下(夹杂着测试代码):

        <div ms-each-md="monument.details" class="row">
          <span>{{monument.name}}</span>
          <div class="span4">
            <h2>{{monument.details}}</h2>
            <h2>{{$index}}</h2>
            <h2>{{md}}</h2>
            <h2>{{md.title}}</h2>
            <p>{{md.descriptions}}</p>
          </div>
        </div>

如上,单层的 {{monument.name}};{{monument.details}}可以正常显示,但是 {{md.title}};{{md.descriptions}} 无法正常显示。

在已经变成Array的基础上,后面再次绑定其他数据也不行。

如果将 vm.monument = res[0]; 将数据的json结构定下来后(不是空Array),后期就可以正常显示。

不知道是不是我书写违规,这种写法我也是在DEMO里面学的,就是ms-each会出问题。其他情况获取单层的数据很正常。

ViewModel中vm作用域bug

描述可能不准确,具体看代码
html 代码:

<form ms-controller="login">        
  <input type="text" ms-model="user" ms-on-blur="validate.user"/>      
</form>

js 代码:

 var login=avalon.define('login',function(vm){
            vm.user='';
            vm.validate={
                user:function(){
                    avalon.log(vm.user);//取不到值
                    avalon.log(login.user);//正常
                }                
            }  
 })

avalon v4的早期实现

avalon原来是mass Framework的一个子模块,v4之前是采用knockout那样笨重的属性变方法的方式实现依赖收集与通知变更。v4时突发奇想,然后Object.defineProperties与VBS重载等于号,实现更优雅的依赖收集与通知变更。接口设计上从angular,js与rivets.js扒了不少好东西,形成现在的样子。现保存在这里,让大家看看这伟大历程

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.toLowerCase()]) {
            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);
   */
});

关于文档的建议

现在已经有不少文档了,不过感觉有些细节还是不太清楚。所以建议文档可以支持评论,这样可以让大家及时反馈。同时文档如果也放到github上可以让大家来修复。已经发现一些bug了,如include中,定义为tpl,但是引用的示例写为tml了。

同事提出的一个怪异现象

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>scope</title>
        <script src="avalon.js" type="text/javascript"></script>
        <script>

            avalon.ready(function() {
                var a = avalon.define("simple", function(vm) {

                    vm.obj = "" //如果是vm.obj = {}就不行
                });
                setTimeout(function() {

                    a.obj = {
                        aaa: "xxxxxxxxxx"
                    }

                }, 1000)


                avalon.scan();
            })
        </script>
    </head>
    <body>
        <fieldset ms-controller="simple">
            <legend>例子</legend>
            <div id="aaa">{{obj.aaa}}</div>
        </fieldset>

    </body>
</html>

让一个数组基于另一个数组进行排序

这是avalon v0.85另一个重大优化。当我们对 VM的数组进行排序时,要求DOM树上的节点也按这个顺序排列,这时就要用到这个算法了。原来比较慢,现在这个应该是最快的了,没有比它更快的了。

            var aaa = [1, 2, 3, 4, 5, 1]
            var bbb = [{v: 2}, {v: 3}, {v: 1}, {v: 1}, {v: 5}, {v: 4}]
            var swapTime = 0
            var isEqual = Object.is || function(x, y) {//主要用于处理NaN 与 NaN 比较
                if (x === y) {
                    return x !== 0 || 1 / x === 1 / y;
                }
                return x !== x && y !== y;
            };
            for (var i = 0, n = bbb.length; i < n; i++) {
                var a = aaa[i];
                var b = bbb[i]
                var b = b && b.v ? b.v : b
                if (!isEqual(a, b)) {
                    console.log(++swapTime)
                    var index = getIndex(a, bbb, i);
                    var el = bbb.splice(index, 1)
                    bbb.splice(i, 0, el[0])
                }
            }
            function getIndex(a, bbb, start) {
                for (var i = start, n = bbb.length; i < n; i++) {
                    var b = bbb[i];
                    var check = b && b.v ? b.v : b
                    if (isEqual(a, check)) {
                        return i
                    }
                }
            }
            console.log(JSON.stringify(bbb))

在框架中,aaa为数据模型M中的数组,bbb为视图模型VM中的数组

数据仓库模块的模拟实现

简单的模拟代码:

var modelWarehouse = avalon.modelWarehouse = {
    watchers : {},
    set : function(dataName,data){
        this[dataName] = data;
        var watchersItem = this.watchers[dataName];
        console.log(watchersItem)
        if (watchersItem) {
            var watcherLen = watchersItem.length;
            for (var i = 0,watch; i < watcherLen; i+=1) {
                watch = watchersItem[i];
                if (avalon.models[watch.viewModel][watch.prototype]!=data) {
                    avalon.models[watch.viewModel][watch.prototype] = data;
                }
            };
        }
    },
    get : function(dataName){
        return this[dataName]||[];
    },
    watch : function(vmName,prototype,dataName){
        var watch = {
            viewModel:vmName,
            prototype:prototype
        };
        var watchersItem = this.watchers[dataName];
        if(watchersItem){
            for(var i=0;item = watchersItem[i];i+=1){
                if (item.viewModel === watch.viewModel&&item.prototype === watch.prototype) {
                    break;
                }
            }
            (i===watchersItem.length)&&(watchersItem[watchersItem.length] = watch);
        }else{
            this.watchers[dataName] = [watch];
        }
        return this[dataName]||[];
    }
};

初始化数据结构:

modelWarehouse.set("blogMessage",{name:"bangeel",other:{date:(new Date()).toString(),id:"2"}})
modelWarehouse.set("postsData",[]);//由于avalon对数组只进行单层的采样,所以对内部各个元素的结构使用动态的设定

使用方式:

avalon.define("postsController",function(vm){
    vm.title = "It's my blogs";
    vm.posts = modelWarehouse.watch("postsController","posts","postsData");
    vm.message = modelWarehouse.watch("postsController","message","blogMessage");
});

avalon.define("postsList",function(vm){
    vm.title = "blogs list";
    vm.posts = modelWarehouse.watch("postsList","posts","postsData");
});

avalon.scan();

更新单一的数据来源:

var postsData = [
    {
        title:"hello",
        date:"2012-12-07 18:38:37",
        content:"nothing.sorry!"
    },
    {
        title:"okkok",
        date:"2013-07-07 18:39:13",
        content:"nothing.sorry!"
    },
];
var blogMessage = {
    name:"Gaubee",
    other:{
        date:(new Date()).toString(),
        id:"3"
    }
}
setTimeout(function(){
    modelWarehouse.set("postsData",postsData);
    modelWarehouse.set("blogMessage",blogMessage);
},1000)

以上为数据管理模块,即数据仓库,保证数据源的一致性,并自动触发所有数据依赖的更新。
PS:跟Ember学习的概念

在更新数组时,不是采用替換,而是更新,造成数据不正确

<div ms-controller="test">
<ul ms-each-item="items">
    <li ms-click="edit1">{{item.name}}:{{item.choices}}</li>
</ul>
<a href="#" ms-click="change">change</a>
</div>
<script>
avalon.define('test', function(vm){
    vm.items = [{'name':'a1', 'choices':1}, 
        {'name':'a2', 'choices':2}];
    vm.edit1 = function(){
        console.log(this.$vmodel.item)
    }
    vm.change = function(){
        vm.items = [{'name':'a3', 'choices':3}, 
        {'name':'a4'}];

    }
});
</script>

以上是一个示例。我的想法是点击change之后,更新items数据。但是注意,更新后,有一个值只有 {'name':'a4'},它并没有包含 'choices'。但是发现虽然我没有设置choices属性,但是在更新后仍然带有这个属性。并且它是上一次对应序号的值留下来的,即它是a2的choices值。

大概看了一下代码,好象是updateViewModel是更新,不是替換造成的。

三元运算符+filter

<!DOCTYPE html>
<html>

<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="avalon.js" type="text/javascript"></script>    
</head> 
<body>
    <div ms-controller="grid" ms-each-item="data ">
        <p>{{item.a && item.b ? item.b / item.a : '' |percents}}</p>
    </div>
    <script type="text/javascript">
    var $a = avalon;
        $a.filters.percents = function(val){
return val + "&";
}
        var $vm = $a.define('grid',function(vm){
            vm.data = [
                {a:10,b:5},
                {a:'',b:''},
            ]
        });

        $a.scan();

    </script>
</body>
</html>

modelfactory

modelfactory中

notifySubscribers(accessor);
model.$events && model.$fire(name, value, old);

是否可以理解为 前者为通知顶层的改变 后者为触发自身的事件,那为什么顶层的改变不是直接绑定到这一层上呢?,就只用一个触发

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.