手写一个vue数据劫持

一、手写一个vue数据劫持
二、介绍
这里学习的是 Vue 2.x 下双向绑定的方法,是 数据劫持 + 订阅发布者模式。用 Object.defineProperty(obj,prop,descriptor) 方法去绑定 data 中的值,然后在发现值变化的时候触发更新函数,去更新视图。

2.1介绍下函数

Object.defineProperty(obj,key,descriptor)

`obj` : 目标对象。
`key` : 需要定义的属性或方法的名称。
`descriptor` : 目标属性所拥有的特性。

`descriptor` 有以下的属性:
// 1. value: 属性的值
// 2. writable: 如果为false,属性的值就不能被重写。
// 3. get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
// 4. set: 一旦目标属性被赋值,就会调回此方法。
// 5. configurable: 可否被修改,如果为false,writable, configurable, enumerable 就不能改。
// 6. enumerable: 是否可被枚举,就是在循环或者 keys 的时候是不是可以被遍历到。

2.2 举个小栗子

let data = {
    name: 'nys',
}

Object.keys(data).forEach(key=> {
    Object.defineProperty(data, key, {
        enumerable: true, //可被枚举
        configurable: true, //可被修改
        get: function(){
            console.log('get');
        },
        set: function(){
            console.log('name changed');
        }
    })
})
console.log(data.name)
// get
data.name = 'test'
// name changed

三、发布订阅
上面对 data 的值进行了监听,在 set 函数里,当数据变化,对其有操作就可以被监听到,然后就可以对数据视图做出改变。但是同时也存在一个问题,当数据被初始化的时候,系统对每个数据进行了订阅和监听,如果后期在对象上加一个属性,那么就不会被监听到,所以需要使用 Vue.set() 。

3.1 订阅器Dep
为了完成了数据的’可观测’,我们需要创建一个依赖收集容器,用来容纳所有的“订阅者”,我们就可以在数据被使用的时候通知那些依赖该数据的视图更新了,一旦数据发生变化,就统一通知更新。创建消息订阅器Dep:

function Dep(){
    let self = this;
    self.subs = [];
    self.target = null;
    
    // 添加订阅
    self.addSub = function(data){
        self.subs.push(data);
    }
    
    // 判断是否增加订阅者
    self.depend = function() {
        if (Dep.target) {
            this.addSub(Dep.target);
        }
    }
    // 通知试图更新
    self.notify = function(){
        self.subs.forEach(sub=>{
            sub.update();
        })
    }
}

然后我们把数据劫持和添加订阅改造下并封装成个函数:

function defineReactive(data, key, val) {
    let dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            dep.depend();
            return val;
        },
        set(newVal) {
            val = newVal;
            dep.notify();
        }
    })
}

function observable(data) {
    if (!data || typeof data !== 'object') {
        return;
    }
    let keys = Object.keys(data);
    keys.forEach((key) => {
        defineReactive(data, key, data[key])
    })
    return data;
}

3.2 订阅者Watcher
订阅者 Watcher 在初始化的时候需要将自己添加进订阅器 Dep 中,那该如何添加呢?我们已经知道监听器 Observer 是在 get 函数执行了添加订阅者 Watcher 的操作的,所以我们只要在订阅者 Watcher 初始化的时候触发对应的 get 函数去执行添加订阅者操作即可,那要如何触发 get 的函数,再简单不过了,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了 Object.defineProperty( ) 进行数据监听。这里还有一个细节点需要处理,我们只要在订阅者 Watcher 初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在Dep.target 上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者 Watcher 的实现如下:

function watcher(vm, exp, cb) {
    let self = this;
    self.vm = vm;        //实例对象
    self.exp = exp;      //绑定的属性值
    self.cb = cb;        //视图更新函数
    self.value = self.get();

    self.update = function () {
        let value = self.vm.data[self.exp];
        let oldVal = self.value;
        if (value !== oldVal) {
            self.value = value;
            self.cb.call(self.vm, value, oldVal);
        }
    }
    self.get = function() {
        Dep.target = self; // 缓存自己
        let value = self.vm.data[self.exp] // 强制执行监听器里的get函数
        Dep.target = null; // 释放自己
        return value;
    }
}

4、总结
大概的流程就是,我们用 Object.defineProperty( ) 设置一个监听器,用来监听所有的属性,如果发生了变化就通知订阅者,是不是要更新,然后通过订阅器来同意管理订阅者。

你可能感兴趣的:(手写一个vue数据劫持)