{"action":"created","issue":{"url":"https://api.github.com/repos/RubyLouvre/avalon/issues/956","labels_url":"https://api.github.com/repos/RubyLouvre/avalon/issues/956/labels{/name}","comments_url":"https://api.github.com/repos/RubyLouvre/avalon/issues/956/comments","events_url":"https://api.github.com/repos/RubyLouvre/avalon/issues/956/events","html_url":"https://github.com/RubyLouvre/avalon/issues/956","id":99017983,"number":956,"title":"双缓冲技术","user":{"login":"RubyLouvre","id":190846,"avatar_url":"https://avatars.githubusercontent.com/u/190846?v=3","gravatar_id":"","url":"https://api.github.com/users/RubyLouvre","html_url":"https://github.com/RubyLouvre","followers_url":"https://api.github.com/users/RubyLouvre/followers","following_url":"https://api.github.com/users/RubyLouvre/following{/other_user}","gists_url":"https://api.github.com/users/RubyLouvre/gists{/gist_id}","starred_url":"https://api.github.com/users/RubyLouvre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RubyLouvre/subscriptions","organizations_url":"https://api.github.com/users/RubyLouvre/orgs","repos_url":"https://api.github.com/users/RubyLouvre/repos","events_url":"https://api.github.com/users/RubyLouvre/events{/privacy}","received_events_url":"https://api.github.com/users/RubyLouvre/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":3,"created_at":"2015-08-04T17:23:05Z","updated_at":"2015-08-05T03:16:47Z","closed_at":null,"body":"avalon1.5一个重要技术升级是引进异步渲染。异步渲染在游戏界有一个更专业的名字，叫双缓冲。游戏界要刷新界面与我们刷新浏览器视图，面临的问题是一致的。视图是由许多存在套嵌关系的方块组成，它们每一个的改动，都可能引起reflow(其父节点，其父父节点的大小重新计算)，这是造成性能问题的关键。\r\n\r\n双缓冲技术的主要原理是：当一个动画争先显示时，程序又在改变它，前面的画面还没显示完，程序又要求重新绘制，这样屏幕就会不停闪烁。为了避免闪烁，可以使用双缓冲技术，将要处理的图片都放在内存中处理好过后，再将其显示到屏幕上。这样出来的就是完整的图像，不会出现闪烁现象。\r\n\r\nMVVM框架带来一个革命性的优化是，用户只操作VM就行了，视图由框架来同步更新。因此这个同步过程，我们就可以加入优化。比如说angular，就是靠用户手动调用$apply来驱动脏检测，只有数据不一致的地方，才会操作DOM。于是没有了aaa.innerHTML = \"xxx\"; aaa.innerHTML = \"xxx\"的愚蠢代码。\r\n\r\n可能说用户说我怎么可能会这样写呢，那是你因为用于jquery，看到它是在同一个循环中对某个节点执行多次相同的操作。\r\n\r\n再回过头来看avalon。avalon是基于事件驱动（通过Object.defineProperty劫持了对象的属性），当用户修改了某属性，就会立即同步视图。这种机制最大的好处是，方便与jQuery或其他操作DOM的库配合使用\r\n\r\n```javascript\r\n            avalon.config.async = false\r\n            var vm = avalon.define({\r\n                $id: \"test\",\r\n                a: 1\r\n            })\r\n           setTimeout(function(){\r\n               vm.a = 2\r\n               alert(document.getElementById(\"aaa\").innerHTML) //2\r\n           }, 1000)\r\n```\r\n```html\r\n        <div ms-controller=\"test\">\r\n            <div id=\"aaa\">{{a}}</div> // 2\r\n        </div>\r\n```\r\n\r\n坏处是可能会造成性能问题。作为测试，我们在avalon的text指令加入这样一行日志： \r\n```javascript\r\navalon.directive(\"text\", {\r\n    update: function (value) {\r\n        console.log(value)\r\n        var elem = this.element\r\n        value = value == null ? \"\" : value //不在页面上显示undefined null\r\n        if (elem.nodeType === 3) { //绑定在文本节点上\r\n            try { //IE对游离于DOM树外的节点赋值会报错\r\n                elem.data = value\r\n            } catch (e) {\r\n            }\r\n        } else { //绑定在特性节点上\r\n            if (\"textContent\" in elem) {\r\n                elem.textContent = value\r\n            } else {\r\n                elem.innerText = value\r\n            }\r\n        }\r\n    }\r\n})\r\n````\r\n测试页面为：\r\n```html\r\n<!DOCTYPE html>\r\n<html>\r\n    <head>\r\n        <title>TODO supply a title</title>\r\n        <meta charset=\"UTF-8\">\r\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n        <script src=\"avalon.js\"></script>\r\n        <script>\r\n            avalon.config.async = false\r\n            var vm = avalon.define({\r\n                $id: \"test\",\r\n                a: 1\r\n            })\r\n            setTimeout(function () {\r\n                vm.a = 2\r\n                vm.a = 3\r\n                vm.a = 4\r\n                vm.a = 5\r\n                vm.a = 6\r\n            })\r\n        </script>\r\n    </head>\r\n    <body>\r\n        <div ms-controller=\"test\">\r\n            <div id=\"aaa\">{{a}}</div>\r\n        </div>\r\n    </body>\r\n</html>\r\n```\r\n![image](https://cloud.githubusercontent.com/assets/190846/9067205/b1044d36-3b0e-11e5-996c-418925b8ff57.png)\r\n\r\n在avalon1.5中，打开 avalon.config.async = true开关后，就只输出两次。第一次扫描肯定立即出来，以后就是异步了。\r\n\r\n![image](https://cloud.githubusercontent.com/assets/190846/9067256/f15a7ebe-3b0e-11e5-8ac5-d88565f58cfc.png)\r\n\r\navalon做到这个非常简单，因为经过多次重构优化，从VM到V的同步，都要经过notifySubscribers这个方法。它是用来执行当前属性对应的订阅数组中的每个对象的update方法。\r\n```javascript\r\nfunction notifySubscribers(subs) {\r\n    if (!subs)\r\n        return\r\n    if (new Date() - beginTime > 444 && typeof subs[0] === \"object\") {\r\n        rejectDisposeQueue()  //如果这个绑定对象的节点已经被移出DOM树，那么需要对这sub对象进行GC回收处理\r\n    }\r\n    for (var i = 0, sub; sub = subs[i++]; ) {\r\n            sub.update && sub.update() //最小化刷新DOM树\r\n     }\r\n}\r\n````\r\n现在改成这样了\r\n\r\n```javascript\r\nfunction notifySubscribers(subs) {\r\n    if (!subs)\r\n        return\r\n    if (new Date() - beginTime > 444 && typeof subs[0] === \"object\") {\r\n        rejectDisposeQueue()//...\r\n    }\r\n    if (kernel.async) {\r\n        buffer.render()\r\n        for (var i = 0, sub; sub = subs[i++]; ) {\r\n            if (sub.update) {\r\n                avalon.Array.ensure(buffer.queue,sub)//这里有去重处理\r\n            }\r\n        }\r\n    } else {\r\n        for (var i = 0, sub; sub = subs[i++]; ) {\r\n            sub.update && sub.update()//最小化刷新DOM树\r\n        }\r\n    }\r\n}\r\n```\r\nbuffer对象很简单\r\n```javascript\r\nvar buffer = {\r\n    render: function () {\r\n        if (!this.locked) {\r\n            this.locked = 1\r\n            avalon.nextTick(function () {\r\n                buffer.flush()\r\n            })\r\n        } \r\n    },\r\n    queue: [],\r\n    flush: function () {\r\n        for (var i = 0, sub; sub = this.queue[i++]; ) {\r\n            sub.update()\r\n        }\r\n        this.queue.length = this.locked = 0\r\n    }\r\n}\r\n```\r\n这是历次重构，精简入口带来的好处。换言之，做一样的东西，只有一个函数负责。那么要对功能做扩展，就直接对 此函数进行处理就行了。\r\n"},"comment":{"url":"https://api.github.com/repos/RubyLouvre/avalon/issues/comments/127832319","html_url":"https://github.com/RubyLouvre/avalon/issues/956#issuecomment-127832319","issue_url":"https://api.github.com/repos/RubyLouvre/avalon/issues/956","id":127832319,"user":{"login":"RubyLouvre","id":190846,"avatar_url":"https://avatars.githubusercontent.com/u/190846?v=3","gravatar_id":"","url":"https://api.github.com/users/RubyLouvre","html_url":"https://github.com/RubyLouvre","followers_url":"https://api.github.com/users/RubyLouvre/followers","following_url":"https://api.github.com/users/RubyLouvre/following{/other_user}","gists_url":"https://api.github.com/users/RubyLouvre/gists{/gist_id}","starred_url":"https://api.github.com/users/RubyLouvre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RubyLouvre/subscriptions","organizations_url":"https://api.github.com/users/RubyLouvre/orgs","repos_url":"https://api.github.com/users/RubyLouvre/repos","events_url":"https://api.github.com/users/RubyLouvre/events{/privacy}","received_events_url":"https://api.github.com/users/RubyLouvre/received_events","type":"User","site_admin":false},"created_at":"2015-08-05T03:16:47Z","updated_at":"2015-08-05T03:16:47Z","body":"脏检测是一种很垃圾的技术，怎么可能！"}}