<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <input type="text" value="" v-model="user.name"/> {{user.name}}哈哈{{user.sex}} <select v-model="selected"> <option value="">请选择学校</option> <option value="a">学校A</option> <option value="b">学校B</option> </select> <div> <div style="display: inline-block;">性别</div> <div> <label for="man">男:</label> <input id="man" type="radio" v-model="user.sex" value="man" name="sex"> <label for="woman">女:</label> <input id="woman" type="radio" v-model="user.sex" value="woman" name="sex"> </div> </div> </div> <script src="vm.js"></script> <script> let vm = new Vue({ el:'#app' ,data:{ user:{ name:'杨文宇' ,sex:'man' } ,selected:'a' } }) </script> </body> </html>
class Vue{ constructor(options){ this.$el = Vue.utils.getEl(options.el); this.$data = options.data; new Observe(this,this.$data); new Compile(this,this.$el); } static utils = { getEl(selector){ return selector.nodeType == Node.ELEMENT_NODE ? selector : document.querySelector(selector) } ,isDirect(attrName){ return /^v-/.test(attrName) } ,isTxtTpl(node){ return node.nodeType == Node.TEXT_NODE && /^s*{{.*}}s*$/.test(node.textContent) } ,getVal(data,expOrFn){ return expOrFn.split(".").reduce((data,cur)=>{ return data[cur]; },data); } ,setVal(data,expOrFn,value){ expOrFn.split(".").reduce((data,cur,index,arr)=>{ if(index == arr.length-1){ return data[cur] = value; } return data[cur]; },data); } } } //劫持类 class Observe{ constructor(vm,data){ this.observer(data); //创建代理 this.dataProxy(vm,data); } //对数据进行劫持 observer(data){ Object.keys(data).forEach(key=>{ this.reactive(data,key,data[key]) }) } //响应式函数 reactive(obj,key,value){ typeof value == 'object' && this.observer(value) let _self = this //为每一个属性创建依赖容器,因为有可能一个属性会被多个地方依赖 , dep = new Dep(); Object.defineProperty(obj,key,{ get(){ //添加订阅者 dep.depend(); return value; } ,set(newVal){ if(value == newVal){return} typeof newVal == 'object' && _self.observer(newVal); value = newVal; dep.notify(); } }) } dataProxy(obj,data){ Object.keys(data).forEach(key=>{ //let value = data[key]; typeof value == 'object' && this.dataProxy(obj,value) Object.defineProperty(obj,key,{ get(){ return data[key] } ,set(newVal){ data[key] = newVal; } }) }) } } class Compile{ constructor(vm,el){ this.el = el; this.vm = vm; this.ready(); } static tagAttrEvent = { text:['value','input'] ,textarea:['value','input'] ,checkbox:['checked','change'] ,radio:['checked','change'] ,'select-one':['value','change'] ,'select-multiple':['value','change'] } //指令集 static directSet = { model(vm,node,expOrFn){ let tag = Compile.tagAttrEvent[node.type]; new Watcher(vm,expOrFn,newVal=>{ node[tag[0]] = newVal; }); node[tag[0]] = Vue.utils.getVal(vm.$data,expOrFn); node.addEventListener(tag[1],e=>{ Vue.utils.setVal(vm.$data,expOrFn,e.target[tag[0]]) }) } ,text(vm,node,expOrFn){ let content = expOrFn.replace(/{{(.+?)}}/g,(...arg)=>{ new Watcher(vm,arg[1],newVal=>{ node.textContent = this.getContentVal(vm,expOrFn); }) return Vue.utils.getVal(vm.$data,arg[1]) }) node.textContent = content; } ,getContentVal(vm,expOrFn){ return expOrFn.replace(/{{(.+?)}}/g,(...arg)=>{ let value = Vue.utils.getVal(vm.$data,arg[1]) return typeof value == 'object'? JSON.stringify(value) : value; }) } } ready(){ let fragment = this.node2fragment(this.el) this.compiler(fragment); this.el.appendChild(fragment); } //编译 compiler(mountEl){ let childNodes = [...mountEl.childNodes]; childNodes.forEach(node=>{ //获取不是文本节点或不是空文本的节点 if(node.nodeType == Node.ELEMENT_NODE){ [...node.attributes].forEach(attr=>{ let {name,value:expOrFn} = attr; if(!Vue.utils.isDirect(name)){return} Compile.directSet[name.split('-')[1]](this.vm,node,expOrFn) }) } if(Vue.utils.isTxtTpl(node)){ Compile.directSet['text'](this.vm,node,node.textContent) } node.childNodes.length > 0 && this.compiler(node) }) } //将节点放到文档碎片流中 node2fragment(el){ let fragment = new DocumentFragment() , child; while(child = el.firstChild){ fragment.appendChild(child) } return fragment; } } /** * 依赖收集--->收集的就是订阅对象 */ class Dep{ static target = null; constructor(){ this.subs = []; } //添加订阅者 //谁用到这个数据,谁就是订阅者,反映在html中就是使用数据的这个dom元素 addSub(watcher){ this.subs.push(watcher) } depend(){ Dep.target && this.subs.indexOf(Dep.target)<=-1 && this.addSub(Dep.target) } //通知所有订阅者 notify(){ this.subs.forEach(sub=>{ sub.update(); }) } } /** * 谁用到这个数据,谁依赖这个数据,谁就是订阅者 * 订阅者-->更新视图 */ class Watcher{ constructor(vm,expOrFn,cb){ this.vm = vm; this.expOrFn = expOrFn; this.cb = cb; this.oldVal = this.get() } get(){ Dep.target = this; let value = Vue.utils.getVal(this.vm.$data,this.expOrFn); Dep.target = null; return value; } //更新视图 update(){ let value = Vue.utils.getVal(this.vm.$data,this.expOrFn); if(this.oldVal != value){ this.cb(value); this.oldVal = value } } }