<p><code></p> <h1 id="articleHeader0"><a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a>-hot-reload-api 源码解析</h1> <h2 id="articleHeader1">起因</h2> <p>最近在搞san框架的热加载方案,自然是少不了向成熟的框架学习(偷窥ing)。热加载方案基本也只是主流框架在做,且做的比较成熟,大部分应用开发者并不会接触到这部分东西,所以相应的资料比较少。google了一下这个库,发现木有人做相应的解析,顺手记录下好了。</p> <h2 id="articleHeader2">什么是<a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a>-hot-reload-api?</h2> <p>众所周知,<code>*.vue</code>文件为广大开发者提供了良好的开发体验,vue-loader的原理不多赘述,在vue的脚手架中,webpack通过vue-loader来解析<code>*.vue</code>文件,把template、js和style文件分离并让相应的loader去处理。</p> <p>在这个过程中,vue-loader还会做些其他事情,比如向client端注入hot-reload相应的代码,构建时编译等等。</p> <div class="google-auto-placed ap_container" style="text-align: center; width: 100%; height: auto; clear: none;"><ins data-ad-format="auto" class="adsbygoogle adsbygoogle-noablate" data-ad-client="ca-pub-6330872677300335" data-adsbygoogle-status="done" style="display: block; margin: auto; background-color: transparent;"><ins id="aswift_4_expand" style="display:inline-table;border:none;height:175px;margin:0;padding:0;position:relative;visibility:visible;width:697px;background-color:transparent;"><ins id="aswift_4_anchor" style="display:block;border:none;height:175px;margin:0;padding:0;position:relative;visibility:visible;width:697px;background-color:transparent;"><iframe id="aswift_4" name="aswift_4" width="697" height="175" frameborder="0" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6330872677300335&amp;output=html&amp;h=175&amp;adk=1934095149&amp;adf=377590429&amp;w=697&amp;lmt=1557204896&amp;num_ads=1&amp;sem=mc&amp;pwprc=5909933996&amp;guci=2.2.0.0.2.2.0.0&amp;ad_type=text&amp;format=697x175&amp;url=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000010744600&amp;flash=0&amp;pra=3&amp;wgl=1&amp;fa=27&amp;adsid=NT&amp;dt=1557204896804&amp;bpp=1&amp;bdt=1097&amp;idt=-M&amp;shv=r20190429&amp;cbv=r20190131&amp;saldr=aa&amp;abxe=1&amp;prev_fmts=0x0%2C728x90%2C970x90%2C1001x90&amp;nras=2&amp;correlator=4942135608721&amp;frm=20&amp;pv=1&amp;ga_vid=1620951673.1557204896&amp;ga_sid=1557204896&amp;ga_hid=1892256669&amp;ga_fc=0&amp;iag=0&amp;icsg=79465818240&amp;dssz=39&amp;mdo=0&amp;mso=8&amp;u_tz=480&amp;u_his=1&amp;u_java=0&amp;u_h=1080&amp;u_w=1920&amp;u_ah=1040&amp;u_aw=1920&amp;u_cd=24&amp;u_nplug=1&amp;u_nmime=1&amp;adx=31&amp;ady=956&amp;biw=1001&amp;bih=717&amp;scr_x=0&amp;scr_y=0&amp;eid=20040013%2C20040080%2C21060853&amp;oid=3&amp;ref=https%3A%2F%2Fsegmentfault.com%2Fsearch%3Fq%3Dvue%26type%3Darticle%26page%3D143&amp;rx=0&amp;eae=0&amp;fc=1424&amp;brdim=447%2C192%2C447%2C192%2C1920%2C0%2C1018%2C717%2C1018%2C717&amp;vis=1&amp;rsz=%7C%7Cs%7C&amp;abl=NS&amp;fu=8216&amp;bc=13&amp;osw_key=3581259108&amp;ifi=4&amp;uci=4.7lm9ne26s1c9&amp;xpc=JeBRxNC3MN&amp;p=https%3A//segmentfault.com&amp;dtd=3" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" data-google-container-id="4.7lm9ne26s1c9" data-google-query-id="CImo5_rQiOICFdwcKgodhTUJqA" data-load-complete="true"></iframe></ins></ins></ins></div> <p>webpack的hmr原理也不多说了,vue的热加载就是通过注入的代码来实现组件的热更新,下面来看下使用时的文档和源码。</p> <h2 id="articleHeader3">用法</h2> <p>先来看下官方文档。</p> <blockquote> <p>你仅会在开发一个基于 <a href="http://www.js-code.com/tag/vue" title="浏览关于“Vue”的文章" target="_blank" class="tag_link">Vue</a> components 构建工具的时候用到这个。对于普通的应用,使用 vue-loader 或者 vueify 就可以了。</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="// 定义一个组件作为选项对象 // 在vue-loader中,这个对象是Component.options const myComponentOptions = { data () { ... }, created () { ... }, render () { ... } } // 检测 Webpack 的 HMR API // https://doc.webpack-china.org/guides/hot-module-replacement/ if (module.hot) { const api = require('vue-hot-reload-api') const Vue = require('vue') // 将 API 安装到 Vue,并且检查版本的兼容性 api.install(Vue) // 在安装之后使用 api.compatible 来检查兼容性 if (!api.compatible) { throw new Error('vue-hot-reload-api与当前Vue的版本不兼容') } // 此模块接受热重载 // 在这儿多说一句,webpack关于hmr的文档实在是太。。。 // 各大框架的loader中关于hmr的实现都是基于自身模块接受更新来实现 module.hot.accept() if (!module.hot.data) { // 为了将每一个组件中的选项变得可以热加载, // 你需要用一个不重复的id创建一次记录, // 只需要在启动的时候做一次。 api.createRecord('very-unique-id', myComponentOptions) } else { // 如果一个组件只是修改了模板或是 render 函数, // 只要把所有相关的实例重新渲染一遍就可以了,而不需要销毁重建他们。 // 这样就可以完整的保持应用的当前状态。 api.rerender('very-unique-id', myComponentOptions) // --- 或者 --- // 如果一个组件更改了除 template 或 render 之外的选项, // 就需要整个重新加载。 // 这将销毁并重建整个组件(包括子组件)。 api.reload('very-unique-id', myComponentOptions) } }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// 定义一个组件作为选项对象</span> <span class="hljs-comment">// 在vue-loader中,这个对象是Component.options</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a></span> myComponentOptions = { data () { ... }, created () { ... }, render () { ... } } <span class="hljs-comment">// 检测 Webpack 的 HMR <a href="http://www.js-code.com/tag/api" title="浏览关于“API”的文章" target="_blank" class="tag_link">API</a></span> <span class="hljs-comment">// https://doc.webpack-china.org/guides/hot-module-replacement/</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">module</span>.hot) { <span class="hljs-keyword">const</span> api = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-hot-reload-api'</span>) <span class="hljs-keyword">const</span> Vue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue'</span>) <span class="hljs-comment">// 将 API 安装到 Vue,并且检查版本的兼容性</span> api.install(Vue) <span class="hljs-comment">// 在安装之后使用 api.compatible 来检查兼容性</span> <span class="hljs-keyword">if</span> (!api.compatible) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'vue-hot-reload-api与当前Vue的版本不兼容'</span>) } <span class="hljs-comment">// 此模块接受热重载</span> <span class="hljs-comment">// 在这儿多说一句,webpack关于hmr的文档实在是太。。。</span> <span class="hljs-comment">// 各大框架的loader中关于hmr的实现都是基于自身模块接受更新来实现</span> <span class="hljs-built_in">module</span>.hot.accept() <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">module</span>.hot.data) { <span class="hljs-comment">// 为了将每一个组件中的选项变得可以热加载,</span> <span class="hljs-comment">// 你需要用一个不重复的id创建一次记录,</span> <span class="hljs-comment">// 只需要在启动的时候做一次。</span> api.createRecord(<span class="hljs-string">'very-unique-id'</span>, myComponentOptions) } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 如果一个组件只是修改了模板或是 render 函数,</span> <span class="hljs-comment">// 只要把所有相关的实例重新渲染一遍就可以了,而不需要销毁重建他们。</span> <span class="hljs-comment">// 这样就可以完整的保持应用的当前状态。</span> api.rerender(<span class="hljs-string">'very-unique-id'</span>, myComponentOptions) <span class="hljs-comment">// --- 或者 ---</span> <span class="hljs-comment">// 如果一个组件更改了除 template 或 render 之外的选项,</span> <span class="hljs-comment">// 就需要整个重新加载。</span> <span class="hljs-comment">// 这将销毁并重建整个组件(包括子组件)。</span> api.reload(<span class="hljs-string">'very-unique-id'</span>, myComponentOptions) } }</code></pre> <p>通过使用说明可以看出,vue-hot-reload-api暴露的接口还是很清晰的,下面来看下具体源码实现。</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 Vue // late bind var version // 全局对象__VUE_HOT_MAP__来保存所有的构造器和实例 var map = window.__VUE_HOT_MAP__ = Object.create(null) var installed = false // 这个参数来判断是vue-loader还是vueify在调用 var isBrowserify = false // 2.0.0-alpha.7版本前的初始化钩子名是init,这个参数来作区分 var initHookName = 'beforeCreate' <a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s.install = function (vue, browserify) {<br /> if (installed) return<br /> installed = true</p> <p>// 判断打包的是esodule还是普通的js函数<br /> Vue = vue.__esModule ? vue.default : vue<br /> version = Vue.version.split('.').map(Number)<br /> isBrowserify = browserify</p> <p> // compat with < 2.0.0-alpha.7 if (Vue.config._lifecycleHooks.indexOf('init') > -1) {<br /> initHookName = 'init'<br /> }</p> <p> exports.compatible = version[0] >= 2<br /> // 兼容性,1.x和2.x的框架实现和loader实现都有很大差异<br /> if (!<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s.compatible) {<br /> console.warn(<br /> '[HMR] You are using a version of vue-hot-reload-api that is ' +<br /> 'only compatible with Vue.js core ^2.0.0.'<br /> )<br /> return<br /> }<br /> }</p> <p>/**<br /> * Create a record for a hot module, which keeps track of its <a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a>ructor<br /> * and instances<br /> *<br /> * @param {String} id<br /> * @param {Object} options<br /> */</p> <p><a href="http://www.js-code.com/tag/export" title="浏览关于“export”的文章" target="_blank" class="tag_link">export</a>s.createRecord = function (id, options) {<br /> var Ctor = null<br /> // 判断传入的options是对象还是函数<br /> if (typeof options === 'function') {<br /> Ctor = options<br /> options = Ctor.options<br /> }<br /> // 燥起来,这个函数会在组件初始化和结束时的生命周期注入hook函数<br /> // 当实例化以后,hook函数调用会把实例记录到map中<br /> // destroy后会从map中删除实例自身<br /> makeOptionsHot(id, options)</p> <p> map[id] = {<br /> Ctor: Vue.extend(options),<br /> instances: []<br /> }<br /> }</p> <p>/**<br /> * Make a Component options object hot.<br /> *<br /> * @param {String} id<br /> * @param {Object} options<br /> */</p> <p>function makeOptionsHot (id, options) {<br /> // 注入hook函数,到达相应声明周期后执行<br /> injectHook(options, initHookName, function () {<br /> map[id].instances.push(<a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>)<br /> })<br /> injectHook(options, 'beforeDestroy', function () {<br /> var instances = map[id].instances<br /> instances.splice(instances.indexOf(<a href="http://www.js-code.com/tag/this" title="this" target="_blank">this</a>), 1)<br /> })<br /> }</p> <p>/**<br /> * Inject a hook to a hot reloadable component so that<br /> * we can keep track of it.<br /> *<br /> * @param {Object} options<br /> * @param {String} name<br /> * @param {Function} hook<br /> */</p> <p>function injectHook (options, name, hook) {<br /> // 判断未注入时,生命周期init/beforeDestroy是否已经有了函数<br /> // 不存在的话,直接把生命周期函数置为[hook]<br /> // 存在的话,判断是否为<a href="http://www.js-code.com/tag/array" title="Array" target="_blank">Array</a>,从而把已存在的函数和hook连接起来<br /> var existing = options[name]<br /> options[name] = existing<br /> ? <a href="http://www.js-code.com/tag/array" title="Array" target="_blank">Array</a>.is<a href="http://www.js-code.com/tag/array" title="浏览关于“Array”的文章" target="_blank" class="tag_link">Array</a>(existing)<br /> ? existing.concat(hook)<br /> : [existing, hook]<br /> : [hook]<br /> }</p> <p>// 不得不说,这个一开始确实没搞懂是为啥要包一层<br /> // 自己实现的时候才知道,当有error弹出时<br /> // 如果不手动这样接住error,webpack会接到然后立即location.reload()<br /> // 根本来不及看reload之前给出的提示<br /> // 所以要手动处理下error<br /> function tryWrap (fn) {<br /> return function (id, arg) {<br /> try { fn(id, arg) } catch (e) {<br /> console.error(e)<br /> console.warn('Something went wrong during Vue component hot-reload. Full reload required.')<br /> }<br /> }<br /> }</p> <p>exports.rerender = tryWrap(function (id, options) {<br /> var record = map[id]<br /> // 边界处理<br /> // 如果没有传options或者已经为空<br /> // 会把这个构造函数生成的所有实例强制刷新并返回<br /> if (!options) {<br /> record.instances.<a href="http://www.js-code.com/tag/slice" title="slice" target="_blank">slice</a>().forEach(function (instance) {<br /> instance.$forceUpdate()<br /> })<br /> return<br /> }<br /> // 判断是否是构造函数还是proto<br /> if (typeof options === 'function') {<br /> options = options.options<br /> }</p> <p> // 修改map对象中的Ctor以便记录<br /> record.Ctor.options.render = options.render<br /> record.Ctor.options.staticRenderFns = options.staticRenderFns<br /> // .<a href="http://www.js-code.com/tag/slice" title="slice" target="_blank">slice</a>方法保证了instances的length是有效的<br /> record.instances.<a href="http://www.js-code.com/tag/slice" title="浏览关于“slice”的文章" target="_blank" class="tag_link">slice</a>().forEach(function (instance) {<br /> // 把更新过的模块render函数和静态方法指到旧的实例上<br /> // reset static trees<br /> // 然后重刷新<br /> instance.$options.render = options.render<br /> instance.$options.staticRenderFns = options.staticRenderFns<br /> instance._staticTrees = [] // reset static trees<br /> instance.$forceUpdate()<br /> })<br /> })</p> <p>exports.reload = tryWrap(function (id, options) {<br /> var record = map[id]<br /> if (options) {<br /> if (typeof options === 'function') {<br /> options = options.options<br /> }<br /> makeOptionsHot(id, options)<br /> if (version[1] < 2) { // preserve pre 2.2 behavior for global mixin handling record.Ctor.extendOptions = options } // 其实最开始的commit中,并未继承Ctor的父类,是直接Vue.extend(options) // 对vue了解不深,不知道为啥改成这样 // 有兴趣的同学可以思考下 var newCtor = record.Ctor.super.extend(options) record.Ctor.options = newCtor.options record.Ctor.cid = newCtor.cid record.Ctor.prototype = newCtor.prototype // 2.0早期版本兼容 if (newCtor.release) { // temporary global mixin strategy used in < 2.0.0-alpha.6 newCtor.release() } } record.instances.slice().forEach(function (instance) { // 判断vNode和上下文是否存在 // 不存在的需要手动刷新 if (instance.$vnode &amp;&amp; instance.$vnode.context) { instance.$vnode.context.$forceUpdate() } else { console.warn('Root or manually mounted instance modified. Full reload required.') } }) }) " title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">var</span> Vue <span class="hljs-comment">// late bind</span> <span class="hljs-keyword">var</span> version <span class="hljs-comment">// 全局对象__VUE_HOT_MAP__来保存所有的构造器和实例</span> <span class="hljs-keyword">var</span> map = <span class="hljs-built_in">window</span>.__VUE_HOT_MAP__ = <span class="hljs-built_in">Object</span>.create(<span class="hljs-literal">null</span>) <span class="hljs-keyword">var</span> installed = <span class="hljs-literal">false</span> <span class="hljs-comment">// 这个参数来判断是vue-loader还是vueify在调用</span> <span class="hljs-keyword">var</span> isBrowserify = <span class="hljs-literal">false</span> <span class="hljs-comment">// 2.0.0-alpha.7版本前的初始化钩子名是init,这个参数来作区分</span> <span class="hljs-keyword">var</span> initHookName = <span class="hljs-string">'beforeCreate'</span> exports.install = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">vue, browserify</span>) </span>{ <span class="hljs-keyword">if</span> (installed) <span class="hljs-keyword">return</span> installed = <span class="hljs-literal">true</span> <span class="hljs-comment">// 判断打包的是esodule还是普通的js函数</span> Vue = vue.__esModule ? vue.default : vue version = Vue.version.split(<span class="hljs-string">'.'</span>).map(<span class="hljs-built_in">Number</span>) isBrowserify = browserify <span class="hljs-comment">// compat with &lt; 2.0.0-alpha.7</span> <span class="hljs-keyword">if</span> (Vue.config._lifecycleHooks.indexOf(<span class="hljs-string">'init'</span>) &gt; <span class="hljs-number">-1</span>) { initHookName = <span class="hljs-string">'init'</span> } exports.compatible = version[<span class="hljs-number">0</span>] &gt;= <span class="hljs-number">2</span> <span class="hljs-comment">// 兼容性,1.x和2.x的框架实现和loader实现都有很大差异</span> <span class="hljs-keyword">if</span> (!exports.compatible) { <span class="hljs-built_in">console</span>.warn( <span class="hljs-string">'[HMR] You are using a version of vue-hot-reload-api that is '</span> + <span class="hljs-string">'only compatible with Vue.js core ^2.0.0.'</span> ) <span class="hljs-keyword">return</span> } } <span class="hljs-comment">/** * Create a record for a hot module, which keeps track of its constructor * and instances * * @param {String} id * @param {Object} options */</span> exports.createRecord = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id, options</span>) </span>{ <span class="hljs-keyword">var</span> Ctor = <span class="hljs-literal">null</span> <span class="hljs-comment">// 判断传入的options是对象还是函数</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> options === <span class="hljs-string">'function'</span>) { Ctor = options options = Ctor.options } <span class="hljs-comment">// 燥起来,这个函数会在组件初始化和结束时的生命周期注入hook函数</span> <span class="hljs-comment">// 当实例化以后,hook函数调用会把实例记录到map中</span> <span class="hljs-comment">// destroy后会从map中删除实例自身</span> makeOptionsHot(id, options) map[id] = { <span class="hljs-attr">Ctor</span>: Vue.extend(options), <span class="hljs-attr">instances</span>: [] } } <span class="hljs-comment">/** * Make a Component options object hot. * * @param {String} id * @param {Object} options */</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeOptionsHot</span> (<span class="hljs-params">id, options</span>) </span>{ <span class="hljs-comment">// 注入hook函数,到达相应声明周期后执行</span> injectHook(options, initHookName, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ map[id].instances.push(<span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>) }) injectHook(options, <span class="hljs-string">'beforeDestroy'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> instances = map[id].instances instances.splice(instances.indexOf(<span class="hljs-keyword">this</span>), <span class="hljs-number">1</span>) }) } <span class="hljs-comment">/** * Inject a hook to a hot reloadable component so that * we can keep track of it. * * @param {Object} options * @param {String} name * @param {Function} hook */</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">injectHook</span> (<span class="hljs-params">options, name, hook</span>) </span>{ <span class="hljs-comment">// 判断未注入时,生命周期init/beforeDestroy是否已经有了函数</span> <span class="hljs-comment">// 不存在的话,直接把生命周期函数置为[hook]</span> <span class="hljs-comment">// 存在的话,判断是否为Array,从而把已存在的函数和hook连接起来</span> <span class="hljs-keyword">var</span> existing = options[name] options[name] = existing ? <span class="hljs-built_in">Array</span>.isArray(existing) ? existing.concat(hook) : [existing, hook] : [hook] } <span class="hljs-comment">// 不得不说,这个一开始确实没搞懂是为啥要包一层</span> <span class="hljs-comment">// 自己实现的时候才知道,当有error弹出时</span> <span class="hljs-comment">// 如果不手动这样接住error,webpack会接到然后立即location.reload()</span> <span class="hljs-comment">// 根本来不及看reload之前给出的提示</span> <span class="hljs-comment">// 所以要手动处理下error</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tryWrap</span> (<span class="hljs-params">fn</span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id, arg</span>) </span>{ <span class="hljs-keyword">try</span> { fn(id, arg) } <span class="hljs-keyword">catch</span> (e) { <span class="hljs-built_in">console</span>.error(e) <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Something went wrong during Vue component hot-reload. Full reload required.'</span>) } } } exports.rerender = tryWrap(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id, options</span>) </span>{ <span class="hljs-keyword">var</span> record = map[id] <span class="hljs-comment">// 边界处理</span> <span class="hljs-comment">// 如果没有传options或者已经为空</span> <span class="hljs-comment">// 会把这个构造函数生成的所有实例强制刷新并返回</span> <span class="hljs-keyword">if</span> (!options) { record.instances.slice().forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">instance</span>) </span>{ instance.$forceUpdate() }) <span class="hljs-keyword">return</span> } <span class="hljs-comment">// 判断是否是构造函数还是proto</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> options === <span class="hljs-string">'function'</span>) { options = options.options } <span class="hljs-comment">// 修改map对象中的Ctor以便记录</span> record.Ctor.options.render = options.render record.Ctor.options.staticRenderFns = options.staticRenderFns <span class="hljs-comment">// .slice方法保证了instances的length是有效的</span> record.instances.slice().forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">instance</span>) </span>{ <span class="hljs-comment">// 把更新过的模块render函数和静态方法指到旧的实例上</span> <span class="hljs-comment">// reset static trees</span> <span class="hljs-comment">// 然后重刷新</span> instance.$options.render = options.render instance.$options.staticRenderFns = options.staticRenderFns instance._staticTrees = [] <span class="hljs-comment">// reset static trees</span> instance.$forceUpdate() }) }) exports.reload = tryWrap(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id, options</span>) </span>{ <span class="hljs-keyword">var</span> record = map[id] <span class="hljs-keyword">if</span> (options) { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> options === <span class="hljs-string">'function'</span>) { options = options.options } makeOptionsHot(id, options) <span class="hljs-keyword">if</span> (version[<span class="hljs-number">1</span>] &lt; <span class="hljs-number">2</span>) { <span class="hljs-comment">// preserve pre 2.2 behavior for global mixin handling</span> record.Ctor.extendOptions = options } <span class="hljs-comment">// 其实最开始的commit中,并未继承Ctor的父类,是直接Vue.extend(options)</span> <span class="hljs-comment">// 对vue了解不深,不知道为啥改成这样</span> <span class="hljs-comment">// 有兴趣的同学可以思考下</span> <span class="hljs-keyword">var</span> newCtor = record.Ctor.super.extend(options) record.Ctor.options = newCtor.options record.Ctor.cid = newCtor.cid record.Ctor.prototype = newCtor.prototype <span class="hljs-comment">// 2.0早期版本兼容</span> <span class="hljs-keyword">if</span> (newCtor.release) { <span class="hljs-comment">// temporary global mixin strategy used in &lt; 2.0.0-alpha.6</span> newCtor.release() } } record.instances.slice().forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">instance</span>) </span>{ <span class="hljs-comment">// 判断vNode和上下文是否存在</span> <span class="hljs-comment">// 不存在的需要手动刷新</span> <span class="hljs-keyword">if</span> (instance.$v<a href="http://www.js-code.com/tag/node" title="浏览关于“node”的文章" target="_blank" class="tag_link">node</a> &amp;&amp; instance.$vnode.context) { instance.$vnode.context.$forceUpdate() } <span class="hljs-keyword">else</span> { <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Root or manually mounted instance modified. Full reload required.'</span>) } }) }) </code></pre> <p>短短的100多行代码,从这个库支持2.x的第一个commit读起,慢慢由简单实现到覆盖大部分边界及兼容性考虑,再到vue-loader的调用,webpack的hmr各种坑和debug,这个过程很受启发。</p> <p>更多的前端、健身内容,请点击 <a href="https://github.com/jiangjiu/blog-md/issues/29" rel="nofollow noreferrer" target="_blank">将就的博客</a>查看</p> <p></code></p>

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