Skip to content
本页目录

Data

1. data 的处理

  1. Object.defineProperty - get,用于 依赖收集
  2. Object.defineProperty - set,用于 依赖更新
  3. 每个 data 声明的属性,都拥有一个的专属依赖收集器 subs
  4. 依赖收集器 subs 保存的依赖是 watcher
  5. watcher 可用于 进行视图更新

2. data 监听的实现

JS
function Observer(data) {

    // 递归出口
    if (!data || typeof data !== "object") return;
    
    Object.keys(data).forEach((key) => {

        // 使用defineProperty后属性里的值会被修改 需要提前保存属性的值
        let value = data[key];
        // 递归劫持data里的子属性
        Observer(value);
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,

            // 收集数据依赖
            get() {
                return value;
            },

            // 触发视图更新
            set(newVal) {
                value = newVal;

                // 处理赋值是对象时的情况
                Observer(newVal);
            },
        });
    });
  }
function Observer(data) {

    // 递归出口
    if (!data || typeof data !== "object") return;
    
    Object.keys(data).forEach((key) => {

        // 使用defineProperty后属性里的值会被修改 需要提前保存属性的值
        let value = data[key];
        // 递归劫持data里的子属性
        Observer(value);
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,

            // 收集数据依赖
            get() {
                return value;
            },

            // 触发视图更新
            set(newVal) {
                value = newVal;

                // 处理赋值是对象时的情况
                Observer(newVal);
            },
        });
    });
  }

3. data 代理的实现

JS
function initData(vm) {    

    var data = vm.$options.data;    

    var keys = Object.keys(data);    

    var i = keys.length;

    data = vm._data =  ( typeof data === 'function' ?  data.call(vm) : data ) || {};    

    while (i--) {        

        var key = keys[i];        

        if (只要不是_和$开头的属性) {

            proxy(vm, "_data", key);
        }
    }
}

function proxy(target, sourceKey, key) {    

    Object.defineProperty(target, key, {
        get() {            
            return this[sourceKey][key]
        },
        set(val) {            
            this[sourceKey][key] = val;
        }
    });
}
function initData(vm) {    

    var data = vm.$options.data;    

    var keys = Object.keys(data);    

    var i = keys.length;

    data = vm._data =  ( typeof data === 'function' ?  data.call(vm) : data ) || {};    

    while (i--) {        

        var key = keys[i];        

        if (只要不是_和$开头的属性) {

            proxy(vm, "_data", key);
        }
    }
}

function proxy(target, sourceKey, key) {    

    Object.defineProperty(target, key, {
        get() {            
            return this[sourceKey][key]
        },
        set(val) {            
            this[sourceKey][key] = val;
        }
    });
}

问题

1. 为什么 data 是一个函数?

  1. 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。
  2. 如果 data 是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间 data 不冲突,data 必须是一个函数(因为函数有自己的作用域)。

2. 如何重置 data 数据?

this.$data 获取当前状态下的 data
this.$options.data() 获取该组件初始状态下的 data

所以,下面就可以将初始状态的 data 复制到当前状态的 data,实现重置效果:

JS
Object.assign(this.$data, this.$options.data())
Object.assign(this.$data, this.$options.data())

当然,如果你只想重置 data 中的某一个对象或者属性:

JS
this.form = this.$options.data().form
this.form = this.$options.data().form

原因:是 Vue 实例化时有个 vm.$options = options 操作,所以 this.$options.data() 返回 data 中的初始化时数据