1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        10個技巧!實現(xiàn)Vue.js極致性能優(yōu)化(建議收藏)

        共 7828字,需瀏覽 16分鐘

         ·

        2021-09-22 04:04


        導(dǎo)語 | Vue是一套用于構(gòu)建用戶界面的漸進式的JavaScript框架。它具有體積小,更高的運行效率,雙向數(shù)據(jù)綁定,生態(tài)豐富、學(xué)習(xí)成本低等優(yōu)點,所以Vue也被廣泛用在移動端跨平臺框架上。接下來,我將為大家梳理10個實現(xiàn)Vue.js極致性能優(yōu)化的技巧,以供大家在實際運用中使用。


        Vue框架通過數(shù)據(jù)雙向綁定和虛擬DOM技術(shù),幫我們處理了前端開發(fā)中最臟最累的DOM操作部分,我們不再需要去考慮如何操作DOM以及如何最高效地操作DOM,但是我們?nèi)匀恍枰リP(guān)注Vue在跨平臺項目性能方面的優(yōu)化,使項目具有更高效的性能、更好的用戶體驗。



        一、v-for遍歷必須為item添加key,

               且避免同時使用v-if


        在列表數(shù)據(jù)進行遍歷渲染時,需要為每一項item設(shè)置唯一key值,方便Vue.js內(nèi)部機制精準(zhǔn)找到該條列表數(shù)據(jù)。當(dāng)state更新時,新的狀態(tài)值和舊的狀態(tài)值對比,較快地定位到diff。


        我們在使用的使用經(jīng)常會使用index(即數(shù)組的下標(biāo))來作為key,但其實這是不推薦的一種使用方法。


        舉個例子:


        var list = [    {        id: 1,        name: 'test1',    },    {        id: 2,        name: 'test2',    },    {        id: 3,        name: 'test3',    },]
        <div v-for="(item, index) in list" :key="index" >{{item.name}}</div>


        在最后一條數(shù)據(jù)后再加一條數(shù)據(jù):


        var list = [    {        id: 1,        name: 'test1',    },    {        id: 2,        name: 'test2',    },    {        id: 3,        name: 'test3',    },    {        id: 4,        name: '我是在最后添加的一條數(shù)據(jù)',    },]


        此時前三條數(shù)據(jù)直接復(fù)用之前的,新渲染最后一條數(shù)據(jù),此時用index作為key,沒有任何問題。


        在中間插入一條數(shù)據(jù):


        var list = [    {        id: 1,        name: 'test1',    },    {        id: 4,        name: '我是插隊的那條數(shù)據(jù)',    },    {        id: 2,        name: 'test2',    },    {        id: 3,        name: 'test3',    },]


        此時更新渲染數(shù)據(jù),通過index定義的key去進行前后數(shù)據(jù)的對比,發(fā)現(xiàn):


        之前的數(shù)據(jù)                         之后的數(shù)據(jù)
        key: 0 index: 0 name: test1 key: 0 index: 0 name: test1
        key: 1 index: 1 name: test2 key: 1 index: 1 name: 我是插隊的那條數(shù)據(jù)
        key: 2 index: 2 name: test3 key: 2 index: 2 name: test2
        key: 3 index: 3 name: test3


        通過上面清晰的對比,發(fā)現(xiàn)除了第一個數(shù)據(jù)可以復(fù)用之前的之外,另外三條數(shù)據(jù)都需要重新渲染。


        是不是很驚奇,我明明只是插入了一條數(shù)據(jù),怎么三條數(shù)據(jù)都要重新渲染?而我想要的只是新增的那一條數(shù)據(jù)新渲染出來就行了。


        最好的辦法是使用數(shù)組中不會變化的那一項作為key值,對應(yīng)到項目中,即每條數(shù)據(jù)都有一個唯一的id,來標(biāo)識這條數(shù)據(jù)的唯一性;使用id作為key值,我們再來對比一下向中間插入一條數(shù)據(jù),此時會怎么去渲染。


        之前的數(shù)據(jù)                               之后的數(shù)據(jù)
        key: 1 id: 1 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1
        key: 2 id: 2 index: 1 name: test2 key: 4 id: 4 index: 1 name: 我是插隊的那條數(shù)據(jù)
        key: 3 id: 3 index: 2 name: test3 key: 2 id: 2 index: 2 name: test2
        key: 3 id: 3 index: 3 name: test3


        現(xiàn)在對比發(fā)現(xiàn)只有一條數(shù)據(jù)變化了,就是id為4的那條數(shù)據(jù),因此只要新渲染這一條數(shù)據(jù)就可以了,其他都是就復(fù)用之前的。


        總結(jié):所以一句話,key的作用主要是為了高效的更新虛擬DOM。另外Vue中在使用相同標(biāo)簽名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓Vue可以區(qū)分它們,否則Vue只會替換其內(nèi)部屬性而不會觸發(fā)過渡效果。


        v-for遍歷避免同時使用v-ifv-for比v-if優(yōu)先級高,如果每一次都需要遍歷整個數(shù)組,將會影響速度,尤其是當(dāng)之需要渲染很小一部分的時候,必要情況下應(yīng)該替換成computed屬性。



        二、長列表性能優(yōu)化


        Vue會通過Object.defineProperty對數(shù)據(jù)進行劫持,來實現(xiàn)視圖響應(yīng)數(shù)據(jù)的變化,然而有些時候我們的組件就是純粹的數(shù)據(jù)展示,不會有任何改變,我們就不需要Vue來劫持我們的數(shù)據(jù),在大量數(shù)據(jù)展示的情況下,這能夠很明顯的減少組件初始化的時間,那如何禁止Vue劫持我們的數(shù)據(jù)呢?可以通過Object.freeze方法來凍結(jié)一個對象,一旦被凍結(jié)的對象就再也不能被修改了。


        export default {  data: () => ({    users: {}  }),
        async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); }};



        三、Vue組件中的data是函數(shù)而不是對象


        export default {  data() {    // data是一個函數(shù),data: function() {}的簡寫    return {      // 頁面要初始化的數(shù)據(jù)      name: 'bartonwang',    };  },};


        而非如下所示:


        export default {  data: {    // data是一個對象    name: 'bartonwang',  },};


        當(dāng)一個組件被定義,data必須聲明為返回一個初始數(shù)據(jù)對象的函數(shù),因為組件可能被用來創(chuàng)建多個實例,復(fù)用在多個頁面。


        如果data是一個純碎的對象,則所有的實例將共享引用同一份data數(shù)據(jù)對象,無論在哪個組件實例中修改data,都會影響到所有的組件實例。


        如果data是函數(shù),每次創(chuàng)建一個新實例后,調(diào)用data函數(shù),從而返回初始數(shù)據(jù)的一個全新副本數(shù)據(jù)對象。


        這樣每復(fù)用一次組件,會返回一份新的data數(shù)據(jù),類似于給每個組件實例創(chuàng)建一個私有的數(shù)據(jù)空間,讓各個組件的實例各自獨立,互不影響,保持低耦合。



        四、Vue鉤子函數(shù)之鉤子事件hookEvent,

               監(jiān)聽組件簡化代碼


        用法:


        1. 通過$on(eventName, eventHandler) 偵聽一個事件。

        2. 通過$once(eventName,eventHandler) 一次性偵聽一個事件。

        3. 通過$off(eventName, eventHandler) 停止偵聽一個事件。


        通常實現(xiàn)一個定時器的調(diào)用與銷毀我可能會以以下方式實現(xiàn):


        export default{  data(){    timer:null  // 需要創(chuàng)建實例  },
        mounted(){ this.timer = setInterval(()=>{ //具體執(zhí)行內(nèi)容 console.log('1'); },1000); }
        beforeDestory(){ clearInterval(this.timer); this.timer = null; }}


        這種方法存在的問題是:


        vue實例中需要有這個定時器的實例,感覺有點多余。創(chuàng)建的定時器代碼和銷毀定時器的代碼沒有放在一起,不容易維護,通常很容易忘記去清理這個定時器。


        使用$on(‘hook:’)監(jiān)聽beforeDestory生命周期可以避免該問題,并且因為只需要監(jiān)聽一次,所以使用$once進行注冊監(jiān)聽。


        export default{  methods:{    fn(){      const timer = setInterval(()=>{        console.log('1');      },1000);
        this.$once('hook:beforeDestory',()=>{ // 監(jiān)聽一次即可 clearInterval(timer); timer = null; }) } }}



        五、組件懶加載


        在單頁應(yīng)用中,如果沒有應(yīng)用懶加載,運用webpack打包后的文件將會異常地大,造成進入首頁時需要加載的內(nèi)容過多,延時過長,不利于用戶體驗,而運用懶加載則可以將頁面進行劃分,需要的時候加載頁面,可以有效的分擔(dān)首頁所承擔(dān)的加載壓力,減少首頁加載用時。


        Vue.js 2.0組件級懶加載方案:


        • 支持組件可見或即將可見時懶加載

        • 支持組件延時加載

        • 支持加載真實組件前展示骨架組件,提高用戶體驗

        • 支持真實組件代碼分包異步加載


        安裝:


        npm install@xunlei/vue-lazy-component


        在組件中實現(xiàn)局部注冊組件:


        import { component as VueLazyComponent } from '@xunlei/vue-lazy-component'
        export default { components: { 'vue-lazy-component': VueLazyComponent }}


        需要懶加載的組件將其包裹在vue-lazy-component中,slot值為skeleton指的是在懶加載過程中顯示的加載狀態(tài)組件。


        <vue-lazy-component :timeout="5000" tagName="div">      <child1 slot="skeleton" />      <child2 />      <child3 />      <child4 />      <child5 /></vue-lazy-component>



        六、非響應(yīng)式數(shù)據(jù)


        初始化時,Vue會對data做getter、setter改造。在Vue的文檔中介紹數(shù)據(jù)綁定和響應(yīng)時,特意標(biāo)注了對于經(jīng)過Object.freeze()方法的對象無法進行更新響應(yīng)。


        性能提升對比


        在基于Vue的一個big table benchmark里,可以看到在渲染一個一個1000x10的表格的時候,開啟Object.freeze()前后重新渲染的對比。



        開啟優(yōu)化之前



        開啟優(yōu)化之后



        在這個例子里,使用了Object.freeze()比不使用快了4倍。


        為什么Object.freeze()的性能會更好,不使用Object.freeze()的CPU開銷?



        使用Object.freeze()的CPU開銷:



        對比可以看出,使用了Object.freeze()之后,減少了observer的開銷。



        七、不要將所有的數(shù)據(jù)都放到data中


        data中的數(shù)據(jù)都會增加getter和setter,又會收集watcher,這樣還占內(nèi)存。不需要響應(yīng)式的數(shù)據(jù)我們可以定義在實例上。




        八、v-for元素綁定事件代理


        事件代理作用主要是2個:


        1. 將事件處理程序代理到父節(jié)點,減少內(nèi)存占用率。

        2. 動態(tài)生成子節(jié)點時能自動綁定事件處理程序到父節(jié)點。


        • 不使用事件代理,每個span節(jié)點綁定一個click事件,并指向同一個事件處理程序:


        <div>      <span         v-for="(item,index) of 100000"         :key="index"         @click="handleClick">        {{item}}      </span> </div>


        • 不使用事件代理,每個span節(jié)點綁定一個click事件,并指向不同的事件處理程序


        <div>      <span         v-for="(item,index) of 100000"         :key="index"         @click="function () {}">        {{item}}      </span>  </div>


        • 使用事件代理


        <div  @click="handleClick">      <span         v-for="(item,index) of 100000"          :key="index">        {{item}}      </span> </div>



        可以看到使用事件代理無論是監(jiān)聽器數(shù)量和內(nèi)存占用率都比前兩者要少,同時對比3個圖中監(jiān)聽器的數(shù)量并沒有發(fā)現(xiàn)Vue會自動做事件代理,但是一般給v-for綁定事件時,都會讓節(jié)點指向同一個事件處理程序(第二種情況可以運行,但是eslint會警告),一定程度上比每生成一個節(jié)點都綁定一個不同的事件處理程序性能好,但是監(jiān)聽器的數(shù)量仍不會變,所以使用事件代理會更好一點。


        代碼使用:


        <ul @click="meths">      <li v-for="(item,key) in 10" :key="key" :data-index="key">{{item}}</li> </ul>
        meths(e) { if (e.target.nodeName.toLowerCase() === 'li') { console.log(e.target.innerHTML) console.log(e.target.dataset) }
        }



        九、函數(shù)式組件


        函數(shù)式組件是無狀態(tài),它無法實例化,沒有任何的生命周期和方法。創(chuàng)建函數(shù)式組件也很簡單,只需要在模板添加functional聲明即可。一般適合只依賴于外部數(shù)據(jù)的變化而變化的組件,因其輕量,渲染性能也會有所提高。


        組件需要的一切都是通過context參數(shù)傳遞。它是一個上下文對象,具體屬性查看文檔。這里props是一個包含所有綁定屬性的對象。


        函數(shù)式組件




        十、函數(shù)式組件provide和inject組件通信


        痛點:常用的父子組件通信方式都是父組件綁定要傳遞給子組件的數(shù)據(jù),子組件通過props屬性接收,一旦組件層級變多時,采用這種方式一級一級傳遞值非常麻煩,而且代碼可讀性不高,不便后期維護。


        Vue提供了provide和inject幫助我們解決多層次嵌套嵌套通信問題。在provide中指定要傳遞給子孫組件的數(shù)據(jù),子孫組件通過inject注入祖父組件傳遞過來的數(shù)據(jù),可以輕松實現(xiàn)跨級訪問父組件的數(shù)據(jù)。


        provide:是一個對象,或者是一個返回對象的函數(shù)。里面呢就包含要給子孫后代的東西,也就是屬性和屬性值。注意:子孫層的provide會掩蓋祖父層provide中相同key的屬性值。


        inject:一個字符串?dāng)?shù)組,或者是一個對象。屬性值可以是一個對象,包含from和default默認值,from是在可用的注入內(nèi)容中搜索用的key (字符串或Symbol),意思就是祖父多層provide提供了很多數(shù)據(jù),from屬性指定取哪一個key;default指定默認值。





        從上面這個例子可以看出,只要在父組件中調(diào)用了,那么在這個父組件生效的生命周期內(nèi),所有的子組件都可以調(diào)用inject來注入父組件中的值。


        在使用場景中,肯定是希望父組件的數(shù)據(jù)一旦發(fā)生改變,子孫組件獲取到的也是父組件更新后的數(shù)據(jù)。那么,怎么實現(xiàn)父組件與子孫組件所綁定的數(shù)據(jù)動態(tài)響應(yīng)呢?



        -------------------parent.vue----------------------
        provide(){
        return {
        // keyName: {name:this.name}, // value 是對象才能實現(xiàn)響應(yīng)式,也就是引用類型
        keyName: this.changeValue // 通過函數(shù)的方式也可以[注意,這里是把函數(shù)作為value,而不是this.changeValue()]
        // keyName: 'test' value 如果是基本類型,就無法實現(xiàn)響應(yīng)式
        } },
        data(){
        return { name:'張三' } },
        methods: {
        changeValue(){ this.name = '改變后的名字-李四' } }
        -------------grandson.vue-----------------
        inject:['keyName'] create(){ console.log(this.keyName) // 改變后的名字-李四}



         作者簡介


        王雄

        騰訊客戶端開發(fā)工程師

        騰訊客戶端開發(fā)工程師,目前在IEG增值服務(wù)部從事掌上道聚城app開發(fā)工作,有豐富的跨平臺weex,react-native,flutter開發(fā)經(jīng)驗。



         推薦閱讀


        為什么WebAssembly不是JavaScript的終結(jié)者,而是它的“助推器”?

        快人一步掌握vue源碼解讀,搞定diff算法?。ǔ敿殻?/span>

        Linux入門必看:如何在60秒內(nèi)分析Linux性能?

        “Docker VS Kubernetes”是共生還是相愛相殺?





        瀏覽 68
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            麻豆影视在线 | 精品少妇人妻Av久久久牛牛 | 大鸡巴在线视频 | 黑人无码 | 婷婷天堂 | 91嫩草影院在线观看 | 91在线91拍拍在线91 | 韵韵阳具 | 先锋资源影音资源 | 国产日韩精品一区二区三区在线 |