DMQ hace 9 años
commit
7688533df7
Se han modificado 10 ficheros con 1555 adiciones y 0 borrados
  1. 803 0
      css/github.css
  2. BIN
      js/.DS_Store
  3. 186 0
      js/compile.js
  4. 35 0
      js/mvvm.js
  5. 84 0
      js/observer.js
  6. 56 0
      js/watcher.js
  7. 51 0
      mvvm.html
  8. 156 0
      readme.html
  9. 154 0
      readme.md
  10. 30 0
      vue-demo/index.html

+ 803 - 0
css/github.css

@@ -0,0 +1,803 @@
+html {
+  font-family: sans-serif;
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+}
+
+article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary {
+  display: block;
+}
+
+audio,canvas,progress,video {
+  display: inline-block;
+  vertical-align: baseline;
+}
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+[hidden],template {
+  display: none;
+}
+
+a {
+  background: transparent;
+}
+
+a:active,a:hover {
+  outline: 0;
+}
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+b,strong {
+  font-weight: bold;
+}
+
+dfn {
+  font-style: italic;
+}
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+mark {
+  background: #ff0;
+  color: #000;
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  border: 0;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+figure {
+  margin: 1em 40px;
+}
+
+hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+pre {
+  overflow: auto;
+}
+
+code,kbd,pre,samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+button,input,optgroup,select,textarea {
+  color: inherit;
+  font: inherit;
+  margin: 0;
+}
+
+button {
+  overflow: visible;
+}
+
+button,select {
+  text-transform: none;
+}
+
+button,html input[type="button"],input[type="reset"],input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+
+button[disabled],html input[disabled] {
+  cursor: default;
+}
+
+button::-moz-focus-inner,input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+input {
+  line-height: normal;
+}
+
+input[type="checkbox"],input[type="radio"] {
+  box-sizing: border-box;
+  padding: 0;
+}
+
+input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+input[type="search"] {
+  -webkit-appearance: textfield;
+  box-sizing: content-box;
+}
+
+input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+
+legend {
+  border: 0;
+  padding: 0;
+}
+
+textarea {
+  overflow: auto;
+}
+
+optgroup {
+  font-weight: bold;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td,th {
+  padding: 0;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+input,select,textarea,button {
+  font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+body {
+  min-width: 1020px;
+  font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
+  color: #333;
+  background-color: #fff;
+}
+
+a {
+  color: #4183c4;
+  text-decoration: none;
+}
+
+a:hover,a:active {
+  text-decoration: underline;
+}
+
+hr,.rule {
+  height: 0;
+  margin: 15px 0;
+  overflow: hidden;
+  background: transparent;
+  border: 0;
+  border-bottom: 1px solid #ddd;
+}
+
+hr:before,.rule:before {
+  display: table;
+  content: "";
+}
+
+hr:after,.rule:after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+h1,h2,h3,h4,h5,h6 {
+  margin-top: 15px;
+  margin-bottom: 15px;
+  line-height: 1.1;
+}
+
+h1 {
+  font-size: 30px;
+}
+
+h2 {
+  font-size: 21px;
+}
+
+h3 {
+  font-size: 16px;
+}
+
+h4 {
+  font-size: 14px;
+}
+
+h5 {
+  font-size: 12px;
+}
+
+h6 {
+  font-size: 11px;
+}
+
+small {
+  font-size: 90%;
+}
+
+blockquote {
+  margin: 0;
+}
+
+.lead {
+  margin-bottom: 30px;
+  font-size: 20px;
+  font-weight: 300;
+  color: #555;
+}
+
+.text-muted {
+  color: #999;
+}
+
+.text-danger {
+  color: #bd2c00;
+}
+
+.text-emphasized {
+  font-weight: bold;
+  color: #333;
+}
+
+ul,ol {
+  padding: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+ol ol,ul ol {
+  list-style-type: lower-roman;
+}
+
+ul ul ol,ul ol ol,ol ul ol,ol ol ol {
+  list-style-type: lower-alpha;
+}
+
+dd {
+  margin-left: 0;
+}
+
+tt,code {
+  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font-size: 12px;
+}
+
+pre {
+  margin-top: 0;
+  margin-bottom: 0;
+  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+}
+
+#realtime .status {
+  overflow: visible;
+  position: absolute;
+  top: -5px;
+  left: 0;
+  background: url("/public/images/github-status.png");
+  width: 26px;
+  height: 26px;
+  display: block;
+  margin: 0 5px 0 0;
+}
+
+#realtime .up {
+  background-position: 0 0;
+}
+
+#realtime .problem {
+  background-position: 0 -53px;
+}
+
+#realtime .down {
+  background-position: 0 -26px;
+}
+
+.container {
+    max-width: 920px;
+    margin: 0 auto 20px auto;
+}
+
+#header {
+    background: #FAFAFA;
+    background: -moz-linear-gradient(#FAFAFA, #EAEAEA);
+    background: -webkit-linear-gradient(#FAFAFA, #EAEAEA);
+    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#fafafa', endColorstr='#eaeaea')";
+    border-bottom: 1px solid #CACACA;
+    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4),0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+#markup {
+    padding: 3px;
+}
+
+#markup article {
+    padding-top: 30px;
+}
+
+.markdown-body {
+  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>*:first-child {
+  margin-top: 0 !important;
+}
+
+.markdown-body>*:last-child {
+  margin-bottom: 0 !important;
+}
+
+.markdown-body .absent {
+  color: #c00;
+}
+
+.markdown-body .anchor {
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: block;
+  padding-right: 6px;
+  padding-left: 30px;
+  margin-left: -30px;
+}
+
+.markdown-body .anchor: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 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link {
+  display: none;
+  color: #000;
+  vertical-align: middle;
+}
+
+.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor {
+  padding-left: 8px;
+  margin-left: -30px;
+  text-decoration: none;
+}
+
+.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link {
+  display: inline-block;
+}
+
+.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code {
+  font-size: inherit;
+}
+
+.markdown-body h1 {
+  padding-bottom: 0.3em;
+  font-size: 2.25em;
+  line-height: 1.2;
+  border-bottom: 1px solid #eee;
+}
+
+.markdown-body h1 .anchor {
+  line-height: 1;
+}
+
+.markdown-body h2 {
+  padding-bottom: 0.3em;
+  font-size: 1.75em;
+  line-height: 1.225;
+  border-bottom: 1px solid #eee;
+}
+
+.markdown-body h2 .anchor {
+  line-height: 1;
+}
+
+.markdown-body h3 {
+  font-size: 1.5em;
+  line-height: 1.43;
+}
+
+.markdown-body h3 .anchor {
+  line-height: 1.2;
+}
+
+.markdown-body h4 {
+  font-size: 1.25em;
+}
+
+.markdown-body h4 .anchor {
+  line-height: 1.2;
+}
+
+.markdown-body h5 {
+  font-size: 1em;
+}
+
+.markdown-body h5 .anchor {
+  line-height: 1.1;
+}
+
+.markdown-body h6 {
+  font-size: 1em;
+  color: #777;
+}
+
+.markdown-body h6 .anchor {
+  line-height: 1.1;
+}
+
+.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre {
+  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.no-list,.markdown-body ol.no-list {
+  padding: 0;
+  list-style-type: none;
+}
+
+.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%;
+  box-sizing: border-box;
+}
+
+.markdown-body span.frame {
+  display: block;
+  overflow: hidden;
+}
+
+.markdown-body span.frame>span {
+  display: block;
+  float: left;
+  width: auto;
+  padding: 7px;
+  margin: 13px 0 0;
+  overflow: hidden;
+  border: 1px solid #ddd;
+}
+
+.markdown-body span.frame span img {
+  display: block;
+  float: left;
+}
+
+.markdown-body span.frame span span {
+  display: block;
+  padding: 5px 0 0;
+  clear: both;
+  color: #333;
+}
+
+.markdown-body span.align-center {
+  display: block;
+  overflow: hidden;
+  clear: both;
+}
+
+.markdown-body span.align-center>span {
+  display: block;
+  margin: 13px auto 0;
+  overflow: hidden;
+  text-align: center;
+}
+
+.markdown-body span.align-center span img {
+  margin: 0 auto;
+  text-align: center;
+}
+
+.markdown-body span.align-right {
+  display: block;
+  overflow: hidden;
+  clear: both;
+}
+
+.markdown-body span.align-right>span {
+  display: block;
+  margin: 13px 0 0;
+  overflow: hidden;
+  text-align: right;
+}
+
+.markdown-body span.align-right span img {
+  margin: 0;
+  text-align: right;
+}
+
+.markdown-body span.float-left {
+  display: block;
+  float: left;
+  margin-right: 13px;
+  overflow: hidden;
+}
+
+.markdown-body span.float-left span {
+  margin: 13px 0 0;
+}
+
+.markdown-body span.float-right {
+  display: block;
+  float: right;
+  margin-left: 13px;
+  overflow: hidden;
+}
+
+.markdown-body span.float-right>span {
+  display: block;
+  margin: 13px auto 0;
+  overflow: hidden;
+  text-align: right;
+}
+
+.markdown-body code,.markdown-body tt {
+  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,.markdown-body tt:before,.markdown-body tt:after {
+  letter-spacing: -0.2em;
+  content: "\00a0";
+}
+
+.markdown-body code br,.markdown-body tt br {
+  display: none;
+}
+
+.markdown-body del code {
+  text-decoration: inherit;
+}
+
+.markdown-body pre>code {
+  padding: 0;
+  margin: 0;
+  font-size: 100%;
+  word-break: normal;
+  white-space: pre;
+  background: transparent;
+  border: 0;
+}
+
+.markdown-body .highlight {
+  margin-bottom: 16px;
+}
+
+.markdown-body .highlight pre,.markdown-body pre {
+  padding: 16px;
+  overflow: auto;
+  font-size: 85%;
+  line-height: 1.45;
+  background-color: #f7f7f7;
+  border-radius: 3px;
+}
+
+.markdown-body .highlight pre {
+  margin-bottom: 0;
+  word-break: normal;
+}
+
+.markdown-body pre {
+  word-wrap: normal;
+}
+
+.markdown-body pre code,.markdown-body pre tt {
+  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,.markdown-body pre tt:before,.markdown-body pre tt:after {
+  content: normal;
+}
+
+.markdown-body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font-size: 11px;
+  line-height: 10px;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fcfcfc;
+  border: solid 1px #ccc;
+  border-bottom-color: #bbb;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #bbb;
+}
+
+/* Syntax highlight */
+.codehilite  { background: #ffffff; }
+.codehilite .c { color: #999988; font-style: italic } /* Comment */
+.codehilite .err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.codehilite .k { color: #000000; font-weight: bold } /* Keyword */
+.codehilite .o { color: #000000; font-weight: bold } /* Operator */
+.codehilite .cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.codehilite .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
+.codehilite .c1 { color: #999988; font-style: italic } /* Comment.Single */
+.codehilite .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
+.codehilite .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.codehilite .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
+.codehilite .ge { color: #000000; font-style: italic } /* Generic.Emph */
+.codehilite .gr { color: #aa0000 } /* Generic.Error */
+.codehilite .gh { color: #999999 } /* Generic.Heading */
+.codehilite .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.codehilite .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
+.codehilite .go { color: #888888 } /* Generic.Output */
+.codehilite .gp { color: #555555 } /* Generic.Prompt */
+.codehilite .gs { font-weight: bold } /* Generic.Strong */
+.codehilite .gu { color: #aaaaaa } /* Generic.Subheading */
+.codehilite .gt { color: #aa0000 } /* Generic.Traceback */
+.codehilite .kc { color: #000000; font-weight: bold } /* Keyword.Constant */
+.codehilite .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */
+.codehilite .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */
+.codehilite .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */
+.codehilite .kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.codehilite .m { color: #009999 } /* Literal.Number */
+.codehilite .s { color: #d14 } /* Literal.String */
+.codehilite .na { color: #008080 } /* Name.Attribute */
+.codehilite .nb { color: #0086B3 } /* Name.Builtin */
+.codehilite .nc { color: #445588; font-weight: bold } /* Name.Class */
+.codehilite .no { color: #008080 } /* Name.Constant */
+.codehilite .ni { color: #800080 } /* Name.Entity */
+.codehilite .ne { color: #990000; font-weight: bold } /* Name.Exception */
+.codehilite .nf { color: #990000; font-weight: bold } /* Name.Function */
+.codehilite .nn { color: #555555 } /* Name.Namespace */
+.codehilite .nt { color: #000080 } /* Name.Tag */
+.codehilite .nv { color: #008080 } /* Name.Variable */
+.codehilite .ow { color: #000000; font-weight: bold } /* Operator.Word */
+.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
+.codehilite .mf { color: #009999 } /* Literal.Number.Float */
+.codehilite .mh { color: #009999 } /* Literal.Number.Hex */
+.codehilite .mi { color: #009999 } /* Literal.Number.Integer */
+.codehilite .mo { color: #009999 } /* Literal.Number.Oct */
+.codehilite .sb { color: #d14 } /* Literal.String.Backtick */
+.codehilite .sc { color: #d14 } /* Literal.String.Char */
+.codehilite .sd { color: #d14 } /* Literal.String.Doc */
+.codehilite .s2 { color: #d14 } /* Literal.String.Double */
+.codehilite .se { color: #d14 } /* Literal.String.Escape */
+.codehilite .sh { color: #d14 } /* Literal.String.Heredoc */
+.codehilite .si { color: #d14 } /* Literal.String.Interpol */
+.codehilite .sx { color: #d14 } /* Literal.String.Other */
+.codehilite .sr { color: #009926 } /* Literal.String.Regex */
+.codehilite .s1 { color: #d14 } /* Literal.String.Single */
+.codehilite .ss { color: #990073 } /* Literal.String.Symbol */
+.codehilite .bp { color: #999999 } /* Name.Builtin.Pseudo */
+.codehilite .vc { color: #008080 } /* Name.Variable.Class */
+.codehilite .vg { color: #008080 } /* Name.Variable.Global */
+.codehilite .vi { color: #008080 } /* Name.Variable.Instance */
+.codehilite .il { color: #009999 } /* Literal.Number.Integer.Long */

BIN
js/.DS_Store


+ 186 - 0
js/compile.js

@@ -0,0 +1,186 @@
+function Compile(el, vm) {
+	this.$vm = vm;
+	this.$el = this.isElementNode(el) ? el : document.querySelector(el);
+	this.$fragment = this.node2Fragment(this.$el);
+
+	this.init();
+
+	this.$el.appendChild(this.$fragment);
+}
+
+Compile.prototype = {
+	node2Fragment: function(el) {
+		var fragment = document.createDocumentFragment(),
+			child;
+
+		// 将原生节点拷贝到fragment
+		while (child = el.firstChild) {
+			fragment.appendChild(child);
+		}
+
+		return fragment;
+	},
+
+	init: function() {
+		this.compileElement(this.$fragment);
+	},
+
+	compileElement: function(el) {
+		var childNodes = el.childNodes,
+			me = this;
+
+		[].slice.call(childNodes).forEach(function(node) {
+			var text = node.textContent;
+			var reg = /\{\{(.*)\}\}/;
+
+			if (me.isElementNode(node)) {
+				me.compile(node);
+
+			} else if (me.isTextNode(node) && reg.test(text)) {
+				me.compileText(node, RegExp.$1);
+			}
+
+			if (node.childNodes && node.childNodes.length) {
+				me.compileElement(node);
+			}
+		});
+	},
+	
+	compile: function(node) {
+		var nodeAttrs = node.attributes,
+			me = this;
+
+		[].slice.call(nodeAttrs).forEach(function(attr) {
+			var attrName = attr.name;
+			if (me.isDirective(attrName)) {
+				var exp = attr.value;
+				var dir = attrName.substring(2);
+                // 事件指令
+				if (me.isEventDirective(dir)) {
+					compileUtil.eventHandler(node, me.$vm, exp, dir);
+                // 普通指令
+				} else {
+					compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
+				}
+
+				node.removeAttribute(attrName);
+			}
+		});
+	},
+
+	compileText: function(node, exp) {
+		compileUtil.text(node, this.$vm, exp);
+	},
+
+	isDirective: function(attr) {
+		return attr.indexOf('v-') == 0;
+	},
+
+    isEventDirective: function(dir) {
+        return dir.indexOf('on') === 0;
+    },
+
+	isElementNode: function(node) {
+		return node.nodeType == 1;
+	},
+
+	isTextNode: function(node) {
+		return node.nodeType == 3;
+	}
+};
+
+// 指令处理集合
+var compileUtil = {
+	text: function(node, vm, exp) {
+		this.bind(node, vm, exp, 'text');
+	},
+
+	html: function(node, vm, exp) {
+		this.bind(node, vm, exp, 'html');
+	},
+
+	model: function(node, vm, exp) {
+		this.bind(node, vm, exp, 'model');
+
+		var me = this, val = this._getVMVal(vm, exp);
+		node.addEventListener('input', function(e) {
+			var newValue = e.target.value;
+			if (val === newValue) {
+				return;
+			}
+			
+			me._setVMVal(vm, exp, newValue);
+			val = newValue;
+		});
+	},
+
+	class: function(node, vm, exp) {
+		this.bind(node, vm, exp, 'class');
+	},
+
+	bind: function(node, vm, exp, dir) {
+		var updaterFn = updater[dir + 'Updater'];
+
+		updaterFn && updaterFn(node, this._getVMVal(vm, exp));
+
+		new Watcher(vm, exp, function(value, oldValue) {
+			updaterFn && updaterFn(node, value, oldValue);
+		});
+	},
+
+    // 事件处理
+	eventHandler: function(node, vm, exp, dir) {
+		var eventType = dir.split(':')[1],
+            fn = vm.$options.methods && vm.$options.methods[exp];
+
+		if (eventType && fn) {
+    		node.addEventListener(eventType, fn.bind(vm), false);
+        }
+	},
+
+	_getVMVal: function(vm, exp) {
+		var val = vm._data;
+		exp = exp.split('.');
+		exp.forEach(function(k) {
+			val = val[k];
+		});
+		return val;
+	},
+
+	_setVMVal: function(vm, exp, value) {
+		var val = vm._data;
+		exp = exp.split('.');
+		exp.forEach(function(k, i) {
+			// 非最后一个key,更新val的值
+			if (i < exp.length - 1) {
+				val = val[k];
+			} else {
+				val[k] = value;
+			}
+		});
+	}
+};
+
+
+var updater = {
+	textUpdater: function(node, value) {
+		node.textContent = typeof value == 'undefined' ? '' : value;
+	},
+
+	htmlUpdater: function(node, value) {
+		node.innerHTML = typeof value == 'undefined' ? '' : value;
+	},
+
+	classUpdater: function(node, value, oldValue) {
+		var className = node.className;
+		className = className.replace(oldValue, '').replace(/\s$/, '');
+
+		var space = className && String(value) ? ' ' : '';
+
+		node.className = className + space + value;
+	},
+
+	modelUpdater: function(node, value, oldValue) {
+		node.value = typeof value == 'undefined' ? '' : value;
+	}
+};

+ 35 - 0
js/mvvm.js

@@ -0,0 +1,35 @@
+function MVVM(options){
+	this.$options = options;
+	var data = this._data = this.$options.data;
+	var me = this;
+
+	// 数据代理
+	// 实现 vm.xxx -> vm._data.xxx
+	Object.keys(data).forEach(function(key) {
+		me._proxy(key);
+	});
+
+	observe(data, this);
+
+	this.$compile = new Compile(options.el || document.body, this)
+}
+
+MVVM.prototype = {
+	$watch: function(key, cb, options) {
+		new Watcher(this, key, cb);
+	},
+
+	_proxy: function(key) {
+		var me = this;
+		Object.defineProperty(me, key, {
+			configurable: true,
+			enumerable: true,
+			get: function proxyGetter() {
+				return me._data[key];
+			},
+			set: function proxySetter(newVal) {
+				me._data[key] = newVal;
+			}
+		});
+	}
+};

+ 84 - 0
js/observer.js

@@ -0,0 +1,84 @@
+function Observer(data) {
+	this.data = data;
+	this.walk(data);
+}
+
+Observer.prototype = {
+	walk: function(data) {
+		var me = this;
+		Object.keys(data).forEach(function(key) {
+			me.convert(key, data[key]);
+		});
+	},
+	convert: function(key, val) {
+		this.defineReactive(this.data, key, val);
+	},
+
+	defineReactive: function(data, key, val) {
+		var dep = new Dep();
+		var childObj = observe(val);
+
+		Object.defineProperty(data, key, {
+			enumerable: true,	// 可枚举
+			configurable: true,	// 不能再define
+			get: function() {
+				if (Dep.target) {
+					dep.depend();
+				}
+				return val;
+			},
+			set: function(newVal) {
+				if (newVal === val) {
+					return ;
+				}
+				val = newVal;
+				// 新的值是object的话,进行监听
+				childObj = observe(newVal);
+				// 通知订阅者
+				dep.notify();
+			}
+		});
+	}
+};
+
+function observe(value, vm) {
+	if (!value || typeof value !== 'object') {
+		return ;
+	}
+
+	return new Observer(value);
+};
+
+
+var uid = 0;
+
+function Dep() {
+	this.id = uid++;
+	this.subs = [];
+}
+
+Dep.prototype = {
+	addSub: function(sub) {
+		this.subs.push(sub);
+	},
+
+	depend: function() {
+		Dep.target.addDep(this);
+	},
+
+	removeSub: function(sub) {
+		var index = this.subs.indexOf(sub);
+		if (index != -1) {
+			this.subs.splice(index, 1);
+		}
+	},
+
+	notify: function() {
+		this.subs.forEach(function(sub){
+			sub.update();
+		});
+	}
+};
+
+Dep.target = null;
+

+ 56 - 0
js/watcher.js

@@ -0,0 +1,56 @@
+function Watcher(vm, exp, cb) {
+	this.cb = cb;
+	this.vm = vm;
+	this.exp = exp;
+	this.depIds = {};
+	this.value = this.get();
+}
+
+Watcher.prototype = {
+	update: function() {
+		this.run();
+	},
+	run: function() {
+		var value = this.get();
+		var oldVal = this.value;
+		if (value !== oldVal) {
+			this.value = value;
+			this.cb.call(this.vm, value, oldVal);
+		}
+	},
+	addDep: function(dep) {
+		// 1. 每次调用run()的时候会触发相应属性的getter
+		// getter里面会触发dep.depend(),继而触发这里的addDep
+		// 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
+		// 则不需要将当前watcher添加到该属性的dep里
+		// 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
+		// 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
+		// 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
+		// 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
+		// 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
+		// 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
+		// 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
+		// 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
+		// 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep
+		// 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher
+		if (!this.depIds.hasOwnProperty(dep.id)) {
+			dep.addSub(this);
+			this.depIds[dep.id] = dep;
+		}
+	},
+	get: function() {
+		Dep.target = this;
+		var value = this.getVMVal();
+		Dep.target = null;
+		return value;
+	},
+
+	getVMVal: function() {
+		var exp = this.exp.split('.');
+		var val = this.vm._data;
+		exp.forEach(function(k) {
+			val = val[k];
+		});
+		return val;
+	}
+};

+ 51 - 0
mvvm.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>MVVM</title>
+</head>
+<body>
+
+<div id="mvvm-app">
+	<input type="text" v-model="someStr">
+	<input type="text" v-model="child.someStr">
+	<p v-class="className" class="abc">
+		{{someStr}}
+		<span v-text="child.someStr"></span>
+	</p>
+	<p v-html="child.htmlStr"></p>
+	<button v-on:click="clickBtn">change model</button>
+</div>
+
+	<script src="http://cdn.bootcss.com/vue/1.0.25/vue.js"></script>
+	<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: {
+				someStr: 'hello ',
+				className: 'btn',
+				htmlStr: '<span style="color: #f00;">red</span>',
+				child: {
+					someStr: 'World !'
+				}
+			},
+
+			methods: {
+				clickBtn: function(e) {
+					var randomStrArr = ['childOne', 'childTwo', 'childThree'];
+					this.child.someStr = randomStrArr[parseInt(Math.random() * 3)];
+				}
+			}
+		});
+
+		vm.$watch('child.someStr', function() {
+			console.log(arguments);
+		});
+	</script>
+
+</body>
+</html>

+ 156 - 0
readme.html

@@ -0,0 +1,156 @@
+<html><head>
+    <meta charset="utf-8">
+    <title>readme.md—C:\Users\kindeng\Desktop\mvvm</title>
+    <link rel="stylesheet" type="text/css" href="./css/github.css">
+  </head>
+  <body>
+    <div class="container">
+      <div id="markup">
+        <article id="content" class="markdown-body"><h2>透过Vue, 如何实现一个简单的mvvm双向数据绑定</h2>
+<h4>1、一个简单的Vue例子:<a href="./vue-demo/index.html">Hello World!</a></h4>
+<p><strong>code:</strong> </p>
+<div class="codehilite"><pre>&lt;div id="vue-app"&gt;
+    &lt;input type="text" v-model="word"&gt;
+    &lt;p&gt;{{word}}&lt;/p&gt;
+    &lt;button v-on:click="sayHi"&gt;change model&lt;/button&gt;
+&lt;/div&gt;
+
+&lt;script src="http://cdn.bootcss.com/vue/1.0.25/vue.js"&gt;&lt;/script&gt;
+&lt;script&gt;
+    var vm = new Vue({
+        el: '#vue-app',
+        data: {
+            word: 'Hello World!'
+        },
+
+        methods: {
+            sayHi: function() {
+                this.word = 'Hi, everybody!';
+            }
+        }
+    });
+&lt;/script&gt;
+</pre></div>
+
+
+<h4>2、如题,今天要跟大家分享的就是实现上面的功能,是这样子的:<a href="./mvvm.html">My mvvm</a></h4>
+<p><strong>code:</strong></p>
+<div class="codehilite"><pre>&lt;div id="mvvm-app"&gt;
+    &lt;input type="text" v-model="name"&gt;
+    &lt;input type="text" v-model="child.name"&gt;
+    &lt;p v-class="className" class="abc"&gt;
+        {{child.child.name}}
+    &lt;/p&gt;
+    &lt;span v-text="child.name"&gt;&lt;/span&gt;
+    &lt;p v-html="child.html"&gt;&lt;/p&gt;
+    &lt;button v-on:click="clickBtn"&gt;change model&lt;/button&gt;
+&lt;/div&gt;
+
+&lt;script src="./js/observer.js"&gt;&lt;/script&gt;
+&lt;script src="./js/watcher.js"&gt;&lt;/script&gt;
+&lt;script src="./js/compile.js"&gt;&lt;/script&gt;
+&lt;script src="./js/mvvm.js"&gt;&lt;/script&gt;
+&lt;script&gt;
+    var vm = new MVVM({
+        el: '#mvvm-app',
+        data: {
+            name: 'hello ',
+            className: 'btn',
+            spanText: 'hello world!',
+            child: {
+                name: '孩子名字',
+                html: '&lt;span style="color: #f00;"&gt;red&lt;/span&gt;',
+                child: {
+                    name: '孩子的孩子名字 '
+                }
+            }
+        },
+
+        methods: {
+            clickBtn: function(e) {
+                var randomStrArr = ['childOne', 'childTwo', 'childThree'];
+                this.child.name = randomStrArr[parseInt(Math.random() * 3)];
+            }
+        }
+    });
+&lt;/script&gt;
+</pre></div>
+
+
+<h4>3、目前实现数据绑定的几种做法</h4>
+<blockquote>
+<p>观察者模式(backbone.js)</p>
+<p>脏值检查(angular.js) </p>
+<p>数据劫持(vue.js) </p>
+</blockquote>
+<p>观察者模式一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常 <code>vm.set('property', value)</code>, 这种方式现在毕竟太low了,我们更希望通过 <code>vm.property = value</code>这种方式更新数据,同时自动更新视图。</p>
+<p>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> vue.js 则是数据劫持结合观察者模式,通过<code>Object.defineProperty()</code>来劫持各个属性的<code>setter</code>,<code>getter</code>,在数据变动时发布消息给订阅者,触发相应的监听回调。</strong></p>
+<h4>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>5、我们要实现的关键点:</h4>
+<ul>
+<li>数据监听 Observer.js 和消息订阅器 Dep.js</li>
+<li>订阅者 Watcher.js</li>
+<li>指令编译器 Compile.js</li>
+<li>入口 mvvm</li>
+</ul>
+<h4>6、一切基于此Object.defineProperty()</h4>
+<p><em>IE8+</em></p>
+<p><strong>code:</strong></p>
+<div class="codehilite"><pre>function defineReative(data) {
+    var val;
+    Object.defineProperty(data, 'name', {
+        enumerable: true,   // 可枚举
+        configurable: true, // 不能再define
+        get: function() {
+            return val;
+        },
+        set: function(newVal) {
+            console.log('你变了:', val, ' ==&gt; ', newVal);
+            val = newVal;
+        }
+    });
+}
+
+var data = {};
+defineReative(data);
+</pre></div>
+
+
+<h4>7、实现Observer.js</h4>
+<p>负责监听源数据所有属性,一旦发生变化,通知订阅者更新视图</p>
+<p><a href="./js/observer.js">code</a></p>
+<h4>8、实现Compile.js</h4>
+<p>负责解析模板指令,不同的指令绑定不同的处理回调及视图更新方法</p>
+<p><a href="./js/compile.js">code</a></p>
+<h4>9、如何连接observe 和 compile --&gt; watcher.js(桥梁)</h4>
+<p>充当数据更新的订阅者,每一个属性的变化都会通知它,在compile阶段实例化并注入回调函数</p>
+<p>每一个属性都有一个watcher</p>
+<p><a href="./js/watcher.js">code</a></p></article>
+      </div>
+    </div>
+  
+  <script type="text/x-omnimarkup-config;executed=true">
+    window.App.Context = {
+      buffer_id: 43,
+      timestamp: '1467358184.713298',
+      revivable_key: 'QzpcVXNlcnNca2luZGVuZ1xEZXNrdG9wXG12dm1ccmVhZG1lLm1k'
+    };
+    window.App.Options = {
+      ajax_polling_interval: 500,
+      mathjax_enabled: false
+    };
+  </script>
+  <script type="text/javascript" src="/public/jquery-2.1.3.min.js"></script>
+  <script type="text/javascript" src="/public/imagesloaded.pkgd.min.js"></script>
+  <script type="text/javascript" src="/public/app.js"></script>
+
+</body></html>

+ 154 - 0
readme.md

@@ -0,0 +1,154 @@
+## 透过Vue, 如何实现一个简单的mvvm双向数据绑定
+
+> 本文主要是在分析Vue源码的基础上,对其相关核心思想和逻辑进行简化,并通过实现一个简单的实现来阐述相关原理和思想,文中并不会涉及太多源码片段的解析,但其核心思想都会在文中表现出来,对阅读Vue源码会有更好的帮助,相信会让你的思路更加清晰~
+
+#### 1、一个简单的Vue例子:[Hello World!](./vue-demo/index.html)
+**code:** 
+```
+<div id="vue-app">
+	<input type="text" v-model="word">
+	<p>{{word}}</p>
+	<button v-on:click="sayHi">change model</button>
+</div>
+
+<script src="http://cdn.bootcss.com/vue/1.0.25/vue.js"></script>
+<script>
+	var vm = new Vue({
+		el: '#vue-app',
+		data: {
+			word: 'Hello World!'
+		},
+
+		methods: {
+			sayHi: function() {
+				this.word = 'Hi, everybody!';
+			}
+		}
+	});
+</script>
+```
+
+#### 2、如题,今天要跟大家分享的就是实现上面的功能,是这样子的:[My mvvm](./mvvm.html)
+**code:**
+```
+<div id="mvvm-app">
+	<input type="text" v-model="name">
+	<input type="text" v-model="child.name">
+	<p v-class="className" class="abc">
+		{{child.child.name}}
+	</p>
+	<span v-text="child.name"></span>
+	<p v-html="child.html"></p>
+	<button v-on:click="clickBtn">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: {
+			name: 'hello ',
+			className: 'btn',
+			spanText: 'hello world!',
+			child: {
+				name: '孩子名字',
+				html: '<span style="color: #f00;">red</span>',
+				child: {
+					name: '孩子的孩子名字 '
+				}
+			}
+		},
+
+		methods: {
+			clickBtn: function(e) {
+				var randomStrArr = ['childOne', 'childTwo', 'childThree'];
+				this.child.name = randomStrArr[parseInt(Math.random() * 3)];
+			}
+		}
+	});
+</script>
+```
+
+#### 3、目前实现数据绑定的几种做法
+
+
+> 观察者模式(backbone.js)
+> 
+> 脏值检查(angular.js) 
+> 
+> 数据劫持(vue.js) 
+
+观察者模式一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常 `vm.set('property', value)`, 这种方式现在毕竟太low了,我们更希望通过 `vm.property = value `这种方式更新数据,同时自动更新视图。
+
+angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 `setInterval()` 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:
+
+- DOM事件,譬如用户输入文本,点击按钮等。( ng-click ) 
+- XHR响应事件 ( $http ) 
+- 浏览器Location变更事件 ( $location ) 
+- Timer事件( $timeout , $interval ) 
+- 执行 $digest() 或 $apply()
+
+** vue.js 则是数据劫持结合观察者模式,通过`Object.defineProperty()`来劫持各个属性的`setter`,`getter`,在数据变动时发布消息给订阅者,触发相应的监听回调。**
+
+
+#### 4、Vue的生命周期 ([Github](https://github.com/vuejs/vue/))
+
+<img src="https://vuejs.org.cn/images/lifecycle.png" width="640px">
+
+
+#### 5、我们要实现的关键点:
+
+- 数据监听 Observer.js 和消息订阅器 Dep.js
+- 订阅者 Watcher.js
+- 指令编译器 Compile.js
+- 入口 mvvm
+
+#### 6、一切基于此Object.defineProperty()  
+*IE8+*
+
+**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);
+```
+
+
+#### 7、实现Observer.js
+
+负责监听源数据所有属性,一旦发生变化,通知订阅者更新视图
+
+[code](./js/observer.js)
+
+
+#### 8、实现Compile.js
+
+负责解析模板指令,不同的指令绑定不同的处理回调及视图更新方法
+
+[code](./js/compile.js)
+
+#### 9、如何连接observe 和 compile --> watcher.js(桥梁)
+
+充当数据更新的订阅者,每一个属性的变化都会通知它,在compile阶段实例化并注入回调函数
+
+每一个属性都有一个watcher
+
+[code](./js/watcher.js)
+

+ 30 - 0
vue-demo/index.html

@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>vue-demo</title>
+</head>
+<body>
+	<div id="vue-app">
+		<input type="text" v-model="word">
+		<p>{{word}}</p>
+		<button v-on:click="sayHi">change model</button>
+	</div>
+
+	<script src="http://cdn.bootcss.com/vue/1.0.25/vue.js"></script>
+	<script>
+		var vm = new Vue({
+			el: '#vue-app',
+			data: {
+				word: 'Hello World!'
+			},
+
+			methods: {
+				sayHi: function() {
+					this.word = 'Hi, everybody!';
+				}
+			}
+		});
+	</script>
+</body>
+</html>