Vue的响应式原理

2021-07-17  本文已影响0人  史蒂夫sdf

vue数据双向绑定是通过数据劫持(Object.defineProperty)+发布者-订阅者模式来实现的

    function defineReactive(obj, key, val){
        Object.defineProperty(obj, key, {
            enumerable:true,//可枚举;改为false,for in时不会被遍历,但使用 "."依然可访问。
            configurable:true,//可配置;改为false之后,不能删除修改(不可逆)。
            writable:true,//可写入;改为false,当前属性变为只读。
            get(){//读取key触发,方法内不能读取key,否则会无限循环调用自身。
                console.log('get=>'+val)
                return val;//val是闭包值
            },
            set(newVal){
                console.log('set=>'+newVal);
                val = newVal;//改变闭包值
            }
        })
    }

发布者-订阅者模式

原始思路,一个商家,一个客户

let saler = {
    production: '地瓜',
    customerLists: [],
    regist(callback){
        this.customerLists.push(callback)
    },
    notify(){
        this.customerLists.forEach(v=>{
            v(this.production)
        })
    }
};
let customer = {
    name: 'xiaoming',
    readProduction(production){
        console.log(this.name+' like '+production);
    }
};
saler.regist(customer.readProduction);
saler.notify();

多个商家,多个客户

//Saler方法被生产者继承,每个生产者都是独立的
function Saler(prod){
    this.production = prod;
    this.customerLists = [];
}
Saler.prototype.regist = function regist(callback,context){
    this.customerLists.push(callback.bind(context))
}
Saler.prototype.notify = function notify(){
    this.customerLists.forEach(v=>{
        v(this.production)
    })
}
function Customer(name){
    this.name = name;
}
Customer.prototype.readProduction = function readProduction(production){
    console.log(this.name+' like '+production);
}
let saler0 = new Saler('红薯');
let customer0 = new Customer('xiaoqiang');
saler0.regist(customer0.readProduction,customer0);
saler0.notify();
let saler1 = new Saler('米粉');
let customer1 = new Customer('xiaoli');
saler1.regist(customer1.readProduction,customer1);
saler1.notify();

一个售卖中介,多个生产者,多个客户

// saler作为公共方法被每个生产者使用
let Saler = {
    customerLists: [],
    regist(callback,context){
        this.customerLists.push(callback.bind(context))
    },
    notify(){
        this.customerLists.forEach(v=>{
            v(this.production)
        })
    }
}
function productor(prod){
    this.production = prod;
    this.__proto__ = Saler;
}


function Customer(name){
    this.name = name;
}
Customer.prototype.readProduction = function readProduction(production){
    console.log(this.name+' like '+production);
}

let saler0 = new productor('红薯');
let customer0 = new Customer('xiaoqiang');
saler0.regist(customer0.readProduction,customer0);
saler0.notify();
let saler1 = new productor('米粉');
let customer1 = new Customer('xiaoli');
saler1.regist(customer1.readProduction,customer1);
saler1.notify();

发布者-订阅者模式+数据劫持:数据劫持是数据修改时的动作,整个发布者和订阅者都要在这个动作内实现。

//一个订阅者应该对应一个发布者,没有所谓的中介
// saler销售中介作为公共方法被每个生产者使用
function Saler(){
    this.customerLists = [];
}
Saler.prototype.regist = function regist(callback,context){
    this.customerLists.push(callback.bind(context))
}
Saler.prototype.notify = function notify(){
    this.customerLists.forEach(v=>{
        v(this)
    })
}
function productor(obj){
    Object.assign(this,obj);
    Saler.call(this);
    this.__proto__ = new Saler();//这种继承方式使得每个生产者共用一个销售中介
}


function Customer(name){
    this.name = document.getElementById(name);
}
Customer.prototype.readProduction = function readProduction(production){
    console.log(this.name,production);
}

function defineItem(key, value, item, productor){
    Object.defineProperty(item, key, {
        enumerable: true,
        configurable: true,
        get(){
            return value;
        },
        set(val){
            value = val;
            productor.notify();
        }
    })
}
function reactInit(obj,productor){
    for(let i in obj){
        if(typeof obj[i] === 'object'){
            reactInit(obj[i],productor);
        }else if(typeof obj[i] !== 'function'){
            defineItem(i, obj[i], obj, productor);
        }
    }
}
function dataInit(obj){
    let productor0 = new productor(obj);//初始化发布者对象
    let customer0 = new Customer('app');//初始化订阅者对象view
    productor0.regist(customer0.readProduction,customer0);//将订阅者的回调函数注册到发布者对象
    reactInit(productor0,productor0);//初始化数据
    customer0.readProduction(productor0);//初始化view
    return productor0;//返回数据对象
}
let vm = dataInit({
    production:'红薯',
    p2:'番茄',
    p3:{
        p31:'里脊',
        p32:'牛排'
    }
})
v2 = dataInit({q:123456})

《JavaScript设计模式与开发实践》书中详细介绍了发布-订阅模式。
vue 的双向绑定原理及实现

上一篇下一篇

猜你喜欢

热点阅读