如何禁止 vue 代理 data 的部分字段(属性)

1,338 阅读3分钟

前言

在我们的日常 vue 开发中,有这样几个场景,我有个 echart 对象或者 dom 对象,我在我这个组件的其他方法中,都需要访问这个对象,于是我用一个 data 字段保存他;我有一些配置性数据,比如我页面 table 的表头信息,表单项的标题,我只读一次后续就不需要监听,也不需要通过代码更改,为了能让界面读取我可能又是一个 data 字段保存。两种情况都可能导致页面新增大量的_ob__(观察者),进而造成不必要的性能浪费,本文主要是针对这种浪费,提供笔者已经知道的方法来进行优化。

1. 使用 methods 中的方法返回配置数据

Vue 组件在创建的过程中会自动代理 data 中的数据,但不会对方法的返回变量进行监控,所以通过方法返回一个或一组变量,template 中直接通过这个方法就可以获取变量,并显示就可以减少不必要的代理,如:

<template>
    <div class="userInfo">
        <div class="info-row" v-for="item in getOption()" :key="item.key">
            <div class="info-title">{{item.title}}</div>
            <div class="info-content">{{data[item.key]}}</div>
        </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                data: {
                    name: 'John Brown',
                    age: 18,
                    address: 'New York No. 1 Lake Park',
                    date: '2016-10-03',
                },
            };
        },
        methods: {
            getOption() {
                return [
                    {
                        title: '姓名',
                        key: 'name',
                    },
                    {
                        title: '年龄',
                        key: 'age',
                    },
                    {
                        title: '地址',
                        key: 'address',
                    },
                ];
            },
        },
    };
</script>

2. 利用 object.defineProperty 对 data 的字段进行赋值

Vue2 代理的基本原理就是深度遍历一个对象,并利用 object.defineProperty 的 get 和 set 方法对象的每个值类型字段进行代理,经过实践,发现 object.defineProperty 直接对 this 进行赋值的属性,不会被代理。如:

<template>
    <div class="home-page">
        <div class="home-text">这是测试页面</div>
    </div>
</template>

<script>
    export default {
        name: 'home',
        data() {
            let object = { proxyObject: [{ name: 'name1' }, { name: 'name2' }] };
            Object.defineProperty(object, 'fieldOptions', {
                value: [{ name: 'name1' }, { name: 'name2' }],
            });
            return object;
        },
        created() {
            Object.defineProperty(this, 'fieldOptions', {
                value: [{ name: 'name1' }, { name: 'name2' }],
            });
        },
        mounted() {
            console.info(this.proxyObject);
            console.info(this.fieldOptions);
        },
    };
</script>

console 的输出结果为:

 (2) [{…}, {…}, __ob__: Observer]
 (2) [{…}, {…}]

data 对应字段的键名以_或者$开头

这个在 vue 的官网中是这样描述的

官网内容

然后大胆的尝试了一波

<template>
    <div class="home-page">
        <div class="home-text">{{$data._dataInfo}}</div>
    </div>
</template>

<script>
    export default {
        name: 'home',
        data() {
            return {
                _dataInfo: { name: 'data' },
                $dataInfo: { name: 'data' },
                dataInfo: { name: 'data' },
            };
        },

        mounted() {
            console.info(this.$data._dataInfo);
            console.info(this.$data.$dataInfo);
            console.info(this.dataInfo);
        },
    };
</script>

结果界面输出:

{__ob__: Observer}
{__ob__: Observer}
{__ob__: Observer}

好像没什么用啊,查阅资料发现应该这么玩:

<template>
    <div class="home-page">
        <span>{{_dataInfo}}</span>
        <span>{{$dataInfo}}</span>
        <span>{{dataInfo}}</span>
    </div>
</template>

<script>
    export default {
        name: 'home',
        data() {
            return {
                _dataInfo: '',
                $dataInfo: '',
                dataInfo: '',
            };
        },
        created() {
            this._dataInfo = { name: 'data' };
            this.$dataInfo = { name: 'data' };
            this.dataInfo = { name: 'data' };
        },
        mounted() {
            console.info(this._dataInfo);
            console.info(this.$dataInfo);
            console.info(this.dataInfo);
        },
    };
</script>

结果界面在输出

{name: "data"}
{name: "data"}
{__ob__: Observer}

很有意思的是把 data 中的 dataInfo 字段删除后,dataInfo 也不在被代理大概代码如下:

<template>
    <div class="home-page">
        <span>{{_dataInfo}}</span>
        <span>{{$dataInfo}}</span>
        <span>{{dataInfo}}</span>
    </div>
</template>

<script>
    export default {
        name: 'home',
        data() {
            return { };
        },
        created() {
            this._dataInfo = { name: 'data' };
            this.$dataInfo = { name: 'data' };
            this.dataInfo = { name: 'data' };
        },
        mounted() {
            console.info(this._dataInfo);
            console.info(this.$dataInfo);
            console.info(this.dataInfo);
        },
    };
</script>

结果输出

{name: "data"}
{name: "data"}
{name: "data"}

object的frezee、seal或preventExtensions 方法

对象的一个字段可以通过 object.defineProperty 赋值来解决这个字段的 writable,但是一个对象都想设置这个属性怎么办。好像有点知识盲区了,最后通过查阅资料得到 object 的 frezee , seal 和 preventExtensions 方法可以做到 然后实验一波

<template>
    <div class="home-page">
        <span>{{freezeInfo}}</span>
        <span>{{sealInfo}}</span>
        <span>{{preventExtensionInfo}}</span>
    </div>
</template>

<script>
    export default {
        name: 'home',
        data() {
            return {
                freezeInfo: Object.freeze({ name: 'data' }),
                sealInfo: Object.seal({ name: 'data' }),
                preventExtensionInfo: Object.preventExtensions({ name: 'data' }),
            };
        },

        mounted() {
            console.info(this.freezeInfo);
            console.info(this.sealInfo);
            console.info(this.preventExtensionInfo);
        },
    };
</script>

输出结果

{name: "data"}
{name: "data"}
{name: "data"}

来个总结

以上就是笔者查找到的不代理 data 属性字段的方法,总结一下:

  • methods 虽然书写简单,但是在日常的开发中,我们都喜欢 v-bind绑定字段, v-on绑定 方法突然, v-bind绑定方法,略微不雅,而且理解容易产生偏差
  • object.defineProperty 这个光看就很丑,是真的丑,声明之前对对象进行设置,既不优雅也不好看,所以不建议使用
  • 属性名用_或者$,因为复杂对象会被代理,而且在 template 中读取很不方便,所以并不建议用于要显示的字段,反到是一些不需要代理并且需要全局缓存的属性,如全局要用的dom对象或echarts s用这个在合适不过
  • object.frezee,object.seal,object.preventExtensionInfo 方法,可能现目前看起来最合适的方式,特别是下一篇文章动态组件的使用用这两个方法输出的字段,尤其简单好用,只是在代码优雅程度上还是有些欠缺。