脚本宝典收集整理的这篇文章主要介绍了

试着用Proxy 实现一个简单mvvm

脚本宝典小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助你少写一行代码,多一份安全和惬意。
<p><code></p> <h3 id="articleHeader0">Proxy、Reflect的简单概述</h3> <blockquote><p>Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。<br />出自阮一峰老师的<a href="http://www.js-code.com/tag/ecmascript" title="ECMAScript" target="_blank">ECMAScript</a> 6 入门,详细点击<a href="http://es6.ruanyifeng.com/#docs/proxy" rel="nofollow noreferrer" target="_blank">http://es6.ruanyifeng.com/#docs/proxy</a> </p></blockquote> <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="var obj = new Proxy({}, { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-keyword"><a href="http://www.js-code.com/tag/var" title="浏览关于“var”的文章" target="_blank" class="tag_link">var</a></span> obj = <span class="hljs-keyword"><a href="http://www.js-code.com/tag/new" title="浏览关于“new”的文章" target="_blank" class="tag_link">new</a></span> <span class="hljs-built_in">Proxy</span>({}, { <span class="hljs-attr">get</span>: <span class="hljs-function"><span class="hljs-keyword"><a href="http://www.js-code.com/tag/function" title="浏览关于“function”的文章" target="_blank" class="tag_link">function</a></span> (<span class="hljs-params">target, key, receiver</span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`gett<a href="http://www.js-code.com/tag/in" title="浏览关于“in”的文章" target="_blank" class="tag_link">in</a>g <span class="hljs-subst">${key}</span>!`</span>); <span class="hljs-keyword"><a href="http://www.js-code.com/tag/return" title="浏览关于“return”的文章" target="_blank" class="tag_link">return</a></span> <span class="hljs-built_in">Reflect</span>.get(target, key, receiver); }, <span class="hljs-attr">set</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">target, key, value, receiver</span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`setting <span class="hljs-subst">${key}</span>!`</span>); <span class="hljs-keyword">return</span> <span class="hljs-built_in">Reflect</span>.set(target, key, value, receiver); } });</code></pre> <p>上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。</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="obj.count = 1 // setting count! ++obj.count // getting count! // setting count! // 2" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs swift"><code>obj.<span class="hljs-built_in">count</span> = <span class="hljs-number">1</span> <span class="hljs-comment">// setting count!</span> ++obj.<span class="hljs-built_in">count</span> <span class="hljs-comment">// getting count!</span> <span class="hljs-comment">// setting count!</span> <span class="hljs-comment">// 2</span></code></pre> <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="var proxy = new Proxy(target, handler);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code style="word-break: break-word; white-space: initial;"><span class="hljs-keyword">var</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target, handler);</code></pre> <p>这里有两个参数,<code>target</code>参数表示所要拦截的目标对象,<code>handler</code>参数也是一个对象,用来定制拦截行为。</p> <blockquote><p>注意,要使得<code>Proxy</code>起作用,必须针对<code>Proxy</code>实例(上例是<code>proxy</code>对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。</p></blockquote> <p><code>Reflect对</code>象与<code>Proxy</code>对象一样,也是 <code>ES6</code> 为了操作对象而提供的新<code> <a href="http://www.js-code.com/tag/api" title="API" target="_blank">API</a></code>。</p> <p><code>Reflect</code>对象的方法与<code>Proxy</code>对象的方法一一对应,只要是Proxy对象的方法,就能在<code>Reflect</code>对象上找到对应的方法。这就让<code>Proxy</code>对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管<code>Proxy</code>怎么修改默认行为,你总可以在<code>Reflect</code>上获取默认行为。</p> <p>同样也放上阮一峰老师的链接<a href="http://es6.ruanyifeng.com/#docs/reflect" rel="nofollow noreferrer" target="_blank">http://es6.ruanyifeng.com/#docs/reflect</a></p> <h3 id="articleHeader1">初始化结构</h3> <p>看到这里,我就当大家有比较明白<code>Proxy</code>(代理)是做什么用的,然后下面我们看下要做最终的图骗。<br /><span class="img-wrap"><img data-src="/img/remote/1460000015460482?w=564&amp;h=475" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>看到上面的图片,首先我们新建一个<code><a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a>dex.html</code>,然后里面的代码是这样子滴。很简单</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="<a href="http://www.js-code.com/tag/button" title="button" target="_blank">button</a>" <a href="http://www.js-code.com/tag/class" title="class" target="_blank">class</a>="copyCode code-tool" data-toggle="tooltip" data-placement="<a href="http://www.js-code.com/tag/top" title="top" target="_blank">top</a>" data-clipboard-<a href="http://www.js-code.com/tag/text" title="text" target="_blank">text</a>="<!DOCTYPE html><br /> <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;> <h1>开发语言:{{language}}</h1> <h2>组成部分:</h2> <ul> <li>{{makeUp.one}}</li> <li>{{makeUp.two}}</li> <li>{{makeUp.three}}</li> </ul> <h2>描述:</h2> <p>{{describe}}</p> <p>计算属性:{{sum}}</p> <p> <input placeholder=&quot;123&quot; v-module=&quot;language&quot; /> </div> <p><script> // 写法和<a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a>一样 <a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a> mvvm = <a href="http://www.js-code.com/tag/new" title="new" target="_blank">new</a> Mvvm({ el: '#app', data: { language: 'Javascript', makeUp: { one: '<a href="http://www.js-code.com/tag/ecmascript" title="ECMAScript" target="_blank">ECMAScript</a>', two: '<a href="http://www.js-code.com/tag/%e6%96%87%e6%a1%a3%e5%af%b9%e8%b1%a1%e6%a8%a1%e5%9e%8b" title="文档对象模型" target="_blank">文档对象模型</a>(<a href="http://www.js-code.com/tag/dom" title="DOM" target="_blank">DOM</a>)', three: '浏览器对象模型(BOM)' }, describe: '没什么产品是写不了的', a: 1, b: 2 }, computed: { sum() { <a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> <a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>.a + <a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>.b } }) </script><br /> </body><br /> </html><br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span> <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"><a href="http://www.js-code.com/tag/char" title="浏览关于“char”的文章" target="_blank" class="tag_link">char</a>set</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">h1</span>&gt;</span>开发语言:{{language}}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>组成部分:<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>{{makeUp.one}}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>{{makeUp.two}}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>{{makeUp.three}}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>描述:<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{describe}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>计算属性:{{sum}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"123"</span> <span class="hljs-attr">v-module</span>=<span class="hljs-string">"language"</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>&gt;</span><span class="actionscript"> <span class="hljs-comment">// 写法和<a href="http://www.js-code.com/tag/vue" title="浏览关于“Vue”的文章" target="_blank" class="tag_link">Vue</a>一样</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a></span> mvvm = <span class="hljs-keyword">new</span> Mvvm({ el: <span class="hljs-string">'#app'</span>, data: { language: <span class="hljs-string">'Javascript'</span>, makeUp: { one: <span class="hljs-string">'<a href="http://www.js-code.com/tag/ecmascript" title="浏览关于“ECMAScript”的文章" target="_blank" class="tag_link">ECMAScript</a>'</span>, two: <span class="hljs-string">'<a href="http://www.js-code.com/tag/%e6%96%87%e6%a1%a3%e5%af%b9%e8%b1%a1%e6%a8%a1%e5%9e%8b" title="浏览关于“文档对象模型”的文章" target="_blank" class="tag_link">文档对象模型</a>(<a href="http://www.js-code.com/tag/dom" title="浏览关于“DOM”的文章" target="_blank" class="tag_link">DOM</a>)'</span>, three: <span class="hljs-string">'浏览器对象模型(BOM)'</span> }, describe: <span class="hljs-string">'没什么产品是写不了的'</span>, a: <span class="hljs-number">1</span>, b: <span class="hljs-number">2</span> }, computed: { sum() { <span class="hljs-keyword">return</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>.a + <span class="hljs-keyword">this</span>.b } }) </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>看到上面的代码,大概跟<code>vue</code>长得差不多,下面去实现<code>Mvvm</code>这个构造函数</p> <h3 id="articleHeader2">实现Mvvm这个构造函数</h3> <p>首先声明一个<code>Mvvm</code>函数,<code><a href="http://www.js-code.com/tag/option" title="option" target="_blank">option</a>s</code>当作参数传进来,<code><a href="http://www.js-code.com/tag/option" title="option" target="_blank">option</a>s</code>就是上面代码的配置,里面有<code>el</code>、<code>data</code>、<code>computed</code>~~</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="function Mvvm(options = {}) { // 把options 赋值给this.$options this.$options = options // 把options.data赋值给this._data let data = this._data = this.$options.data let vm = initVm.call(this) return this._vm }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>function Mvvm(<a href="http://www.js-code.com/tag/option" title="浏览关于“option”的文章" target="_blank" class="tag_link">option</a>s = {}) { <span class="hljs-comment">// 把options 赋值给this.$options</span> <span class="hljs-keyword">this</span>.$options = options <span class="hljs-comment">// 把options.data赋值给this._data</span> <a href="http://www.js-code.com/tag/let" title="浏览关于“let”的文章" target="_blank" class="tag_link">let</a> <span class="hljs-keyword">data</span> = <span class="hljs-keyword">this</span>._data = <span class="hljs-keyword">this</span>.$options.<span class="hljs-keyword">data</span> let vm = initVm.c<a href="http://www.js-code.com/tag/all" title="浏览关于“all”的文章" target="_blank" class="tag_link">all</a>(<span class="hljs-keyword">this</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm }</code></pre> <p>上面Mvvm函数很简单,就是把<code>参数options</code> 赋值给<code><a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>.$options</code>、把<code>options.data</code>赋值给<code><a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>._data</code>、然后调用初始化<code><a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a>itVm</code>函数,并用<code>c<a href="http://www.js-code.com/tag/all" title="all" target="_blank">all</a></code>改变<code><a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a></code>的指向,方便<code>initVm</code>函操作。然后返回一个<code><a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>._vm</code>,这个是在<code>initVm</code>函数生成的。</p> <p>下面继续写<code>initVm</code>函数,</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=" function initVm () { this._vm = new Proxy(this, { // 拦截get get: (target, key, receiver) => {<br /> return this[key] || this._data[key] || this._computed[key]<br /> },<br /> // 拦截set<br /> set: (target, key, value) => {<br /> return Reflect.set(this._data, key, value)<br /> }<br /> })<br /> return this._vm<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code> function initVm () { <span class="hljs-keyword">this</span>._vm = new Proxy(<span class="hljs-keyword">this</span>, { <span class="hljs-comment">// 拦截get</span> <span class="hljs-keyword">get</span>: (target, key, receiver) =&gt; { <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>[key] || <span class="hljs-keyword">this</span>._data[key] || <span class="hljs-keyword">this</span>._computed[key] }, <span class="hljs-comment">// 拦截set</span> <span class="hljs-keyword">set</span>: (target, key, value) =&gt; { <span class="hljs-keyword">return</span> Reflect.<span class="hljs-keyword">set</span>(<span class="hljs-keyword">this</span>._data, key, value) } }) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm } </code></pre> <p>这个<code>init函数</code>用到<code>Proxy</code>拦截了,<code>this</code>对象,生产<code>Proxy</code>实例的然后赋值给<code>this._vm</code>,最后返回<code>this._vm</code>,</p> <blockquote><p>上面我们说了,要使得<code>Proxy</code>起作用,必须针对<code>Proxy</code>实例。</p></blockquote> <p>在代理里面,拦截了<code>get</code>和<code>set</code>,<code>get函数</code>里面,返回<code>this</code>对象的对应的<code>key</code>的值,没有就去<code>this._data</code>对象里面取对应的<code>key</code>,再没有去<code>this._computed</code>对象里面去对应的<code>key</code>值。<code>set函数</code>就是直接返回修改<code>this._data</code>对应<code>key</code>。</p> <p>做好这些各种拦截工作。我们就可以直接从实力上访问到我们相对应的值了。(mvvm使我们第一块代码生成的实例)</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="mvvm.b // 2 mvvm.a // 1 mvvm.language // &quot;Javascript&quot;" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs objectivec"><code>mvvm.b <span class="hljs-comment">// 2</span> mvvm.a <span class="hljs-comment">// 1</span> mvvm.language <span class="hljs-comment">// "Javascript"</span></code></pre> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460483?w=1150&amp;h=445" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>如上图看控制台。可以设置值,可以获取值,但是这不是响应式的。</p> <p>打开控制台看一下<br /><span class="img-wrap"><img data-src="/img/remote/1460000015460484?w=2012&amp;h=1032" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>可以详细的看到。只有<code>_vm</code>这个是<code>proxy</code>,我们需要的是,<code>_data</code>下面所有数据都是有拦截代理的;下面我们就去实现它。</p> <h3 id="articleHeader3">实现所有数据代理拦截</h3> <p>我们首先在<code>Mvvm</code>里面加一个<code>initObserve</code>,如下</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="function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) + initObserve.call(this, data) // 初始化data的Observe return this._vm }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>function Mvvm(options = {}) { <span class="hljs-keyword">this</span>.$options = options let <span class="hljs-keyword">data</span> = <span class="hljs-keyword">this</span>._data = <span class="hljs-keyword">this</span>.$options.<span class="hljs-keyword">data</span> let vm = initVm.call(<span class="hljs-keyword">this</span>) + initObserve.call(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">data</span>) <span class="hljs-comment">// 初始化data的Observe</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm }</code></pre> <p><code>initObserve</code>这个函数主要是把,<code>this._data</code>都加上代理。如下</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=" function initObserve(data) { this._data = observe(data) // 把所有observe都赋值到 this._data } // 分开这个主要是为了下面递归调用 function observe(data) { if (!data || typeof data !== 'object') return data // 如果不是对象直接返回值 return new Observe(data) // 对象调用Observe }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code> function initObserve(<span class="hljs-keyword">data</span>) { <span class="hljs-keyword">this</span>._data = observe(<span class="hljs-keyword">data</span>) <span class="hljs-comment">// 把所有observe都赋值到 this._data</span> } <span class="hljs-comment">// 分开这个主要是为了下面递归调用</span> function observe(<span class="hljs-keyword">data</span>) { <span class="hljs-keyword"><a href="http://www.js-code.com/tag/if" title="浏览关于“if”的文章" target="_blank" class="tag_link">if</a></span> (!<span class="hljs-keyword">data</span> || <a href="http://www.js-code.com/tag/typeof" title="浏览关于“typeof”的文章" target="_blank" class="tag_link">typeof</a> <span class="hljs-keyword">data</span> !== <span class="hljs-string">'object'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">data</span> <span class="hljs-comment">// 如果不是对象直接返回值</span> <span class="hljs-keyword">return</span> new Observe(<span class="hljs-keyword">data</span>) <span class="hljs-comment">// 对象调用Observe</span> }</code></pre> <p>下面主要实现Observe类</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="// Observe类 class Observe { constructor(data) { this.dep = new Dep() // 订阅类,后面会介绍 for (let key in data) { data[key] = observe(data[key]) // 递归调用子对象 } return this.proxy(data) } proxy(data) { let dep = this.dep return new Proxy(data, { get: (target, key, receiver) => {<br /> return Reflect.get(target, key, receiver)<br /> },<br /> set: (target, key, value) => {<br /> const result = Reflect.set(target, key, observe(value)) // 对于新添加的对象也要进行添加observe<br /> return result<br /> }<br /> })<br /> }<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code><span class="hljs-comment">// Observe类</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Observe</span> </span>{ <span class="hljs-keyword"><a href="http://www.js-code.com/tag/constructor" title="浏览关于“constructor”的文章" target="_blank" class="tag_link">constructor</a></span>(<span class="hljs-keyword">data</span>) { <span class="hljs-keyword">this</span>.dep = new Dep() <span class="hljs-comment">// 订阅类,后面会介绍</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/for" title="浏览关于“for”的文章" target="_blank" class="tag_link">for</a></span> (let key <span class="hljs-keyword">in</span> <span class="hljs-keyword">data</span>) { <span class="hljs-keyword">data</span>[key] = observe(<span class="hljs-keyword">data</span>[key]) <span class="hljs-comment">// 递归调用子对象</span> } <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.proxy(<span class="hljs-keyword">data</span>) } proxy(<span class="hljs-keyword">data</span>) { let dep = <span class="hljs-keyword">this</span>.dep <span class="hljs-keyword">return</span> new Proxy(<span class="hljs-keyword">data</span>, { <span class="hljs-keyword">get</span>: (target, key, receiver) =&gt; { <span class="hljs-keyword">return</span> Reflect.<span class="hljs-keyword">get</span>(target, key, receiver) }, <span class="hljs-keyword">set</span>: (target, key, value) =&gt; { <span class="hljs-keyword">const</span> result = Reflect.<span class="hljs-keyword">set</span>(target, key, observe(value)) <span class="hljs-comment">// 对于新添加的对象也要进行添加observe</span> <span class="hljs-keyword">return</span> result } }) } } </code></pre> <p>这样子,通过我们层层递归添加<code>proxy</code>,把我们的<code>_data</code>对象都添加一遍,再看一下控制台</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460485?w=1678&amp;h=818" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>很不错,<code>_data</code>也有<code>proxy</code>了,很王祖蓝式的完美。</p> <p>看到我们的html的界面,都是没有数据的,上面我们把数据都准备好了,下面我们就开始把数据结合到html的界面上。</p> <h3 id="articleHeader4">套数据,实现hmtl界面</h3> <p>先把计算属性这个html注释掉,后面进行实现</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="<a href="http://www.js-code.com/tag/button" title="button" target="_blank">button</a>" <a href="http://www.js-code.com/tag/class" title="class" target="_blank">class</a>="copyCode code-tool" data-toggle="tooltip" data-placement="<a href="http://www.js-code.com/tag/top" title="top" target="_blank">top</a>" data-clipboard-<a href="http://www.js-code.com/tag/text" title="text" target="_blank">text</a>="<!-- <p>计算属性:{{sum}}</p> -->" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code style="word-break: break-word; white-space: initial;"><span class="hljs-comment">&lt;!-- &lt;p&gt;计算属性:{{sum}}&lt;/p&gt; --&gt;</span></code></pre> <p>然后在Mvvm函数中增加一个编译函数,&#x2795;号表示是添加的函数</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="function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) + new Compile(this.$options.el, vm) // 添加一个编译函数 return this._vm }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>function Mvvm(options = {}) { <span class="hljs-keyword">this</span>.$options = options let <span class="hljs-keyword">data</span> = <span class="hljs-keyword">this</span>._data = <span class="hljs-keyword">this</span>.$options.<span class="hljs-keyword">data</span> let vm = initVm.call(<span class="hljs-keyword">this</span>) + new Compile(<span class="hljs-keyword">this</span>.$options.el, vm) <span class="hljs-comment">// 添加一个编译函数</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm }</code></pre> <p>上面我们添加了一个<code>Compile</code>的构造函数。把配置的<code>el</code>作为参数传机进来,把生成<code>proxy</code>的实例<code>vm</code>也传进去,这样子我们就可以拿到<code>vm</code>下面的数据,下面我们就去实现它。顺序读注释就可以了,很好理解</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 Compile { constructor (el, vm) { this.vm = vm // 把传进来的vm 存起来,因为这个vm.a = 1 没毛病 let element = document.querySelector(el) // 拿到 app 节点 let fragment = document.createDocumentFragment() // 创建fragment代码片段 fragment.append(element) // 把app节点 添加到 创建fragment代码片段中 this.replace(fragment) // 套数据函数 document.body.appendChild(fragment) // 最后添加到body中 } replace(frag) { let vm = this.vm // 拿到之前存起来的vm // 循环frag.childNodes Array.from(frag.childNodes).forEach(node => {<br /> let txt = node.textContent // 拿到文本 例如:&quot;开发语言:{{language}}&quot;<br /> let reg = /{{(.*?)}}/g // 定义匹配正则<br /> if (node.nodeType === 3 &amp;&amp; reg.test(txt)) {</p> <p> replaceTxt()</p> <p> function replaceTxt() {<br /> // 如果匹配到的话,就替换文本<br /> node.textContent = txt.replace(reg, (matched, placeholder) => {<br /> return placeholder.split('.').reduce((obj, key) => {<br /> return obj[key] // 例如:去vm.makeUp.one对象拿到值<br /> }, vm)<br /> })<br /> }<br /> }<br /> // 如果还有字节点,并且长度不为0<br /> if (node.childNodes &amp;&amp; node.childNodes.length) {<br /> // 直接递归匹配替换<br /> this.replace(node)<br /> }<br /> })<br /> }<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// 编译类</span> <span class="hljs-keyword">class</span> Compile { <span class="hljs-keyword">constructor</span> (<span class="hljs-params">el, vm</span>) { <span class="hljs-keyword">this</span>.vm = vm <span class="hljs-comment">// 把传进来的vm 存起来,因为这个vm.a = 1 没毛病</span> <span class="hljs-keyword">let</span> <a href="http://www.js-code.com/tag/element" title="浏览关于“element”的文章" target="_blank" class="tag_link">element</a> = <span class="hljs-built_in"><a href="http://www.js-code.com/tag/document" title="浏览关于“document”的文章" target="_blank" class="tag_link">document</a></span>.querySelector(el) <span class="hljs-comment">// 拿到 app 节点</span> <span class="hljs-keyword">let</span> fragment = <span class="hljs-built_in"><a href="http://www.js-code.com/tag/do" title="浏览关于“do”的文章" target="_blank" class="tag_link">do</a>cument</span>.createDocumentFragment() <span class="hljs-comment">// 创建fragment代码片段</span> fragment.append(element) <span class="hljs-comment">// 把app节点 添加到 创建fragment代码片段中</span> <span class="hljs-keyword">this</span>.replace(fragment) <span class="hljs-comment">// 套数据函数</span> <span class="hljs-built_in">document</span>.body.appendChild(fragment) <span class="hljs-comment">// 最后添加到body中</span> } replace(frag) { <span class="hljs-keyword">let</span> vm = <span class="hljs-keyword">this</span>.vm <span class="hljs-comment">// 拿到之前存起来的vm</span> <span class="hljs-comment">// 循环frag.childNodes</span> <span class="hljs-built_in"><a href="http://www.js-code.com/tag/array" title="浏览关于“Array”的文章" target="_blank" class="tag_link">Array</a></span>.from(frag.childNodes).forEach(<span class="hljs-function"><span class="hljs-params"><a href="http://www.js-code.com/tag/node" title="浏览关于“node”的文章" target="_blank" class="tag_link">node</a></span> =&gt;</span> { <span class="hljs-keyword">let</span> txt = node.<a href="http://www.js-code.com/tag/text" title="浏览关于“text”的文章" target="_blank" class="tag_link">text</a>Content <span class="hljs-comment">// 拿到文本 例如:"开发语言:{{language}}"</span> <span class="hljs-keyword">let</span> reg = <span class="hljs-regexp">/{{(.*?)}}/g</span> <span class="hljs-comment">// 定义匹配正则</span> <span class="hljs-keyword">if</span> (node.nodeType === <span class="hljs-number">3</span> &amp;&amp; reg.test(txt)) { replaceTxt() <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">replaceTxt</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-comment">// 如果匹配到的话,就替换文本</span> node.textContent = txt.replace(reg, <span class="hljs-function">(<span class="hljs-params">matched, placeholder</span>) =&gt;</span> { <span class="hljs-keyword">return</span> placeholder.split(<span class="hljs-string">'.'</span>).reduce(<span class="hljs-function">(<span class="hljs-params">obj, key</span>) =&gt;</span> { <span class="hljs-keyword">return</span> obj[key] <span class="hljs-comment">// 例如:去vm.makeUp.one对象拿到值</span> }, vm) }) } } <span class="hljs-comment">// 如果还有字节点,并且长度不为0 </span> <span class="hljs-keyword">if</span> (node.childNodes &amp;&amp; node.childNodes.<a href="http://www.js-code.com/tag/length" title="浏览关于“length”的文章" target="_blank" class="tag_link">length</a>) { <span class="hljs-comment">// 直接递归匹配替换</span> <span class="hljs-keyword">this</span>.replace(node) } }) } } </code></pre> <p>上面的编译函数,总之就是一句话,千方百计的把{{xxx}}的占位符通过正则替换成真实的数据。</p> <p>然后刷新浏览器,铛铛档铛铛档,就出现我们要的数据了。</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460486?w=594&amp;h=448" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>很好很好,但是我们现在的数据并不是改变了 就发生变化了。还需要订阅发布和watcher来配合,才能做好改变数据就发生变化了。下面我们先实现订阅发布。</p> <h3 id="articleHeader5">实现订阅发布</h3> <p>订阅发布其实是一种常见的程序设计模式,简单直白来说就是:</p> <blockquote><p>把函数push到一个<a href="http://www.js-code.com/tag/%e6%95%b0%e7%bb%84" title="数组" target="_blank">数组</a>里面,然后循环数据调用函数。</p></blockquote> <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="let arr = [] let a = () => {console.log('a')}</p> <p>arr.push(a) // 订阅a函数<br /> arr.push(a) // 又订阅a函数<br /> arr.push(a) // 双订阅a函数</p> <p>arr.forEach(fn => fn()) // 发布所有</p> <p>// 此时会打印三个a" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-keyword">let</span> arr = [] <span class="hljs-keyword">let</span> a = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a'</span>)} arr.push(a) <span class="hljs-comment">// 订阅a函数</span> arr.push(a) <span class="hljs-comment">// 又订阅a函数</span> arr.push(a) <span class="hljs-comment">// 双订阅a函数</span> arr.forEach(<span class="hljs-function"><span class="hljs-params">fn</span> =&gt;</span> fn()) <span class="hljs-comment">// 发布所有</span> <span class="hljs-comment">// 此时会打印三个a</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 Dep { constructor() { this.subs = [] // 定义数组 } // 订阅函数 addSub(sub) { this.subs.push(sub) } // 发布函数 notify() { this.subs.filter(item => typeof item !== 'string').forEach(sub => sub.update())<br /> }<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// 订阅类</span> <span class="hljs-keyword">class</span> Dep { <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">this</span>.subs = [] <span class="hljs-comment">// 定义<a href="http://www.js-code.com/tag/%e6%95%b0%e7%bb%84" title="浏览关于“数组”的文章" target="_blank" class="tag_link">数组</a></span> } <span class="hljs-comment">// 订阅函数</span> addSub(sub) { <span class="hljs-keyword">this</span>.subs.push(sub) } <span class="hljs-comment">// 发布函数</span> notify() { <span class="hljs-keyword">this</span>.subs.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> <span class="hljs-keyword">typeof</span> item !== <span class="hljs-string">'string'</span>).forEach(<span class="hljs-function"><span class="hljs-params">sub</span> =&gt;</span> sub.update()) } }</code></pre> <p>订阅发布是写好了,但是在什么时候订阅,什么时候发布??这时候,我们是在数据获取的时候订阅<code>watcher</code>,然后在数据设置的时候发布<code>watcher</code>,在上面的<code>Observe</code>类里面里面,看&#x2795;号的代码。 .</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="... //省略代码 ... proxy(data) { let dep = this.dep return new Proxy(data, { // 拦截get get: (target, prop, receiver) => {<br /> + if (Dep.target) {<br /> // 如果之前是push过的,就不用重复push了<br /> if (!dep.subs.includes(Dep.exp)) {<br /> dep.addSub(Dep.exp) // 把Dep.exp。push到sub数组里面,订阅<br /> dep.addSub(Dep.target) // 把Dep.target。push到sub数组里面,订阅<br /> }<br /> + }<br /> return Reflect.get(target, prop, receiver)<br /> },<br /> // 拦截set<br /> set: (target, prop, value) => {<br /> const result = Reflect.set(target, prop, observe(value))<br /> + dep.notify() // 发布<br /> return result<br /> }<br /> })<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>... <span class="hljs-comment">//省略代码</span> ... proxy(<span class="hljs-keyword">data</span>) { let dep = <span class="hljs-keyword">this</span>.dep <span class="hljs-keyword">return</span> new Proxy(<span class="hljs-keyword">data</span>, { <span class="hljs-comment">// 拦截get</span> <span class="hljs-keyword">get</span>: (target, prop, receiver) =&gt; { + <span class="hljs-keyword">if</span> (Dep.target) { <span class="hljs-comment">// 如果之前是push过的,就不用重复push了</span> <span class="hljs-keyword">if</span> (!dep.subs.includes(Dep.exp)) { dep.addSub(Dep.exp) <span class="hljs-comment">// 把Dep.exp。push到sub数组里面,订阅</span> dep.addSub(Dep.target) <span class="hljs-comment">// 把Dep.target。push到sub数组里面,订阅</span> } + } <span class="hljs-keyword">return</span> Reflect.<span class="hljs-keyword">get</span>(target, prop, receiver) }, <span class="hljs-comment">// 拦截set</span> <span class="hljs-keyword">set</span>: (target, prop, value) =&gt; { <span class="hljs-keyword">const</span> result = Reflect.<span class="hljs-keyword">set</span>(target, prop, observe(value)) + dep.notify() <span class="hljs-comment">// 发布</span> <span class="hljs-keyword">return</span> result } }) }</code></pre> <p>上面代码说到,watcher是什么鬼?然后发布里面的sub.update()又是什么鬼??</p> <p>带着一堆疑问我们来到了watcher</p> <h3 id="articleHeader6">实现watcher</h3> <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="// Watcher类 class Watcher { constructor (vm, exp, fn) { this.fn = fn // 传进来的fn this.vm = vm // 传进来的vm this.exp = exp // 传进来的匹配到exp 例如:&quot;language&quot;,&quot;makeUp.one&quot; Dep.exp = exp // 给Dep类挂载一个exp Dep.target = this // 给Dep类挂载一个watcher对象,跟新的时候就用到了 let arr = exp.split('.') let val = vm arr.forEach(key => {<br /> val = val[key] // 获取值,这时候会粗发vm.proxy的get()函数,get()里面就添加addSub订阅函数<br /> })<br /> Dep.target = null // 添加了订阅之后,把Dep.target清空<br /> }<br /> update() {<br /> // 设置值会触发vm.proxy.set函数,然后调用发布的notify,<br /> // 最后调用update,update里面继续调用this.fn(val)<br /> let exp = this.exp<br /> let arr = exp.split('.')<br /> let val = this.vm<br /> arr.forEach(key => {<br /> val = val[key]<br /> })<br /> this.fn(val)<br /> }<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code><span class="hljs-comment">// Watcher类</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Watcher</span> </span>{ <span class="hljs-keyword">constructor</span> (vm, exp, fn) { <span class="hljs-keyword">this</span>.fn = fn <span class="hljs-comment">// 传进来的fn</span> <span class="hljs-keyword">this</span>.vm = vm <span class="hljs-comment">// 传进来的vm</span> <span class="hljs-keyword">this</span>.exp = exp <span class="hljs-comment">// 传进来的匹配到exp 例如:"language","makeUp.one"</span> Dep.exp = exp <span class="hljs-comment">// 给Dep类挂载一个exp</span> Dep.target = <span class="hljs-keyword">this</span> <span class="hljs-comment">// 给Dep类挂载一个watcher对象,跟新的时候就用到了</span> let arr = exp.split(<span class="hljs-string">'.'</span>) let <span class="hljs-keyword">val</span> = vm arr.forEach(key =&gt; { <span class="hljs-keyword">val</span> = <span class="hljs-keyword">val</span>[key] <span class="hljs-comment">// 获取值,这时候会粗发vm.proxy的get()函数,get()里面就添加addSub订阅函数</span> }) Dep.target = <span class="hljs-literal"><a href="http://www.js-code.com/tag/null" title="浏览关于“null”的文章" target="_blank" class="tag_link">null</a></span> <span class="hljs-comment">// 添加了订阅之后,把Dep.target清空</span> } update() { <span class="hljs-comment">// 设置值会触发vm.proxy.set函数,然后调用发布的notify,</span> <span class="hljs-comment">// 最后调用update,update里面继续调用this.fn(val)</span> let exp = <span class="hljs-keyword">this</span>.exp let arr = exp.split(<span class="hljs-string">'.'</span>) let <span class="hljs-keyword">val</span> = <span class="hljs-keyword">this</span>.vm arr.forEach(key =&gt; { <span class="hljs-keyword">val</span> = <span class="hljs-keyword">val</span>[key] }) <span class="hljs-keyword">this</span>.fn(<span class="hljs-keyword">val</span>) } } </code></pre> <p>Watcher类就是我们要订阅的watcher,里面有回调函数fn,有update函数调用fn,</p> <p>我们都弄好了。但是在哪里添加watcher呢??如下代码</p> <p>在Compile里面</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="... ... function replaceTxt() { node.textContent = txt.replace(reg, (matched, placeholder) => {<br /> + new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容<br /> return placeholder.split('.').reduce((val, key) => {<br /> return val[key]<br /> }, vm)<br /> })<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code>... ... <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">replaceTxt</span>(<span class="hljs-params"></span>) </span>{ node.textContent = txt.replace(reg, <span class="hljs-function">(<span class="hljs-params">matched, placeholder</span>) =&gt;</span> { + <span class="hljs-keyword">new</span> Watcher(vm, placeholder, replaceTxt); <span class="hljs-comment">// 监听变化,进行匹配替换内容</span> <span class="hljs-keyword">return</span> placeholder.split(<span class="hljs-string">'.'</span>).reduce(<span class="hljs-function">(<span class="hljs-params">val, key</span>) =&gt;</span> { <span class="hljs-keyword">return</span> val[key] }, vm) }) } </code></pre> <p>添加好有所的东西了,我们看一下控制台。修改发现果然起作用了。</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460487?w=1490&amp;h=962" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>然后我们回顾一下所有的流程,然后看见古老(我也是别的地方弄来的)的一张图。</p> <p>帮助理解嘛</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460488" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>响应式的数据我们都已经完成了,下面我们完成一下<a href="http://www.js-code.com/tag/%e5%8f%8c%e5%90%91%e7%bb%91%e5%ae%9a" title="双向绑定" target="_blank">双向绑定</a>。</p> <h3 id="articleHeader7">实现<a href="http://www.js-code.com/tag/%e5%8f%8c%e5%90%91%e7%bb%91%e5%ae%9a" title="双向绑定" target="_blank">双向绑定</a></h3> <p>看到我们html里面有个<code>&lt;input placeholder="123" v-module="language" /&gt;</code>,<code>v-module</code>绑定了一个<code>language</code>,然后在<code>Compile类</code>里面的<code>replace函数</code>,我们加上</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="replace(frag) { let vm = this.vm Array.from(frag.childNodes).forEach(node => {<br /> let txt = node.textContent<br /> let reg = /{{(.*?)}}/g<br /> // 判断nodeType<br /> + if (node.nodeType === 1) {<br /> const nodeAttr = node.attributes // 属性集合<br /> Array.from(nodeAttr).forEach(item => {<br /> let name = item.name // 属性名<br /> let exp = item.value // 属性值<br /> // 如果属性有 v-<br /> if (name.includes('v-')){<br /> node.value = vm[exp]<br /> node.addEventListener('input', e => {<br /> // 相当于给this.language赋了一个新值<br /> // 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新操作<br /> vm[exp] = e.target.value<br /> })<br /> }<br /> });<br /> + }<br /> ...<br /> ...<br /> }<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code>replace(frag) { <span class="hljs-keyword">let</span> vm = <span class="hljs-keyword">this</span>.vm <span class="hljs-built_in">Array</span>.from(frag.childNodes).forEach(<span class="hljs-function"><span class="hljs-params">node</span> =&gt;</span> { <span class="hljs-keyword">let</span> txt = node.textContent <span class="hljs-keyword">let</span> reg = <span class="hljs-regexp">/{{(.*?)}}/g</span> <span class="hljs-comment">// 判断nodeType</span> + <span class="hljs-keyword">if</span> (node.nodeType === <span class="hljs-number">1</span>) { <span class="hljs-keyword">const</span> nodeAttr = node.attributes <span class="hljs-comment">// 属性集合</span> <span class="hljs-built_in">Array</span>.from(nodeAttr).forEach(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> { <span class="hljs-keyword">let</span> <a href="http://www.js-code.com/tag/name" title="浏览关于“name”的文章" target="_blank" class="tag_link">name</a> = item.name <span class="hljs-comment">// 属性名</span> <span class="hljs-keyword">let</span> exp = item.value <span class="hljs-comment">// 属性值</span> <span class="hljs-comment">// 如果属性有 v-</span> <span class="hljs-keyword">if</span> (name.includes(<span class="hljs-string">'v-'</span>)){ node.value = vm[exp] node.addEventListener(<span class="hljs-string">'input'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> { <span class="hljs-comment">// 相当于给this.language赋了一个新值</span> <span class="hljs-comment">// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新操作</span> vm[exp] = e.target.value }) } }); + } ... ... } } </code></pre> <p>上面的方法就是,让我们的<code>input</code>节点绑定一个<code>input事件</code>,然后当<code>input事件</code>触发的时候,改变我们的值,而值的改变会调用<code>set</code>,<code>set</code>中又会调用<code>not<a href="http://www.js-code.com/tag/if" title="if" target="_blank">if</a>y</code>,<code>not<a href="http://www.js-code.com/tag/if" title="if" target="_blank">if</a>y</code>中调用<code>watcher</code>的<code>update</code>方法实现了更新操作。</p> <p>然后我们看一下,界面<br /><span class="img-wrap"><img data-src="/img/remote/1460000015460489?w=1304&amp;h=972" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>双向数据绑定我们基本完成了,别忘了,我们上面还有个注释掉的计算属性。</p> <h3 id="articleHeader8">计算属性</h3> <p>先把<code>&lt;p&gt;计算属性:{{sum}}&lt;/p&gt;</code>注释去掉,以为上面一开始initVm函数里面,我们加了这个代码<code><a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> this[key] || this._data[key] || this._computed[key]</code>,到这里大家都明白了,只需要把this._computed也加一个watcher就好了。</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="function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) initObserve.call(this, data) + initComputed.call(this) // 添加计算函数,改变this指向 new Compile(this.$options.el, vm) return this._vm } function initComputed() { let vm = this let computed = this.$options.computed // 拿到配置的computed vm._computed = {} if (!computed) return // 没有计算直接返回 Object.keys(computed).forEach(key => {<br /> // 相当于把sum里的this指向到this._vm,然后就可以拿到this.a、this、b<br /> this._computed[key] = computed[key].call(this._vm)<br /> // 添加新的Watcher<br /> new Watcher(this._vm, key, val => {<br /> // 每次设置的时候都会计算<br /> this._computed[key] = computed[key].call(this._vm)<br /> })<br /> })<br /> }<br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>function Mvvm(options = {}) { <span class="hljs-keyword">this</span>.$options = options let <span class="hljs-keyword">data</span> = <span class="hljs-keyword">this</span>._data = <span class="hljs-keyword">this</span>.$options.<span class="hljs-keyword">data</span> let vm = initVm.call(<span class="hljs-keyword">this</span>) initObserve.call(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">data</span>) + initComputed.call(<span class="hljs-keyword">this</span>) <span class="hljs-comment">// 添加计算函数,改变this指向</span> new Compile(<span class="hljs-keyword">this</span>.$options.el, vm) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm } function initComputed() { let vm = <span class="hljs-keyword">this</span> let computed = <span class="hljs-keyword">this</span>.$options.computed <span class="hljs-comment">// 拿到配置的computed</span> vm._computed = {} <span class="hljs-keyword">if</span> (!computed) <span class="hljs-keyword">return</span> <span class="hljs-comment">// 没有计算直接返回</span> <a href="http://www.js-code.com/tag/Object" title="浏览关于“Object”的文章" target="_blank" class="tag_link">Object</a>.keys(computed).forEach(key =&gt; { <span class="hljs-comment">// 相当于把sum里的this指向到this._vm,然后就可以拿到this.a、this、b</span> <span class="hljs-keyword">this</span>._computed[key] = computed[key].call(<span class="hljs-keyword">this</span>._vm) <span class="hljs-comment">// 添加新的Watcher</span> new Watcher(<span class="hljs-keyword">this</span>._vm, key, <span class="hljs-keyword">val</span> =&gt; { <span class="hljs-comment">// 每次设置的时候都会计算</span> <span class="hljs-keyword">this</span>._computed[key] = computed[key].call(<span class="hljs-keyword">this</span>._vm) }) }) } </code></pre> <p>上面的initComputed 就是添加一个watcher,大致流程:</p> <p>this._vm改变 ---&gt; vm.set() ---&gt; notify() --&gt;update()--&gt;更新界面</p> <p>最后看看图片</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000015460490?w=1238&amp;h=974" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>一切似乎没什么毛病~~~~</p> <h3 id="articleHeader9">添加mounted钩子</h3> <p>添加mounted也很简单</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="// 写法和Vue一样 let mvvm = new Mvvm({ el: '#app', data: { ... ... }, computed: { ... ... }, mounted() { console.log('i am mounted', this.a) } }) " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// 写法和Vue一样</span> <span class="hljs-keyword">let</span> mvvm = <span class="hljs-keyword">new</span> Mvvm({ el: <span class="hljs-string">'#app'</span>, data: { ... ... }, computed: { ... ... }, mounted() { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'i am mounted'</span>, <span class="hljs-keyword">this</span>.a) } }) </code></pre> <p>在<a href="http://www.js-code.com/tag/new" title="new" target="_blank">new</a> Mvvm里面添加mounted,<br />然后到<a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a> Mvvm里面加上</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="function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) initObserve.call(this, data) initComputed.call(this) new Compile(this.$options.el, vm) + mounted.call(this._vm) // 加上mounted,改变指向 return this._vm } // 运行mounted + function mounted() { let mounted = this.$options.mounted mounted &amp;&amp; mounted.call(this) + } " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code>function Mvvm(options = {}) { <span class="hljs-keyword">this</span>.$options = options let <span class="hljs-keyword">data</span> = <span class="hljs-keyword">this</span>._data = <span class="hljs-keyword">this</span>.$options.<span class="hljs-keyword">data</span> let vm = initVm.call(<span class="hljs-keyword">this</span>) initObserve.call(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">data</span>) initComputed.call(<span class="hljs-keyword">this</span>) new Compile(<span class="hljs-keyword">this</span>.$options.el, vm) + mounted.call(<span class="hljs-keyword">this</span>._vm) <span class="hljs-comment">// 加上mounted,改变指向</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._vm } <span class="hljs-comment">// 运行mounted</span> + function mounted() { let mounted = <span class="hljs-keyword">this</span>.$options.mounted mounted &amp;&amp; mounted.call(<span class="hljs-keyword">this</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="i am mounted 1" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs nginx"><code style="word-break: break-word; white-space: initial;"><span class="hljs-attribute">i</span> am mounted <span class="hljs-number">1</span></code></pre> <p>完结~~~~撒花</p> <p>ps:编译里面的,参考到这个大神的操作。@<a href="https://juejin.im/post/5abdd6f6f265da23793c4458" rel="nofollow noreferrer" target="_blank">chenhongdong</a>,谢谢大佬</p> <p>最后附上,源代码地址,直接下载运行就可以啦。</p> <p>源码地址:<a href="https://github.com/naihe138/proxy-mvvm" rel="nofollow noreferrer" target="_blank">https://github.com/naihe138/proxy-mvvm</a></p> <p>预览地址:<a href="http://gitblog.naice.me/proxy-mvvm/index.html" rel="nofollow noreferrer" target="_blank">http://gitblog.naice.me/proxy-mvvm/index.html</a></p> <p></code></p>

总结

以上是脚本宝典为你收集整理的

试着用Proxy 实现一个简单mvvm

全部内容,希望文章能够帮你解决

试着用Proxy 实现一个简单mvvm

所遇到的程序开发问题,欢迎加入QQ群277859234一起讨论学习。如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典网站推荐给程序员好友。 本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。

80%的人都看过