zoukankan      html  css  js  c++  java
  • 记 vue 表单的一个性能问题

    背景

    产品反馈表单页太卡了,这是一个有意思的情况,让我看看。

    如图所见,当在 input 输入数据的时候,连续输入会感觉明显的延迟。

    那个项目最多情况下,表单数量达到千数。笔者在 demo 里简化实现,并把表单数量提升到 10000,把下面的代码粘贴运行一边就能得到卡顿效果。

    <!DOCTYPE html>
    <html>
    <head>
        <title>Form Demo</title>
    </head>
    <body>
        <div id="app">
            <template v-for="item in options">
                <input type="text" v-model="item.data">    
            </template>
        </div>
    
        <!-- Vue.js v2.6.11 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script>
            let options = []
            for (let i = 0; i < 10000; i++) {
                options.push({
                    data: '',
                })
            }
            var app = new Vue({
                el: '#app',
                data: {
                    options: options,
                },
            })
            window.app = app;
            console.log(app);
    
            // 接着控制台里输入
            // var event = new CustomEvent('test', { 'detail': ['a', 'c', 'e', 'f', 'b', 'd'] }); window.dispatchEvent(event);
            // 能把 message 改为这个数组
        </script>
    </body>
    </html>
    

    前置知识梳理

    众所周知,vue2 里的数据使用 Object.defineProperty 设定 get/set 来进行劫持,而当数据改变时,将会触发 set,在 set 里触发广播通知被观察者进行更新的。

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
            // ...
        },
        set: function reactiveSetter (newVal) {
            // ...
            dep.notify();
        }
    })
    
    // ------ dep 的结构如下
    /*{
        id: 118,
        subs: [Watcher]
    }*/
    
    // ------- Watcher 的结构
    /*{
        vm,
        cb,
        deps,
        express,
        ...
    }*/
    

    Vue 把更新收集到队列里,并每隔一段时间去执行,一般是这些被观察者 Watcher 的表达式。

    总之在这里执行的更新语句是

    expression: "function () { vm._update(vm._render(), hydrating); }"
    

    其中 _render 执行完后得出此组件的 Vnode,并传给 Vue.prototype._update 语句进行更新。

    ok,知识梳理完毕,那么到底 _update 里怎么做的,让页面更新渲染如此之慢呢?

    调试过程

    Vue.prototype._update 打断点调试,如下图:

    省略函数进入步骤 patch -> patchVnode -> updateChildren,直到 updateChildren 发现核心比对逻辑

    比对这 10000 个节点。简单来说相同 key 和标签名的被判定为相同节点,相同节点还得继续去递归比对其子节点是否相同。并且比对过程中,还需要判定更新的内容里有 attr、 class、listener、style 等等信息,由此产生的计算量还是挺大的。笔者下图与本次调试无关,但能简单揭示下比对逻辑。

    笔者在这里 pachVnode 里执行 updateChildren 的地方,打印耗时,发现当 input 为 1000 项的时候每输入一个字符耗时一般是个位数的毫秒。

    而 input 为 10000 项时,每个字符输入响应需要 50~100 毫秒的话,快速输入一串字符,产生的卡顿感就会比较厉害。

    而在我们实际的项目中,表单复杂的多,比对的层级深,或许 1000 不到的表单就能产生这样的效果。

    解决

    既然更新 10000 个节点费力,那何不缩小更新范围呢。把表单拆成若干组,每组包裹在组件中,输入时只会更新那个组件,影响范围就笑得多。由此产生的更新如下:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Form Demo</title>
    </head>
    <body>
        <div id="app">
            <input-group :forms="forms" v-for="(forms, index) in options" :key="index"></input-group>
        </div>
    
        <!-- Vue.js v2.6.11 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script>
            Vue.component('input-group', {
                props: ['forms'],
                template: `<div>
                    <template v-for="item in forms">
                        <input type="text" v-model="item.data">
                    </template>
                </div>`
            })
    
            let options = []
            for (let i = 0; i < 100; i++) {
                for (let j = 0; j < 100; j++) {
                    options[i] = options[i] || [];
                    options[i].push({
                        data: '',
                    })
                }
                
            }
            var app = new Vue({
                el: '#app',
                data: {
                    options: options,
                },
            })
            window.app = app;
            console.log(app);
    
            // 接着控制台里输入
            // var event = new CustomEvent('test', { 'detail': ['a', 'c', 'e', 'f', 'b', 'd'] }); window.dispatchEvent(event);
            // 能把 message 改为这个数组
        </script>
    </body>
    </html>
    

    每个字符的更新就降低到 3ms 的样子,响应快得多了。

    总结

    本质上这就是一个原则,不要在一个 vue 组件上绑定那么多的元素,请拆分成多个子组件。。

  • 相关阅读:
    上传代码到github
    AFN多文件进度下载
    NSURLSession各文件关系
    H5动静分离
    iOS设备获取总结
    iOS与JS开发交互总结
    iOS11 push控制器tabbar上移问题
    解决iOS11 UIScrollView下移问题
    关于react16.4——错误边界
    关于react16.4——上下文Context
  • 原文地址:https://www.cnblogs.com/everlose/p/12541925.html
Copyright © 2011-2022 走看看