视频地址:https://www.bilibili.com/video/BV1qJ411W7YR?from=search&seid=12050981068446870345
index.html
<!DOCTYPE html> <!-- saved from url=(0026)https://www.jingjibao.com/ --> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" /> <style> html, body { width: 100%; } </style> </head> <body> <div id="app"> <div>{{person.name}} --- {{person.age}}</div> <h3>{{person.fav}}</h3> <div>{{msg}}</div> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <div v-text="msg"></div> <div v-html="htmlStr"></div> <input type="text" v-model="msg" /> <button v-on:click="handlClick">点击</button> <button @click="handlClick2">点击2</button> </div> <script src="Observer.js"></script> <script src="Mvue.js"></script> <script> let vm = new Mvue({ el: "#app", data: { person: { name: "张三", age: "15", fav: "打架", }, msg: "vue源码解析", htmlStr: "<p>模板解析</p>", }, methods: { handlClick() { console.log("点击事件"); }, handlClick2() { this.$data.person.name = "学不动"; console.log("点击事件2"); console.log(this.$data); }, }, }); </script> </body> </html>
Mvue.js
class Mvue{ constructor(option){ this.$el = option.el; // 1.是字符串还是节点,有没有值 this.$data = option.data; this.$option = option; if(this.$el){ // 1、实现一个数据的观察者 new Observer(this.$data) // 2、实现一个数据解析器 if(this.$el){ new Compile(this.$el,this) } this.proxyData(this.$data) } console.log(this) } proxyData(data){ for(const key in data){ Object.defineProperty(this, key,{ get(){ return data[key] }, set(newVal){ data[key] = newVal; } }) } } } class Compile{ constructor(el,vm){ this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; //1、获取文档碎片化对象,放入内存会减少页面的回流和重绘 const fragment = this.node2Fragment(this.el); //2、编译模板 this.compile(fragment) //3、子元素追加到根元素 this.el.appendChild(fragment) } compile(fragment){ const childNode = fragment.childNodes; [...childNode].forEach(child=>{ //文本或标签 if(this.isElementNode(child)){ // console.log("元素节点",child) this.compileElement(child) }else{ this.compileText(child) // console.log("文本节点",child) } if(child.childNodes && child.childNodes.length){ this.compile(child) } }) } compileElement(node){ const attributes = node.attributes; [...attributes].forEach(attr=>{ const {name , value} = attr;
// console.log(attr) // v-html="htmlStr" v-text="msg"
// console.log(name) // v-html v-text
// console.log(value) // htmlStr msg
if(this.isDirective(name)){ //是一个指令,即v-开头 const [,directive] = name.split('-') //v-html,v-text const [dirName , eventName] = directive.split(':') //v-on:click console.log('dirName-'+dirName, 'node-'+node,'value-'+value,'this.vm-'+this.vm,'eventName-'+eventName ) //更新数据 数据驱动视图 complieUtil[dirName](node, value, this.vm, eventName) //删除有指令的标签上的属性 node.removeAttribute('v-'+directive) }else if(this.isEventName(name)){ let [, eventName] = name.split('@'); complieUtil['on'](node, value, this.vm, eventName) } }) } isDirective(attrName){ return attrName.startsWith('v-') } isEventName(attrName){ return attrName.startsWith('@') } compileText(node){ const content = node.textContent if((/{{(.+?)}}/).test(content)){ complieUtil['text'](node, content, this.vm) // console.log(content) } } isElementNode(node){ return node.nodeType === 1; } node2Fragment(el){ //创建文档碎片 const f = document.createDocumentFragment() //创建了一虚拟的节点对象,节点对象包含所有属性和方法。 let firstChild; while(firstChild = el.firstChild){ //循环,父节点第一个子节点,定义的firsr ,有值,为真,追加进去 f.appendChild(firstChild) //appendChid的移动性 页面没有,子节点追加到缓存变量中 } return f; } } const complieUtil = { getVal(expr,vm){ return expr.split('.').reduce((data,currentVal)=>{ // console.log(currentVal) return data[currentVal] },vm.$data) }, setVal(expr,vm, inputVal){ return expr.split('.').reduce((data,currentVal)=>{ data[currentVal] = inputVal; //新的值 // return data[currentVal] },vm.$data) }, getContentVal(expr, vm){ return expr.replace(/{{(.+?)}}/g, (...args) => { return this.getVal(args[1], vm) }) }, text(node, expr, vm){ //expr="msg" // const value = vm.$data[expr]; let value; if(expr.indexOf('{{') !== -1){ // console.log('///////') value = expr.replace(/{{(.+?)}}/g, (...args)=>{ new Watcher(vm, args[1], (newVal)=>{ this.updater.textUpdater(node, this.getContentVal(expr, vm)) }) return this.getVal(args[1],vm) }) }else{ value = this.getVal(expr, vm) } this.updater.textUpdater(node, value) }, html(node, expr, vm){ const value = this.getVal(expr, vm); new Watcher(vm, expr, (newVal)=>{ this.updater.htmlUpdater(node, newVal) }) this.updater.htmlUpdater(node, value) }, model(node, expr, vm){ const value = this.getVal(expr, vm); //绑定更新函数 数据=》视图 new Watcher(vm, expr, (newVal)=>{ this.updater.modelUpdater(node, newVal) }) // 视图 =》数据=》视图 node.addEventListener('input', (e)=>{ this.setVal(expr, vm, e.target.value) }) this.updater.modelUpdater(node, value) }, on(node, expr, vm, eventName){ // console.log(vm) let fn = vm.$option.methods && vm.$option.methods[expr] node.addEventListener(eventName, fn.bind(vm), false) }, updater:{ textUpdater(node, value){ node.textContent = value }, htmlUpdater(node, value){ node.innerHtml = value; }, modelUpdater(node, value){ node.value = value } } }
Observer.js
class Watcher{ //判断新值旧值有没有变化,有的话更新 // 通过回调函数实现更新的数据通知到视图 constructor(vm, expr, cb){ this.vm = vm; this.expr = expr; this.cb = cb; this.oldVal = this.getOldVal(); } // 获取旧数据 getOldVal(){ //有新值了,回调回去 // 在利用getValue获取数据调用getter()方法时先把当前观察者挂载 Dep.target = this; const oldVal = complieUtil.getVal(this.expr, this.vm); // 挂载完毕需要注销,防止重复挂载 (数据一更新就会挂载) Dep.target = null; //挂载完删除 return oldVal; } // 通过回调函数更新数据 update(){ const newVal = complieUtil.getVal(this.expr, this.vm) if(newVal !== this.oldVal){ this.cb(newVal) } } } // Dep类存储watcher对象,并在数据变化时通知watcher class Dep{ constructor(){ this.subs = []; } //收集观察者 addSub(watcher){ this.subs.push(watcher) } //通知观察者去更新 notify(){ console.log("通知了观察者",this.subs) this.subs.forEach(w=>w.update()) } } class Observer{ //劫持监听所有属性 constructor(data){ this.observe(data) } observe(data){ if(data && typeof data === 'object'){ Object.keys(data).forEach((key)=>{ this.defineReactive(data,key,data[key]) }) } } defineReactive(obj, key, value){ //劫持数据 //递归遍历 value可能是对象 this.observe(value); const dep = new Dep() Object.defineProperty(obj, key, { //Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 enumerable:true, //是否可遍历 configurable:false, //是否可以更改编写 get(){ //订阅数据变化时,往dep添加观察者 Dep.target && dep.addSub(Dep.target) return value }, // 采用箭头函数在定义时绑定this的定义域(拿到this) set:(newVal)=>{ this.observe(newVal); //去劫持新的值 if(newVal !== value){ value = newVal } //更改完之后,通知变化 dep.notify(); } }) } }
自己的理解:
compile:解析指令,如解析v-hml,v-text,{{}},v-model
dep的作用:1、存放监听者。2、通知;
observe:劫持监听属性数据,里面有set和get;
observe,get监听到属性数据,就会增加监听者watcher(每个数据有自己的监听者);set检测到数据变化时,会通知dep(dep.notify),dep通知对应的watcher去更新,watcher比较新旧值变化,执行回调,把新值重新赋给节点
watcher比较新旧值变化:watcher旧值是从要改变的节点拿的,新值是知道数据更新后从节点拿的