1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"></div> <script> function h(tag,props,children){ return { tag, props, children } } function mount(vnode,container){ //创建相应的真实DOM结点 const el=vnode.el=document.createElement(vnode.tag); //props if(vnode.props){ for(const key in vnode.props){ const value=vnode.props[key]; if(key.startsWith('on')){ el.addEventListener(key.slice(2).toLowerCase(),value)//事件机制 }else{ el.setAttribute(key,value); } } } //children if(vnode.children){ if(typeof vnode.children==='string'){ el.textContent=vnode.children; } else{ vnode.children.forEach(child=>{ mount(child,el)//递归 }) } } container.appendChild(el); } function patch(n1,n2){ const el=n2.el=n1.el; if(n1.tag===n2.tag){ //props const oldProps=n1.props||{}; const newProps=n1.props||{}; for(const key in newProps){ const oldValue=oldProps[key]; const newValue=newProps[key] if(newValue!==oldValue){ el.setAttribute(key,newValue) } } for(const key in oldProps){ if(!key in newProps){ el.removeAttribute(key) } } //children const oldChildren=n1.children; const newChildren=n2.children; if(typeof newChildren==='string'){ if(typeof oldChildren==='string'){ if(newChildren!==oldChildren){ el.textContent=newChildren; } }else{ el.textContent=newChildren } }else { //if newChildren is not string if(typeof oldChildren==='string'){ el.innerHTML='' newChildren.forEach(child=>{ mount(child,el) }) }else{ const commonLength=Math.min(oldChildren.length,newChildren.length) for(let i=0;i<commonLength;i++){ patch(oldChildren[i],newChildren[i]) } if(newChildren.length>oldChildren.length){ newChildren.slice(oldChildren.length).forEach(child=>{ mount(child,el) }) }else if(newChildren.length<oldChildren.length){ oldChildren.slice(newChildren.length).forEach(child=>{ el.removeChild(child.el); }) } } }
}else{ //replace
}
} let activeEffect; class Dep{ subscribers=new Set() depend(){ if(activeEffect){ this.subscribers.add(activeEffect) } } notify(){ this.subscribers.forEach(effect=>{ effect() }) }
}
const targetMap=new WeakMap()//只接受键值是对象,不接受其他类型的值作为键值,本身不可以从任何代码访问,而且会被自动垃圾回收,不可以迭代 function getDep(target,key){ let depsMap=targetMap.get(target) if(!depsMap){ depsMap=new Map()//可以迭代键 targetMap.set(target,depsMap) } let dep=depsMap.get(key) if(!dep){ dep=new Dep() depsMap.set(key,dep) } return dep;
} const reactiveHandler={ get(target,key,receiver){ let dep=getDep(target,key) dep.depend() return Reflect.get(target,key,receiver)//Reflect不会抛出异常,只会抛出真假 }, set(target,key,value,receiver){ let dep=getDep(target,key) const result=Reflect.set(target,key,value,receiver) dep.notify() return result
} } function reactive(raw){ return new Proxy(raw,reactiveHandler)//Proxy 会触发set get 有利于数组观测,不必多写一些数组内置方法 }
function watchEffect(effect){ activeEffect=effect effect() activeEffect=null } const dep=new Dep() const App={ data:reactive({ count:0 }), render(){ return h('div',{ onClick:()=>{ this.data.count++; } }, String(this.data.count)) } } function mountApp(component,container){ let isMounted=false let prevVdom watchEffect(()=>{ if(!isMounted){ prevVdom=component.render() mount(prevVdom,container) isMounted=true }else{ const newVdom=component.render() patch(prevVdom,newVdom) prevVdom=newVdom } }) } mountApp(App,document.getElementById('app')) </script> </body> </html>
|