<div id="app"> <h2>{{title}}</h2> <input v-model="name"> <h1>{{name}}</h1> <button v-on:click="clickMe">click me!</buttuon> </div>
<script type="text/javascript"> new Vue({ el: '#app', data: { title: 'hello world', name: 'canfoo' }, methods: { clickMe: function () { this.title = 'hello world'; } } }) </script>
function Vue (options) { var self = this; this.data = options.data; this.methods = options.methods; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options.el, this); options.mounted.call(this);//全部事情處理好後執行mounted函數 } Vue.prototype = { proxyKeys: function(key) { var self = this; Object.defneProperty(this, key, { enumerable: false; configurable: true; get: function getter () { return self.data[key]; }, set: function setter (newVal) { self.data[key] = newVal; } }) } } function Observer(data) { Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }) } function defineReactive(data, key, val) { var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function getter () { if (Dep.target) { dep.addSub(Dep.target); } return val; }, set: function setter (newVal) { if (newVal === val) { return; } 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(); }); } }; Dep.target = null; function Compile(el, vm) { this.vm = vm; this.el = document.querySlector(el); this.fragment = null; this,init(); } Compile.prototype = { init: function() { this.fragment = this.nodeToFragment(this.el); this.compileElement(this.fragment); this.el.appendChild(this.fragment); }, nodeToFragment: function(el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { //將dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild; } return fragment; }, compileElement: function(el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isElementNode(node)) { self.compile(node); } else if (self.isTextNode(node) && reg.test(text)) { self.compileText(node, reg.exec(test)[1]); } if(node.childNodes && node.childNodes.length) { self.compileElement(node); } }) }, compile: function(node) { var nodeAttrs = node.atrributes; var self = this; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (self.isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); if(self.isEventDirective(dir)) {//事件指令 self.compileEvent(node, self.vm, exp, dir); }else {//v-model指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); } }) }, compileEvent: function(node, vm, exp, dir) { var eventType = dir.split(':')[1]; var cb = vm.methods && vm.methods[exp]; if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false); } }, compileModel: function (node, vm, exp, dir) { var self = this; var val = this.vm[exp]; this.modelUpdater(node, val); new Watcher(this.vm, exp, function(value) { self.modelUpdater(node, value); }) node.addEventListener('input', function(e) { var newValue = e.target.value; if (val === newValue) { return; } self.vm[exp] = newValue; val = newValue; }) } compileText: function(node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); new Watcher(this.vm, exp, function(value) { self.updateText(node, value); }) }, modelUpdater: function(node, value, oldValue) { node.value = typeof value == 'undefined'?'':value; } updateText: function(node, value) { node.textContent = typeof value == 'undefined'?'':value; }, isDirective: function(attr) { return attr.indexOf('v-') == 0; }, isElementNode: function(node) { return node.nodeType == 1; }, isTextNode: function(node) { return node.nodeType == 3; }, isEventDirective: function(dir) { return dir.indexOf('on:') === 0; } } function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get();//將本身添加到訂閱器的操做 } Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 緩存本身 var value = this.vm.data[this.exp] // 強制執行監聽器裏的get函數 Dep.target = null; // 釋放本身 return value; } }