数据劫持,订阅者模式,双向绑定
//index.html文件 <!DOCTYPE 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>Document</title> <script src="./cvue.js"></script> </head> <body> <div id="app"> {{message}} <!-- <p>{{message}}</p> --> <input type="text" c-model="name" />{{name}} </div> </body> </html> <script> let vm = new Cvue({ el : '#app', data : { message : '测试数据', name : 'cher' } }) vm._data.message = '11'; vm._data.name = 'chun' </script> //cvue.js文件 class Cvue{ constructor(options){ this.$options = options; //为什么加 $;因为防止冲突,和vm里面的数据做区分 this._data = options.data; this.observer(this._data); //数据劫持 this.compile(options.el); } observer(data){ Object.keys(data).forEach(key => { //为每个data属性进行数据劫持 let value = data[key]; let dep = new Dep(); Object.defineProperty(data,key,{ configurable : true, enumerable : true, get(){ if(Dep.target){ //添加一个订阅者,必须要触发get (2) dep.addSub(Dep.target) } return value; }, set(newValue){ value = newValue; dep.notify(newValue); //提醒订阅者 (3) } }) }) } compile(el){ let element = document.querySelector(el); //获取 #app节点 this.compileNode(element); } compileNode(element){ let childNodes = element.childNodes; //获取 #app下的所有子节点 Array.from(childNodes).forEach((node) => { if(node.nodeType == 3){ //文本节点 let nodeContent = node.textContent; //文本内容 let reg = /\{\{\s*(\S*)\s*\}\}/; //匹配文本节点 if(reg.test(nodeContent)){ node.textContent = this._data[RegExp.$1]; //替换文本 new Watcher(this,RegExp.$1,newValue => { node.textContent = newValue; //更新视图 }); //初次渲染订阅者,this指Cvue (1) } }else if(node.nodeType == 1){ //标签节点 //双向绑定 c-model let attrs = node.attributes; Array.from(attrs).forEach(attr => { let attrName = attr.name; let attrValue = attr.value; if(attrName.indexOf('c-') == 0){ attrName = attrName.substr(2); if(attrName == 'model'){ node.value = this._data[attrValue]; //把data里的值放到c-model的值里 } node.addEventListener('input',e => { //c-model值改变的时候data里的值也随着改变 this._data[attrValue] = e.target.value }) new Watcher(this,attrValue,newValue => { //双向绑定 node.value = newValue; }); } }) } if(node.childNodes.length > 0){ //递归 this.compileNode(node); } }) } } class Dep{ //发布者 constructor(){ this.subs = []; //要订阅的数据 } addSub(sub){ this.subs.push(sub); //添加那些订阅者 } notify(newValue){ //提醒所有订阅者更新 this.subs.forEach(v => { v.update(newValue); }) } } class Watcher{ //订阅者 constructor(vm,exp,cb){ Dep.target = this; //为了判断是否有watcher;这个this就是订阅者实例化的类 this.cb = cb; vm._data[exp]; //触发get Dep.target = null; //不在重复添加 } update(newValue){ this.cb(newValue); console.log('更新了'); } }