代码拉取完成,页面将自动刷新
<!DOCTYPE html><html><head><meta charset="utf-8"><style>body { width: 45em; border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body { padding: 30px; } @font-face { font-family: fontawesome-mini; src: url(data:font/woff;charset=utf-8;base64,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) format('woff'); } @font-face { font-family: octicons-anchor; src: url(data:font/woff;charset=utf-8;base64,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) format('woff'); } .markdown-body { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; color: #333333; overflow: hidden; font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif; font-size: 16px; line-height: 1.6; word-wrap: break-word; } .markdown-body a { background: transparent; } .markdown-body a:active, .markdown-body a:hover { outline: 0; } .markdown-body b, .markdown-body strong { font-weight: bold; } .markdown-body mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } .markdown-body sub, .markdown-body sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } .markdown-body sup { top: -0.5em; } .markdown-body sub { bottom: -0.25em; } .markdown-body h1 { font-size: 2em; margin: 0.67em 0; } .markdown-body img { border: 0; } .markdown-body hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } .markdown-body pre { overflow: auto; } .markdown-body code, .markdown-body kbd, .markdown-body pre, .markdown-body samp { font-family: monospace, monospace; font-size: 1em; } .markdown-body input { color: inherit; font: inherit; margin: 0; } .markdown-body html input[disabled] { cursor: default; } .markdown-body input { line-height: normal; } .markdown-body input[type="checkbox"] { box-sizing: border-box; padding: 0; } .markdown-body table { border-collapse: collapse; border-spacing: 0; } .markdown-body td, .markdown-body th { padding: 0; } .markdown-body .codehilitetable { border: 0; border-spacing: 0; } .markdown-body .codehilitetable tr { border: 0; } .markdown-body .codehilitetable pre, .markdown-body .codehilitetable div.codehilite { margin: 0; } .markdown-body .linenos, .markdown-body .code, .markdown-body .codehilitetable td { border: 0; padding: 0; } .markdown-body td:not(.linenos) .linenodiv { padding: 0 !important; } .markdown-body .code { width: 100%; } .markdown-body .linenos div pre, .markdown-body .linenodiv pre, .markdown-body .linenodiv { border: 0; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; -webkit-border-top-left-radius: 3px; -webkit-border-bottom-left-radius: 3px; -moz-border-radius-topleft: 3px; -moz-border-radius-bottomleft: 3px; border-top-left-radius: 3px; border-bottom-left-radius: 3px; } .markdown-body .code div pre, .markdown-body .code div { border: 0; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; -webkit-border-top-right-radius: 3px; -webkit-border-bottom-right-radius: 3px; -moz-border-radius-topright: 3px; -moz-border-radius-bottomright: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } .markdown-body * { -moz-box-sizing: border-box; box-sizing: border-box; } .markdown-body input { font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; line-height: 1.4; } .markdown-body a { color: #4183c4; text-decoration: none; } .markdown-body a:hover, .markdown-body a:focus, .markdown-body a:active { text-decoration: underline; } .markdown-body hr { height: 0; margin: 15px 0; overflow: hidden; background: transparent; border: 0; border-bottom: 1px solid #ddd; } .markdown-body hr:before, .markdown-body hr:after { display: table; content: " "; } .markdown-body hr:after { clear: both; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 15px; margin-bottom: 15px; line-height: 1.1; } .markdown-body h1 { font-size: 30px; } .markdown-body h2 { font-size: 21px; } .markdown-body h3 { font-size: 16px; } .markdown-body h4 { font-size: 14px; } .markdown-body h5 { font-size: 12px; } .markdown-body h6 { font-size: 11px; } .markdown-body blockquote { margin: 0; } .markdown-body ul, .markdown-body ol { padding: 0; margin-top: 0; margin-bottom: 0; } .markdown-body ol ol, .markdown-body ul ol { list-style-type: lower-roman; } .markdown-body ul ul ol, .markdown-body ul ol ol, .markdown-body ol ul ol, .markdown-body ol ol ol { list-style-type: lower-alpha; } .markdown-body dd { margin-left: 0; } .markdown-body code, .markdown-body pre, .markdown-body samp { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; } .markdown-body pre { margin-top: 0; margin-bottom: 0; } .markdown-body kbd { background-color: #e7e7e7; background-image: -moz-linear-gradient(#fefefe, #e7e7e7); background-image: -webkit-linear-gradient(#fefefe, #e7e7e7); background-image: linear-gradient(#fefefe, #e7e7e7); background-repeat: repeat-x; border-radius: 2px; border: 1px solid #cfcfcf; color: #000; padding: 3px 5px; line-height: 10px; font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; display: inline-block; } .markdown-body>*:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !important; } .markdown-body .headeranchor-link { position: absolute; top: 0; bottom: 0; left: 0; display: block; padding-right: 6px; padding-left: 30px; margin-left: -30px; } .markdown-body .headeranchor-link:focus { outline: none; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { position: relative; margin-top: 1em; margin-bottom: 16px; font-weight: bold; line-height: 1.4; } .markdown-body h1 .headeranchor, .markdown-body h2 .headeranchor, .markdown-body h3 .headeranchor, .markdown-body h4 .headeranchor, .markdown-body h5 .headeranchor, .markdown-body h6 .headeranchor { display: none; color: #000; vertical-align: middle; } .markdown-body h1:hover .headeranchor-link, .markdown-body h2:hover .headeranchor-link, .markdown-body h3:hover .headeranchor-link, .markdown-body h4:hover .headeranchor-link, .markdown-body h5:hover .headeranchor-link, .markdown-body h6:hover .headeranchor-link { height: 1em; padding-left: 8px; margin-left: -30px; line-height: 1; text-decoration: none; } .markdown-body h1:hover .headeranchor-link .headeranchor, .markdown-body h2:hover .headeranchor-link .headeranchor, .markdown-body h3:hover .headeranchor-link .headeranchor, .markdown-body h4:hover .headeranchor-link .headeranchor, .markdown-body h5:hover .headeranchor-link .headeranchor, .markdown-body h6:hover .headeranchor-link .headeranchor { display: inline-block; } .markdown-body h1 { padding-bottom: 0.3em; font-size: 2.25em; line-height: 1.2; border-bottom: 1px solid #eee; } .markdown-body h2 { padding-bottom: 0.3em; font-size: 1.75em; line-height: 1.225; border-bottom: 1px solid #eee; } .markdown-body h3 { font-size: 1.5em; line-height: 1.43; } .markdown-body h4 { font-size: 1.25em; } .markdown-body h5 { font-size: 1em; } .markdown-body h6 { font-size: 1em; color: #777; } .markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre, .markdown-body .admonition { margin-top: 0; margin-bottom: 16px; } .markdown-body hr { height: 4px; padding: 0; margin: 16px 0; background-color: #e7e7e7; border: 0 none; } .markdown-body ul, .markdown-body ol { padding-left: 2em; } .markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-top: 0; margin-bottom: 0; } .markdown-body li>p { margin-top: 16px; } .markdown-body dl { padding: 0; } .markdown-body dl dt { padding: 0; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: bold; } .markdown-body dl dd { padding: 0 16px; margin-bottom: 16px; } .markdown-body blockquote { padding: 0 15px; color: #777; border-left: 4px solid #ddd; } .markdown-body blockquote>:first-child { margin-top: 0; } .markdown-body blockquote>:last-child { margin-bottom: 0; } .markdown-body table { display: block; width: 100%; overflow: auto; word-break: normal; word-break: keep-all; } .markdown-body table th { font-weight: bold; } .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #ddd; } .markdown-body table tr { background-color: #fff; border-top: 1px solid #ccc; } .markdown-body table tr:nth-child(2n) { background-color: #f8f8f8; } .markdown-body img { max-width: 100%; -moz-box-sizing: border-box; box-sizing: border-box; } .markdown-body code, .markdown-body samp { padding: 0; padding-top: 0.2em; padding-bottom: 0.2em; margin: 0; font-size: 85%; background-color: rgba(0,0,0,0.04); border-radius: 3px; } .markdown-body code:before, .markdown-body code:after { letter-spacing: -0.2em; content: "\00a0"; } .markdown-body pre>code { padding: 0; margin: 0; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0; } .markdown-body .codehilite { margin-bottom: 16px; } .markdown-body .codehilite pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f7f7f7; border-radius: 3px; } .markdown-body .codehilite pre { margin-bottom: 0; word-break: normal; } .markdown-body pre { word-wrap: normal; } .markdown-body pre code { display: inline; max-width: initial; padding: 0; margin: 0; overflow: initial; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0; } .markdown-body pre code:before, .markdown-body pre code:after { content: normal; } /* Admonition */ .markdown-body .admonition { -webkit-border-radius: 3px; -moz-border-radius: 3px; position: relative; border-radius: 3px; border: 1px solid #e0e0e0; border-left: 6px solid #333; padding: 10px 10px 10px 30px; } .markdown-body .admonition table { color: #333; } .markdown-body .admonition p { padding: 0; } .markdown-body .admonition-title { font-weight: bold; margin: 0; } .markdown-body .admonition>.admonition-title { color: #333; } .markdown-body .attention>.admonition-title { color: #a6d796; } .markdown-body .caution>.admonition-title { color: #d7a796; } .markdown-body .hint>.admonition-title { color: #96c6d7; } .markdown-body .danger>.admonition-title { color: #c25f77; } .markdown-body .question>.admonition-title { color: #96a6d7; } .markdown-body .note>.admonition-title { color: #d7c896; } .markdown-body .admonition:before, .markdown-body .attention:before, .markdown-body .caution:before, .markdown-body .hint:before, .markdown-body .danger:before, .markdown-body .question:before, .markdown-body .note:before { font: normal normal 16px fontawesome-mini; -moz-osx-font-smoothing: grayscale; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; line-height: 1.5; color: #333; position: absolute; left: 0; top: 0; padding-top: 10px; padding-left: 10px; } .markdown-body .admonition:before { content: "\f056\00a0"; color: 333; } .markdown-body .attention:before { content: "\f058\00a0"; color: #a6d796; } .markdown-body .caution:before { content: "\f06a\00a0"; color: #d7a796; } .markdown-body .hint:before { content: "\f05a\00a0"; color: #96c6d7; } .markdown-body .danger:before { content: "\f057\00a0"; color: #c25f77; } .markdown-body .question:before { content: "\f059\00a0"; color: #96a6d7; } .markdown-body .note:before { content: "\f040\00a0"; color: #d7c896; } .markdown-body .admonition::after { content: normal; } .markdown-body .attention { border-left: 6px solid #a6d796; } .markdown-body .caution { border-left: 6px solid #d7a796; } .markdown-body .hint { border-left: 6px solid #96c6d7; } .markdown-body .danger { border-left: 6px solid #c25f77; } .markdown-body .question { border-left: 6px solid #96a6d7; } .markdown-body .note { border-left: 6px solid #d7c896; } .markdown-body .admonition>*:first-child { margin-top: 0 !important; } .markdown-body .admonition>*:last-child { margin-bottom: 0 !important; } /* progress bar*/ .markdown-body .progress { display: block; width: 300px; margin: 10px 0; height: 24px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; background-color: #ededed; position: relative; box-shadow: inset -1px 1px 3px rgba(0, 0, 0, .1); } .markdown-body .progress-label { position: absolute; text-align: center; font-weight: bold; width: 100%; margin: 0; line-height: 24px; color: #333; text-shadow: 1px 1px 0 #fefefe, -1px -1px 0 #fefefe, -1px 1px 0 #fefefe, 1px -1px 0 #fefefe, 0 1px 0 #fefefe, 0 -1px 0 #fefefe, 1px 0 0 #fefefe, -1px 0 0 #fefefe, 1px 1px 2px #000; -webkit-font-smoothing: antialiased !important; white-space: nowrap; overflow: hidden; } .markdown-body .progress-bar { height: 24px; float: left; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; background-color: #96c6d7; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -1px 0 rgba(0, 0, 0, .1); background-size: 30px 30px; background-image: -webkit-linear-gradient( 135deg, rgba(255, 255, 255, .4) 27%, transparent 27%, transparent 52%, rgba(255, 255, 255, .4) 52%, rgba(255, 255, 255, .4) 77%, transparent 77%, transparent ); background-image: -moz-linear-gradient( 135deg, rgba(255, 255, 255, .4) 27%, transparent 27%, transparent 52%, rgba(255, 255, 255, .4) 52%, rgba(255, 255, 255, .4) 77%, transparent 77%, transparent ); background-image: -ms-linear-gradient( 135deg, rgba(255, 255, 255, .4) 27%, transparent 27%, transparent 52%, rgba(255, 255, 255, .4) 52%, rgba(255, 255, 255, .4) 77%, transparent 77%, transparent ); background-image: -o-linear-gradient( 135deg, rgba(255, 255, 255, .4) 27%, transparent 27%, transparent 52%, rgba(255, 255, 255, .4) 52%, rgba(255, 255, 255, .4) 77%, transparent 77%, transparent ); background-image: linear-gradient( 135deg, rgba(255, 255, 255, .4) 27%, transparent 27%, transparent 52%, rgba(255, 255, 255, .4) 52%, rgba(255, 255, 255, .4) 77%, transparent 77%, transparent ); } .markdown-body .progress-100plus .progress-bar { background-color: #a6d796; } .markdown-body .progress-80plus .progress-bar { background-color: #c6d796; } .markdown-body .progress-60plus .progress-bar { background-color: #d7c896; } .markdown-body .progress-40plus .progress-bar { background-color: #d7a796; } .markdown-body .progress-20plus .progress-bar { background-color: #d796a6; } .markdown-body .progress-0plus .progress-bar { background-color: #c25f77; } .markdown-body .candystripe-animate .progress-bar{ -webkit-animation: animate-stripes 3s linear infinite; -moz-animation: animate-stripes 3s linear infinite; animation: animate-stripes 3s linear infinite; } @-webkit-keyframes animate-stripes { 0% { background-position: 0 0; } 100% { background-position: 60px 0; } } @-moz-keyframes animate-stripes { 0% { background-position: 0 0; } 100% { background-position: 60px 0; } } @keyframes animate-stripes { 0% { background-position: 0 0; } 100% { background-position: 60px 0; } } .markdown-body .gloss .progress-bar { box-shadow: inset 0 4px 12px rgba(255, 255, 255, .7), inset 0 -12px 0 rgba(0, 0, 0, .05); } /* Multimarkdown Critic Blocks */ .markdown-body .critic_mark { background: #ff0; } .markdown-body .critic_delete { color: #c82829; text-decoration: line-through; } .markdown-body .critic_insert { color: #718c00 ; text-decoration: underline; } .markdown-body .critic_comment { color: #8e908c; font-style: italic; } .markdown-body .headeranchor { font: normal normal 16px octicons-anchor; line-height: 1; display: inline-block; text-decoration: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .headeranchor:before { content: '\f05c'; } .markdown-body .task-list-item { list-style-type: none; } .markdown-body .task-list-item+.task-list-item { margin-top: 3px; } .markdown-body .task-list-item input { margin: 0 4px 0.25em -20px; vertical-align: middle; } /* Media */ @media only screen and (min-width: 480px) { .markdown-body { font-size:14px; } } @media only screen and (min-width: 768px) { .markdown-body { font-size:16px; } } @media print { .markdown-body * { background: transparent !important; color: black !important; filter:none !important; -ms-filter: none !important; } .markdown-body { font-size:12pt; max-width:100%; outline:none; border: 0; } .markdown-body a, .markdown-body a:visited { text-decoration: underline; } .markdown-body .headeranchor-link { display: none; } .markdown-body a[href]:after { content: " (" attr(href) ")"; } .markdown-body abbr[title]:after { content: " (" attr(title) ")"; } .markdown-body .ir a:after, .markdown-body a[href^="javascript:"]:after, .markdown-body a[href^="#"]:after { content: ""; } .markdown-body pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } .markdown-body pre, .markdown-body blockquote { border: 1px solid #999; padding-right: 1em; page-break-inside: avoid; } .markdown-body .progress, .markdown-body .progress-bar { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } .markdown-body .progress { border: 1px solid #ddd; } .markdown-body .progress-bar { height: 22px; border-right: 1px solid #ddd; } .markdown-body tr, .markdown-body img { page-break-inside: avoid; } .markdown-body img { max-width: 100% !important; } .markdown-body p, .markdown-body h2, .markdown-body h3 { orphans: 3; widows: 3; } .markdown-body h2, .markdown-body h3 { page-break-after: avoid; } } </style><title>readme</title></head><body><article class="markdown-body"><h2 id="vue-mvvm"><a name="user-content-vue-mvvm" href="#vue-mvvm" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>剖析Vue实现原理 - 如何实现双向绑定mvvm</h2> <blockquote> <p>本文能帮你做什么?<br /> 1、了解vue的双向数据绑定原理以及核心代码模块<br /> 2、缓解好奇心的同时了解如何实现双向绑定<br /> 为了便于说明原理与实现,本文相关代码主要摘自<a href="https://github.com/vuejs/vue">vue源码</a>, 并进行了简化改造,相对较简陋,并未考虑到数组的处理、数据的循环依赖等,也难免存在一些问题,欢迎大家指正。不过这些并不会影响大家的阅读和理解,相信看完本文后对大家在阅读vue源码的时候会更有帮助<br /> 本文所有相关代码均在github上面可找到 <a href="https://github.com/DMQ/mvvm"><a href="https://github.com/DMQ/mvvm"><a href="https://github.com/DMQ/mvvm">https://github.com/DMQ/mvvm</a></a></a></p> </blockquote> <h5 id="mvvmvue"><a name="user-content-mvvmvue" href="#mvvmvue" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>相信大家对mvvm双向绑定应该都不陌生了,一言不合上代码(可怜的代码),下面先看一个本文最终实现的效果吧,和vue一样的语法,如果还不了解双向绑定,猛戳<a href="">这里</a></h5> <pre><code><div id="mvvm-app"> <input type="text" v-model="word"> <p>{{word}}</p> <button v-on:click="sayHi">change model</button> </div> <script src="./js/observer.js"></script> <script src="./js/watcher.js"></script> <script src="./js/compile.js"></script> <script src="./js/mvvm.js"></script> <script> var vm = new MVVM({ el: '#mvvm-app', data: { word: 'Hello World!' }, methods: { sayHi: function() { this.word = 'Hi, everybody!'; } } }); </script> </code></pre> <p>效果:<br /> <img alt="img1" src="///F://git/mvvm/img/1.gif" /></p> <h3 id="_1"><a name="user-content-_1" href="#_1" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>几种实现双向绑定的做法</h3> <p>目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view,并没有多高深。所以无需太过介怀是实现的单向或双向绑定。</p> <p>实现数据绑定的做法有大致如下几种:</p> <blockquote> <p>发布者-订阅者模式(backbone.js)</p> <p>脏值检查(angular.js) </p> <p>数据劫持(vue.js) </p> </blockquote> <p><strong>发布者-订阅者模式:</strong> 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 <code>vm.set('property', value)</code>,这里有篇文章讲的比较详细,有兴趣可点<a href="http://www.html-js.com/article/Study-of-twoway-data-binding-JavaScript-talk-about-JavaScript-every-day">这里</a></p> <p>这种方式现在毕竟太low了,我们更希望通过 <code>vm.property = value</code>这种方式更新数据,同时自动更新视图,于是有了下面两种方式</p> <p><strong>脏值检查:</strong> angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 <code>setInterval()</code> 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:</p> <ul> <li>DOM事件,譬如用户输入文本,点击按钮等。( ng-click ) </li> <li>XHR响应事件 ( $http ) </li> <li>浏览器Location变更事件 ( $location ) </li> <li>Timer事件( $timeout , $interval ) </li> <li>执行 $digest() 或 $apply()</li> </ul> <p><strong>数据劫持:</strong> vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过<code>Object.defineProperty()</code>来劫持各个属性的<code>setter</code>,<code>getter</code>,在数据变动时发布消息给订阅者,触发相应的监听回调。</p> <h3 id="_2"><a name="user-content-_2" href="#_2" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>思路整理</h3> <p>已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过<code>Object.defineProperty()</code>来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,如果不熟悉defineProperty,猛戳<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty">这里</a><br /> 整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:<br /> 1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者<br /> 2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令替换数据,以及绑定相应的更新函数<br /> 3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图<br /> 4、mvvm入口函数,整合以上三者</p> <p>上述流程如图所示:<br /> <img alt="img2" src="///F://git/mvvm/img/2.png" /></p> <h3 id="1observer"><a name="user-content-1observer" href="#1observer" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>1、实现Observer</h3> <p>ok, 思路已经整理完毕,也已经比较明确相关逻辑和模块功能了,let’s do it<br /> 我们知道可以利用<code>Obeject.defineProperty()</code>来监听属性变动<br /> 那么将需要observe的数据对象进行递归遍历,包括子属性的属性,都加上 <code>set</code>和<code>get</code><br /> 这样的话,给这个对象的某个值赋值,就会触发<code>set</code>,那么就能监听到了数据变化。。相关代码可以是这样:<br /> <pre><code>var data = {name: 'kindeng'}; observe(data); data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq function observe(data) { if (!data || typeof data !== 'object') { return; } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function defineReactive(data, key, val) { observe(val); // 监听子属性 Object.defineProperty(data, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get: function() { return val; }, set: function(newVal) { console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal); val = newVal; } }); } </code></pre><br /> 这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法,代码改一下之后是这样:<br /> <pre><code>// ... 省略 function defineReactive(data, key, val) { var dep = new Dep(); observe(val); // 监听子属性 Object.defineProperty(data, key, { // ... 省略 set: function(newVal) { if (val === newVal) return; console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal); val = newVal; dep.notify(); // 通知所有订阅者 } }); } function Dep() { this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; </code></pre><br /> 那么问题来了,谁是订阅者?没错,上面的思路整理中我们已经知道订阅者应该是Watcher, 而且<code>var dep = new Dep();</code>是在 defineReactive 方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在 <code>get</code>里面动手脚,<br /> <pre><code>// Observer.js // ...省略 Object.defineProperty(data, key, { get: function() { // dep.addDep(watcher); // 由于需要在闭包内添加watcher,所以可以在Dep定义一个全局target属性,暂存watcher, 添加完移除 Dep.target && dep.addDep(Dep.target); return val } // ... 省略 }); // Wacher.js // ...省略 get: function(key) { Dep.target = this; this.value = data.key; } </code></pre><br /> 先记住这个思路,Dep.target在什么时候赋值对应的watcher后面会讲到</p> <p>这里已经实现了一个Observer了,已经具备了应有的功能,vue源码在<a href="https://github.com/vuejs/vue/blob/dev/src/observer/index.js">这里</a>。那么接下来就是实现Compile了</p> <h3 id="2watcher"><a name="user-content-2watcher" href="#2watcher" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>2、实现Watcher</h3> <p>从前面知道,<code>var dep = new Dep();</code>是在 defineReactive 方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在 <code>get</code>里面动手脚<br /> <pre><code>// ... 省略 var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get: function() { dep.add return val; }, set: function(newVal) { if (val === newVal) return; console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal); val = newVal; dep.notify(); // 通知所有订阅者 } }); </code></pre></p> <h4 id="4vue-github"><a name="user-content-4vue-github" href="#4vue-github" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>4、Vue的生命周期 (<a href="https://github.com/vuejs/vue/">Github</a>)</h4> <p><img src="https://vuejs.org.cn/images/lifecycle.png" width="640px"></p> <h4 id="5"><a name="user-content-5" href="#5" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>5、我们要实现的关键点:</h4> <ul> <li>数据监听 Observer.js 和消息订阅器 Dep.js</li> <li>订阅者 Watcher.js</li> <li>指令编译器 Compile.js</li> <li>入口 mvvm</li> </ul> <h4 id="6objectdefineproperty"><a name="user-content-6objectdefineproperty" href="#6objectdefineproperty" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>6、一切基于此Object.defineProperty()</h4> <p><em>IE8+</em></p> <p><strong>code:</strong><br /> <pre><code>function defineReative(data) { var val; Object.defineProperty(data, 'name', { enumerable: true, // 可枚举 configurable: true, // 不能再define get: function() { return val; }, set: function(newVal) { console.log('你变了:', val, ' ==> ', newVal); val = newVal; } }); } var data = {}; defineReative(data); </code></pre></p> <h4 id="7observerjs"><a name="user-content-7observerjs" href="#7observerjs" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>7、实现Observer.js</h4> <p>负责监听源数据所有属性,一旦发生变化,通知订阅者更新视图</p> <p><a href="///F://git/mvvm/js/observer.js">code</a></p> <h4 id="8compilejs"><a name="user-content-8compilejs" href="#8compilejs" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>8、实现Compile.js</h4> <p>负责解析模板指令,不同的指令绑定不同的处理回调及视图更新方法</p> <p><a href="///F://git/mvvm/js/compile.js">code</a></p> <h4 id="9observe-compile-watcherjs"><a name="user-content-9observe-compile-watcherjs" href="#9observe-compile-watcherjs" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>9、如何连接observe 和 compile –> watcher.js(桥梁)</h4> <p>充当数据更新的订阅者,每一个属性的变化都会通知它,在compile阶段实例化并注入回调函数</p> <p>每一个属性都有一个watcher</p> <p><a href="///F://git/mvvm/js/watcher.js">code</a></p></article></body></html>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。