<p><code></p> <p>最近在团队内做了一次vue原理分享,现场手写了一个乞丐版mvvm,这里记录一下这个mvvm实现的过程。</p> <p>源码:<a href="https://github.com/keller35/mvvm" rel="nofollow noreferrer" target="_blank">https://github.com/keller35/mvvm</a></p> <p>这个mvvm是基于发布订阅模式实现(也是vue本身的实现原理),最终达到的效果如下:</p> <p><span class="img-wrap"><img data-src="/img/bVbeuuy?w=320&amp;h=72" src="/img/bVbeuuy?w=320&amp;h=72" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span></p> <p>使用方式也跟vue一样:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="<html lang=&quot;en&quot;><br /> <head><br /> <meta charset=&quot;UTF-8&quot;><br /> <title>mvvm</title><br /> </head><br /> <body></p> <div id=&quot;app&quot;> <input type=&quot;text&quot; v-model=&quot;text&quot;><br /> {{ text }}<br /> <button @click=&quot;reset&quot; style=&quot;display:block;&quot;>重置</button> </div> <p> <script src=&quot;./index.js&quot;></script><br /> <script> var vm = new Mvvm({ el: 'app', data: { text: 'hello world' }, methods: { reset() { <a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>.text = ''; }, }, }); </script><br /> </body><br /> </html>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>mvvm<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name"><a href="http://www.js-code.com/tag/div" title="浏览关于“div”的文章" target="_blank" class="tag_link">div</a></span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"text"</span>&gt;</span> {{ text }} <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"reset"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display:block;"</span>&gt;</span>重置<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./index.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="actionscript"> <span class="hljs-keyword">var</span> vm = <span class="hljs-keyword">new</span> Mvvm({ el: <span class="hljs-string">'app'</span>, data: { text: <span class="hljs-string">'hello world'</span> }, methods: { reset() { <span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>.text = <span class="hljs-string">''</span>; }, }, }); </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre> <p>实现很简单:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="class Mvvm { constructor(options) { const { el, data, methods } = options; this.methods = methods; this.target = null; // 初始化dispatcher this.observe(this, data); // 初始化watcher this.compile(document.getElementById(el)); } observe(root, data) { for (const key in data) { this.defineReactive(root, key, data[key]); } } defineReactive(root, key, value) { if (typeof value == 'object') { return this.observe(value, value); } const dep = new Dispatcher(); Object.defineProperty(root, key, { set(newValue) { if (value == newValue) return; value = newValue; // 发布 dep.notify(newValue); }, get() { // 订阅 dep.add(this.target); return value; } }); } compile(dom) { const nodes = dom.childNodes; for (const node of nodes) { // 元素节点 if (node.nodeType == 1) { const attrs = node.attributes; for (const attr of attrs) { if (attr.name == 'v-model') { const name = attr.value; node.addEventListener('input', e => {<br /> this[name] = e.target.value;<br /> });<br /> this.target = new Watcher(node, 'input');<br /> this[name];<br /> }<br /> if (attr.name == '@click') {<br /> const name = attr.value;<br /> node.addEventListener('click', this.methods[name].bind(this));<br /> }<br /> }<br /> }<br /> // text节点<br /> if (node.nodeType == 3) {<br /> const reg = /{{(.*)}}/;<br /> const match = node.nodeValue.match(reg);<br /> if (match) {<br /> const name = match[1].trim();<br /> this.target = new Watcher(node, 'text');<br /> this[name];<br /> }<br /> }<br /> }<br /> }<br /> }</p> <p>class Dispatcher {<br /> constructor() {<br /> this.watchers = [];<br /> }<br /> add(watcher) {<br /> this.watchers.push(watcher);<br /> }<br /> notify(value) {<br /> this.watchers.forEach(watcher => watcher.update(value));<br /> }<br /> }</p> <p>class Watcher {<br /> constructor(node, type) {<br /> this.node = node;<br /> this.type = type;<br /> }<br /> update(value) {<br /> if (this.type == 'input') {<br /> this.node.value = value;<br /> }<br /> if (this.type == 'text') {<br /> this.node.nodeValue = value;<br /> }<br /> }<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Mvvm</span> </span>{ <span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a>ructor</span>(options) { <span class="hljs-keyword">const</span> { el, data, methods } = options; <span class="hljs-keyword">this</span>.methods = methods; <span class="hljs-keyword">this</span>.target = <span class="hljs-literal">null</span>; <span class="hljs-comment">// 初始化dispatcher</span> <span class="hljs-keyword">this</span>.observe(<span class="hljs-keyword">this</span>, data); <span class="hljs-comment">// 初始化watcher</span> <span class="hljs-keyword">this</span>.compile(<span class="hljs-built_in">document</span>.getElementById(el)); } observe(root, data) { <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> key <span class="hljs-keyword">in</span> data) { <span class="hljs-keyword">this</span>.define<a href="http://www.js-code.com/tag/react" title="浏览关于“React”的文章" target="_blank" class="tag_link">React</a>ive(root, key, data[key]); } } defineReactive(root, key, value) { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value == <span class="hljs-string">'object'</span>) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.observe(value, value); } <span class="hljs-keyword">const</span> dep = <span class="hljs-keyword">new</span> Dispatcher(); <span class="hljs-built_in">Object</span>.define<a href="http://www.js-code.com/tag/prop" title="浏览关于“Prop”的文章" target="_blank" class="tag_link">Prop</a>erty(root, key, { set(newValue) { <span class="hljs-keyword">if</span> (value == newValue) <span class="hljs-keyword">return</span>; value = newValue; <span class="hljs-comment">// 发布</span> dep.notify(newValue); }, get() { <span class="hljs-comment">// 订阅</span> dep.add(<span class="hljs-keyword">this</span>.target); <span class="hljs-keyword">return</span> value; } }); } compile(dom) { <span class="hljs-keyword">const</span> <a href="http://www.js-code.com/tag/node" title="浏览关于“node”的文章" target="_blank" class="tag_link">node</a>s = dom.childNodes; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> node <span class="hljs-keyword">of</span> nodes) { <span class="hljs-comment">// 元素节点</span> <span class="hljs-keyword">if</span> (node.nodeType == <span class="hljs-number">1</span>) { <span class="hljs-keyword">const</span> attrs = node.attributes; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> attr <span class="hljs-keyword">of</span> attrs) { <span class="hljs-keyword">if</span> (attr.name == <span class="hljs-string">'v-model'</span>) { <span class="hljs-keyword">const</span> name = attr.value; node.addEventListener(<span class="hljs-string">'input'</span>, e =&gt; { <span class="hljs-keyword">this</span>[name] = e.target.value; }); <span class="hljs-keyword">this</span>.target = <span class="hljs-keyword">new</span> Watcher(node, <span class="hljs-string">'input'</span>); <span class="hljs-keyword">this</span>[name]; } <span class="hljs-keyword">if</span> (attr.name == <span class="hljs-string">'@click'</span>) { <span class="hljs-keyword">const</span> name = attr.value; node.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-keyword">this</span>.methods[name].bind(<span class="hljs-keyword">this</span>)); } } } <span class="hljs-comment">// text节点</span> <span class="hljs-keyword">if</span> (node.nodeType == <span class="hljs-number">3</span>) { <span class="hljs-keyword">const</span> reg = <span class="hljs-regexp">/{{(.*)}}/</span>; <span class="hljs-keyword">const</span> match = node.nodeValue.match(reg); <span class="hljs-keyword">if</span> (match) { <span class="hljs-keyword">const</span> name = match[<span class="hljs-number">1</span>].trim(); <span class="hljs-keyword">this</span>.target = <span class="hljs-keyword">new</span> Watcher(node, <span class="hljs-string">'text'</span>); <span class="hljs-keyword">this</span>[name]; } } } } } <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Dispatcher</span> </span>{ <span class="hljs-keyword">constructor</span>() { <span class="hljs-keyword">this</span>.watchers = []; } add(watcher) { <span class="hljs-keyword">this</span>.watchers.push(watcher); } notify(value) { <span class="hljs-keyword">this</span>.watchers.forEach(<span class="hljs-function"><span class="hljs-params">watcher</span> =&gt;</span> watcher.update(value)); } } <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Watcher</span> </span>{ <span class="hljs-keyword">constructor</span>(node, type) { <span class="hljs-keyword">this</span>.node = node; <span class="hljs-keyword">this</span>.type = type; } update(value) { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.type == <span class="hljs-string">'input'</span>) { <span class="hljs-keyword">this</span>.node.value = value; } <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.type == <span class="hljs-string">'text'</span>) { <span class="hljs-keyword">this</span>.node.nodeValue = value; } } } </code></pre> <p>原理:</p> <ol> <li>最根本的原理很简单,无非是基于发布订阅的消息通知模式,消息发出方来自mvvm中modal层的变法,而订阅方来自view层。</li> <li>modal层的变化,是通过对data设置setter来实现响应式,只要数据发生变化,通知所有订阅者。</li> <li>view层的订阅,则是在compile阶段,compile会对所有数据依赖进行收集,然后在getter中注册监听。</li> </ol> <p></code></p>

本文固定链接: http://www.js-code.com/node-js/node-js_35378.html