<p><code></p> <h1 id="articleHeader0"><a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a> 生命周期</h1> <p>下面是 <code>index.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="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text=" <div id=&quot;app&quot;></div> <p>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript" style="word-break: break-word; white-space: initial;">&lt;<a href="http://www.js-code.com/tag/div" title="浏览关于“div”的文章" target="_blank" class="tag_link">div</a> id=<span class="hljs-string">"app"</span>&gt;<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span></code></pre> <h2 id="articleHeader1"><a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a> 生命周期方法的调用顺序 / 生命周期方法在什么情况下被触发</h2> <p>我们在 <code>new</code> 一个 <a href="http://www.js-code.com/tag/vue" title="浏览关于“Vue”的文章" target="_blank" class="tag_link">Vue</a> 实例的过程中,生命周期的方法就会以一定的顺序相继执行,我们可以观察以下代码的执行结果。</p> <p><strong><em>注意:</em></strong>该 Vue 实例并未设置 <code>el</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="new Vue({ data: { message: 0 }, template: ' <div>{{ message }}</div> <p>',<br /> beforeCreate() {<br /> console.log(this.$el, 'beforeCreate');<br /> },<br /> created() {<br /> console.log(this.$el, 'created');<br /> },<br /> beforeMount() {<br /> console.log(this.$el, 'beforeMount');<br /> },<br /> mounted() {<br /> console.log(this.$el, 'mounted');<br /> },<br /> beforeUpdate() {<br /> console.log(this.$el, 'beforeUpdate');<br /> },<br /> updated() {<br /> console.log(this.$el, 'updated');<br /> },<br /> activated() {<br /> console.log(this.$el, 'activated');<br /> },<br /> deactivated() {<br /> console.log(this.$el, 'deactivated');<br /> },<br /> beforeDestroy() {<br /> console.log(this.$el, 'beforeDestroy');<br /> },<br /> destroyed() {<br /> console.log(this.$el, 'destroyed');<br /> },<br /> errorCaptured() {<br /> console.log(this.$el, 'errorCaptured');<br /> }<br /> });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">new</span> Vue({ <span class="hljs-attr">data</span>: { <span class="hljs-attr">message</span>: <span class="hljs-number">0</span> }, <span class="hljs-attr">template</span>: <span class="hljs-string">'&lt;div&gt;{{ message }}&lt;/div&gt;'</span>, beforeCreate() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>.$el, <span class="hljs-string">'beforeCreate'</span>); }, created() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'created'</span>); }, beforeMount() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'beforeMount'</span>); }, mounted() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'mounted'</span>); }, beforeUpdate() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'beforeUpdate'</span>); }, updated() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'updated'</span>); }, activated() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'activated'</span>); }, deactivated() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'deactivated'</span>); }, beforeDestroy() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'beforeDestroy'</span>); }, destroyed() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'destroyed'</span>); }, errorCaptured() { <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.$el, <span class="hljs-string">'errorCaptured'</span>); } });</code></pre> <p><span class="img-wrap"><img data-src="/img/bVbbg1d?w=167&amp;h=44" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="实例化 Vue 时的生命周期执行结果(未设置 el 属性)" title="实例化 Vue 时的生命周期执行结果(未设置 el 属性)" style="cursor: pointer;"></span></p> <p>我们可以看到 <code>beforeCreate()</code>、<code>created()</code> 两个个生命周期方法被依次执行,而其他的生命周期方法并没有被触发执行。</p> <p>如果我们加上 <code>el</code> 属性,或者调用 <code>vm.$mount()</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="new Vue({ el: '#app', // 设置 el 属性 // ... });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">new</span> Vue({ <span class="hljs-attr">el</span>: <span class="hljs-string">'#app'</span>, <span class="hljs-comment">// 设置 el 属性</span> <span class="hljs-comment">// ...</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="var app = new Vue({ // ... }); app.$mount('#root'); // 调用 Vue 实例的 $mount() 方法" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">var</span> app = <span class="hljs-keyword">new</span> Vue({ <span class="hljs-comment">// ...</span> }); app.$mount(<span class="hljs-string">'#root'</span>); <span class="hljs-comment">// 调用 Vue 实例的 $mount() 方法</span></code></pre> <p><span class="img-wrap"><img data-src="/img/bVbbg1f?w=250&amp;h=84" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="实例化 Vue 时的生命周期执行结果" title="实例化 Vue 时的生命周期执行结果" style="cursor: pointer;"></span></p> <p>可以看到 <code>beforeCreate()</code>、<code>created()</code>、<code>beforeMount()</code> 和 <code>mounted()</code> 四个生命周期方法被依次执行。</p> <p>这里我们可以知道,在 <code>new</code> 一个 Vue 实例的时候,如果没有设置 <code>el</code> 属性或者调用 Vue 实例的 <code>$mount()</code> 方法,其实是只会执行 <code>beforeCreate()</code> 和 <code>created()</code> 方法。原因在于,生命周期中的 <code>mountd()</code> 方法其实是把 Vue 实例中的 <code>template</code>属性里的 <code>html</code> 挂载到 <code>el</code> 属性对应的 <code>dom</code> 节点上,如果没有定义 <code>el</code> 属性或者没有调用 Vue 实例的 <code>$mount()</code> 方法,那么就无法执行挂载的动作,因为根本不知道要挂载到哪儿去。 </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: 0px; margin: 0px; padding: 0px; position: relative; visibility: visible; width: 697px; background-color: transparent;"><ins id="aswift_4_anchor" style="display: block; border: none; height: 0px; margin: 0px; padding: 0px; position: relative; visibility: visible; width: 697px; background-color: transparent; overflow: hidden; opacity: 0;"><iframe width="697" height="175" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" onload="var i=this.id,s=window.google_iframe_oncopy,H=s&amp;&amp;s.handlers,h=H&amp;&amp;H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&amp;&amp;d&amp;&amp;(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_4" name="aswift_4" style="left:0;position:absolute;top:0;border:0px;width:697px;height:175px;"></iframe></ins></ins></ins></div> <p><code>beforeCreate()</code>、<code>created()</code>、<code>beforeMount()</code> 和 <code>mounted()</code> 四个方法都是一次性地,在 Vue 实例的生命周期中它们只会被触发调用一次。并且,<code>beforeMount()</code> 和 <code>mounted()</code> 两个方法在服务端渲染的时候是不会被调用的,因为这两个方法是与 <code>dom</code> 的操作有一定关系,而服务端中没有 <code>dom</code> 执行的环境,所以也就不会有<code>beforeMount()</code> 和 <code>mounted()</code> 了。</p> <p>同时,可以看到 <code>vm.$el</code> 在各个生命周期中的值是不同的,在 <code>beforeCreate()</code> 和 <code>created()</code> 中是 <code>undefined</code>,在 <code>beforeMount()</code> 中变成了 <code>index.html</code> 里的 <code>&lt;<a href="http://www.js-code.com/tag/div" title="div" target="_blank">div</a> id="app"&gt;&lt;/<a href="http://www.js-code.com/tag/div" title="div" target="_blank">div</a>&gt;</code>,等到执行 <code>mounted()</code> 方法时则又变成了渲染之后 <code>&lt;div&gt;0&lt;/div&gt;</code>。在 <code>mounted()</code> 方法之后,我们再调用生命周期的方法拿到的 <code>vm.$el</code> 都是跟 <code>mounted()</code> 方法中一样的。</p> <p>因此,我们是无法在 <code>beforeCreate()</code> 和 <code>created()</code> 这两个生命周期中对 <code>dom</code> 做操作的,因为根本就拿不到 Vue 实例对应的那个 <code>dom</code> 节点,所以一般我们是会在 <code>mounted()</code> 中做一些与 <code>dom</code> 有关的操作。</p> <p>剩下的生命周期方法为什么没有执行呢?接下来将介绍他们。</p> <h3 id="articleHeader2">beforeUpdate() 和 updated()</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="setTimeout(() => {<br /> app.message += 1;<br /> }, 1000);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript">setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> { app.message += <span class="hljs-number">1</span>; }, <span class="hljs-number">1000</span>);</code></pre> <p><span class="img-wrap"><img data-src="/img/bVbbg1g?w=245&amp;h=124" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="数据更新时生命周期执行结果" title="数据更新时生命周期执行结果" style="cursor: pointer;"></span></p> <h3 id="articleHeader3">activated() 和 deactivated()</h3> <p>TODO...</p> <h3 id="articleHeader4">beforeDestroy() 和 destroyed()</h3> <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="setTimeout(() => {<br /> app.$destroy(); // 该方法会销毁 Vue 实例<br /> }, 2000);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript">setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> { app.$destroy(); <span class="hljs-comment">// 该方法会销毁 Vue 实例</span> }, <span class="hljs-number">2000</span>);</code></pre> <p><span class="img-wrap"><img data-src="/img/bVbbg1i?w=243&amp;h=165" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="销毁 Vue 实例时生命周期执行结果" title="销毁 Vue 实例时生命周期执行结果" style="cursor: pointer;"></span></p> <h3 id="articleHeader5">renderError()</h3> <p>该方法只有在开发时才会被调用,在正式打包上线时不会被调用,它的目的是帮我们更容易地调试一些 <code>render()</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="new Vue({ // ... render(h) { throw new TypeError('render error'); }, renderError(h, err) { return h('div', {}, err.stack); } });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">new</span> Vue({ <span class="hljs-comment">// ...</span> render(h) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">'render error'</span>); }, renderError(h, err) { <span class="hljs-keyword">return</span> h(<span class="hljs-string">'div'</span>, {}, err.stack); } });</code></pre> <p><code>renderError()</code> 方法会在 <code>render()</code> 方法报错的时候被触发执行,页面渲染出来的内容就是错误信息。该方法只有在当前 Vue 实例的 <code>render()</code> 方法中出现错误的时候才会被触发,子组件的报错是不会被当前 Vue 实例捕获到的。</p> <h3 id="articleHeader6">errorCaptured()</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="new Vue({ // ... errorCaptured(h, err) { // ... } });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">new</span> Vue({ <span class="hljs-comment">// ...</span> errorCaptured(h, err) { <span class="hljs-comment">// ...</span> } });</code></pre> <h3 id="articleHeader7">生命周期图示解析</h3> <p>下面是 Vue 的生命周期的图示,接下来将对其进行简单的解析。</p> <p><span class="img-wrap"><img data-src="/img/bV4xju?w=1200&amp;h=3039" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="生命周期图示" title="生命周期图示" style="cursor: pointer;"></span></p> <p>在 <code>new</code> 一个 Vue 实例的过程中,首先执行了 <code>init()</code> 方法,在 <code>init()</code> 方法的 <code>Events &amp; LifeCycle</code> 之后 执行 <code>beforeCreate()</code> 方法,在 <code>init()</code> 方法的 <code>injections &amp; reactivity</code> 之后执行 <code>created</code> 方法。可以看到,在执行 <code>beforeCreate()</code> 的时候,Vue 实例的事件绑定是已经完成的了,但是它的响应式还没有,所以我们不在 <code>beforeCreate()</code> 中去修改 <code>data</code> 里面的数据,如果你要做一些 <code>ajax</code> 请求,然后给 Vue 实例的 <code>data</code> 赋新值,最早也要放在 <code>created()</code> 中来做。</p> <p>接下来是会判断是否设置了 <code>el</code> 属性,如果有,则会去判断是否有 <code>template</code> 属性,如果没有,则会等到调用了 <code>vm.$mount()</code> 方法之后再去判断是否有 <code>template</code> 属性。</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_5_expand" style="display: inline-table; border: none; height: 0px; margin: 0px; padding: 0px; position: relative; visibility: visible; width: 697px; background-color: transparent;"><ins id="aswift_5_anchor" style="display: block; border: none; height: 0px; margin: 0px; padding: 0px; position: relative; visibility: visible; width: 697px; background-color: transparent; overflow: hidden; opacity: 0;"><iframe width="697" height="175" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" onload="var i=this.id,s=window.google_iframe_oncopy,H=s&amp;&amp;s.handlers,h=H&amp;&amp;H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&amp;&amp;d&amp;&amp;(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_5" name="aswift_5" style="left:0;position:absolute;top:0;border:0px;width:697px;height:175px;"></iframe></ins></ins></ins></div> <p>如果设置了 <code>template</code> 属性,会将 <code>template</code> 解析为一个 <code>render()</code> 方法,如果没有设置,会将 <code>el</code> 属性对应的 <code>html</code> 解析为 <code>template</code>。这一步可以不设置 <code>template</code> 属性,通过手动调用 <code>render()</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="new Vue({ // ... render(h) { return h('div', {}, this.message); } });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="JavaScript"><span class="hljs-keyword">new</span> Vue({ <span class="hljs-comment">// ...</span> render(h) { <span class="hljs-keyword">return</span> h(<span class="hljs-string">'div'</span>, {}, <span class="hljs-keyword">this</span>.message); } });</code></pre> <p><code>render()</code> 方法接收一个入参,该入参是一个创建虚拟节点的方法,也就是说,<code>render()</code> 方法会返回一个虚拟节点。<code>render()</code> 方法会在 <code>beforeMount()</code> 与 <code>mounted()</code> 之间执行,创建 <code>vm.$el</code> 属性并且替换掉 <code>el</code> 属性对应的 <code>html</code> 节点,这就解释了前面的内容,在 <code>beforeMount()</code> 中 <code>vm.$el</code> 为 <code>index.html</code> 中的 <code>&lt;div id="app"&gt;&lt;/div&gt;</code>,在 <code>mounted()</code> 中 <code>vm.$el</code> 变成了渲染 <code>template</code> 后的 <code>&lt;div&gt;0&lt;/div&gt;</code>,这中间就是调用了 <code>render()</code> 方法。用这种方法来做,与设置 <code>template</code> 属性的结果是一样的。</p> <p>在用 <code>.vue</code> 文件开发的过程中,是没有在 Vue 实例中设置 <code>template</code> 属性的,而是在 <code>.vue</code> 文件中编写了 <code>&lt;template&gt;&lt;/template&gt;</code> 标签,再经过 vue-loader 处理,直接解析为 <code>render()</code> 方法,这个方式有一个好处。因为解析 Vue 实例中的 <code>template</code> 属性为 <code>render()</code> 方法是一个比较耗时的过程,使用 vue-loader 来帮我们处理,会使得在页面上执行 vue 代码的效率更高。</p> <p>在 <code>mounted()</code> 执行之后,这个 Vue 实例其实就已经初始化完成了。后续的生命周期都是需要一些外部的触发才会执行的,比如,有数据更新时会调用 <code>beforeUpdate()</code> 和 <code>updated()</code>,Vue 实例被销毁时会调用 <code>beforeDestroy()</code> 和 <code>destroyed()</code>。</p> <p>这就是一个 Vue 实例从新建到销毁的整个流程。</p> <p><strong><em>以上是我对 Vue 生命周期的一点见解,若有认为不正确或不妥当的地方,欢迎指正!</em></strong></p> <p></code></p>

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