<div id="app"> <div id="app"> <p>{{ a.a }}</p> <span>{{ b }}</span> </div> </div> <script> const vm = new Mvvm({ el: '#app', data: { a: { a: '我是a' }, b: '我是b' } }) </script>
答:经过Object.defineProperty()方法,对data中的属性,在访问或者修改对象的其中某个属性时,经过一段代码拦截这个行为,进行额外的操做或者修改返回结果node
// TODO:1.定义Mvvm类 function Mvvm(options={}){ // TODO:this表明的是fade实例对象 // TODO:将全部属性挂载到$options this.$options = options var data = this._data = this.options.data // TODO:调用数据劫持 observe(data) } // TODO:3.观察者 function Observe(obj){ for (let key in obj) { let val = obj[key] // TODO:深度劫持 observe(val) Object.defineProperty(obj,key,{ enumerable : true, get(){ return val }, set(newVal){ if (newVal === val) return val = newVal // TODO:深度劫持 observe(newVal) } }) } } // TODO:2.数据劫持-使每一个对象都具备get和set方法 function observe(vmData){ if (typeof data !== 'object') return return new Observe(vmData) }
答:对于每一个data上的属性,都在app上作一个代理,实际操做的是this.data
实现的代码以下:数组
function Mvvm(options = {}) { // TODO:this表明的是zhufeng实例对象 // TODO:将全部属性挂载到$options this.$options = options //this._data var data = this._data = this.$options.data observe(data) // TODO:4.数据代理 for (let key in data) { Object.defineProperty(this,key,{ enumerable : true, get(){ return this._data[key] }, set(newVal){ this._data[key] = newVal } }) } }
答:经过获取vm管理DOM的根节点,让其在内存中完成相关的正则匹配工做,替换DOM中的文本节点缓存
// TODO:5数据编译 function Compile(el,vm){ vm.$el = document.querySelector(el) let fragment = document.createDocumentFragment() while (child = vm.$el.firstChild) { fragment.appendChild(child) } replace(fragment) // TODO:6.数据替换 function replace(frag){ Array.from(frag.childNodes).forEach(function (node) { let text = node.textContent let regExp = /\{\{(.*)\}\}/ if (node.nodeType === 1 && regExp.test(text)) { let arr = RegExp.$1.trim().split('.') let val = vm arr.forEach(function (k) { val = val[k] }) node.textContent = text.replace(regExp,val).trim() } if (node.childNodes && node.childNodes.length) { replace(node) } }) } vm.$el.appendChild(fragment) }
function Mvvm(options = {}) { .... // TODO:进行编译 new Compile(optionns.el,this) }
答:发布订阅主要靠的就是数组关系,订阅就是放入函数,发布就是让数组里的函数执行app
// TODO:8.发布订阅模式 // TODO:桥梁 function Dep(){ // 桥梁 this.subs = [] // 订阅事件池 } // TODO:进行订阅的方法(往里面扔函数) Dep.property.addSub = function (sub) { //sub就是watcher this.subs.push(sub) } // TODO:进行发布/通知的方法(让函数的每一项一次执行) Dep.prototype.notify = function () { this.subs.forEach(sub => sub.update())//绑定的事件,都有一个update属性 } // TODO:订阅者 function Watcher(fn){ //Watcher是一个类,经过这个类建立的实例都拥有update方法 this.fn = fn } Watcher.prototype.update = function () { //调用fn() this.fn() }
答:如今咱们要订阅一个事件,当数据改变须要从新刷新视图,这就须要在replace替换的逻辑里来处理
经过new Watcher把数据订阅一下,数据一变就执行改变内容的操做函数
// TODO:6.数据替换 function replace(frag) { ... node.textContent = text.replace(regExp, val).trim() // TODO:监听变化 new Watcher(vm,RegExp.$1,function (newVal) { node.textContent = text.replace(regExp,newVal).trim() }) if (node.childNodes && node.childNodes.length) { replace(node) } ... }
// TODO:订阅者 function Watcher(vm,exp,fn){ //Watcher是一个类,经过这个类建立的实例都拥有update方法 this.fn = fn this.vm = vm this.exp = exp Dep.target = this let arr = exp.trim().split('.') let val = vm arr.forEach(function (key) { val = val[key] }) Dep.target = null // // 上面获取val[key]的时候会调用get方法, 所以使用完毕以后须要把该属性置为null }
重写数据劫持get和set方法this
// TODO:3.观察者 function Observe(obj){ // TODO:建立桥梁 let dep = new Dep() for (let key in obj) { let val = obj[key] // TODO:深度劫持 observe(val) Object.defineProperty(obj,key,{ enumerable : true, get(){ // TODO:将watcher添加到订阅事件中 [watcher] Dep.target && dep.addSub(Dep.target) return val }, set(newVal){ // 更改值得时候 if (newVal === val) return // 设置的值和之前的是同样的东西 val = newVal // 若是之后在获取值的时候将刚才设置的值丢回去 // TODO:深度劫持 observe(newVal) // TODO:执行update方法 dep.notify() } }) } }
修改watcher的update方法spa
Watcher.prototype.update = function () { //调用fn() this.fn() // notify的时候值已经更改了 // 再经过vm, exp来获取新的值 let arr = this.exp.trim().split('.') let val = this.vm arr.forEach(function(key){ val = val[key] }) this.fn(val) // 将每次拿到的新值去替换{{}}的内容便可 }
未完待续...prototype