
- observer:监听者,监听视图模型data的变化
- 检测到变化,触发Object.defineProperty下面的set函数,循环通知观察者列表中的成员
- Dep:观察者列表,接收通知以后执行watcher给的回调
- watcher添加观察者列表
- 重点:三个角色是重点

- 关于dom的操作在mounted之前都不能实现(this.$refs)
- 数据双向绑定渲染在created里面以及之后的生命周期里实现
- 关于dom的操作如果一定要写在mounted之前,则需使用this.$nextTick()
index.html
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>MVVM</title></head><body><div id="app"><h2>{{title}}</h2><button v-on:click="clickMe">点我</button><br/><input type="text" v-model="name"><h1>{{name}}</h1></div><script src="js/observer.js"></script><script src="js/watcher.js"></script><script src="js/compile.js"></script><script src="js/index.js"></script><script>new Vue({el:'#app',data:{title:'vue source code',name:'mooc',// age:10},methods:{clickMe(){this.title="vue click "}},// mounted:function(){// window.setTimeout(()=>{// this.title="3000s later"// },3000)// }})</script></body></html>
index.js
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.defineProperty(this, key, {enumerable: false,configurable: true,get: function () {console.log("get",key)return self.data[key];},set: function (newVal) {console.log("set",key)self.data[key] = newVal;},});},};
observer.js
function Observer (data) {this.data = data;this.walk(data);}Observer.prototype = {walk: function (data) {var self = this;Object.keys(data).forEach(function (key) {self.defineReactive(data, key, data[key]);});},defineReactive: function (data, key, val) {var dep = new Dep();var childObj = observe(val);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 observe (value, vm) {if (!value || typeof value !== 'object') {return;}return new Observer(value);}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;
watcher.js
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;},};
compile.js
function Compile (el, vm) {this.vm = vm;this.el = document.querySelector(el);this.fragment = null;this.init();}Compile.prototype = {init: function () {if (this.el) {this.fragment = this.nodeToFragment(this.el);this.compileElement(this.fragment);this.el.appendChild(this.fragment);} else {console.log('Dom元素不存在');}},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(text)[1]);}if (node.childNodes && node.childNodes.length) {self.compileElement(node);}});},compile: function (node) {var nodeAttrs = node.attributes;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);}});},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);});},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;});},updateText: function (node, value) {node.textContent = typeof value === 'undefined' ? '' : value;},modelUpdater: function (node, value, oldValue) {node.value = typeof value === 'undefined' ? '' : value;},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;},};
