vue双向数据绑定原理学习

vue双向数据

整个过程分为三部分:
1、数据劫持监听Observer
2、发布订阅者模式Watcher
3、解析器Complier
其实很好理解,想要实现双向数据绑定,首先必须是视图和数据双向监听:
1、数据这边通过数据劫持的方式,可以监听到数据的读写,当发现读取数据的时候,判断是否为订阅者,如果是将这个对象存起来Dep,下次数据有更新的时候通知订阅者。
2、视图这边通过Complier解析器,将逐层遍历每个节点,判断是否有指令属性,并根据指令类型进行相应的操作(生成订阅器watcher并绑定更新函数,更新函数new Watcher同事添加一些更新动作:可能是监听input(v-model)、监听click(事件)、更新文本信息({{}})等),当生成订阅器的时候,每次读取数据的时候就会被存到上面说的Dep中,然后下次数据更新watcher就会通知到,进而更新视图层
小结:视图和数据有自己的劫持、监听方式,然后通过watcher(也就是核心发布订阅者模式)关联起来

具体代码实现

数据劫持监听Observer

//创建,传入要劫持的数据
function Observer (data) {
	this.data = data;
	this.goReactive(data);
}
Observer.prototype = {
	goReactive: function (data) {
		let self = this;
		Object.key(data).forEach(function(key, index){
			//每个属性进行
			self.observerReactive(data, key, data[key]);
		})
	},
	observerReactive: function (data, key, val) {//这里的val可能还是一个对象
		let dep = new Dep();
		let childObj = observer(val);//如果子属性也是对象的话,递归遍历
		Object.defineProperty(data, key, {
			enumerable: true,
            configurable: true,
            get: function getter () {
            	if (Dep.needWatch) {//判断读取数据的是否需要订阅
            		dep.add(Dep.needWatch);
            	}
            	return val;
            },
            set: function setter (newVal) {
            	if (newVal === val) {
            		return;
            	} else {
            		val = newVal;
            		dep.notify();//通知订阅者
            	}
            }
		});
	}
};
//创建监听存储器,上面提到的,每次数据劫持到监听到被读取的时候会判断是否是订阅者,是的话就会存储
function Dep(){
	this.watchers = [];//订阅者们
}
Dep.prototype = {
	add: function (watcher){
		this.watchers.push(watcher);
	},
	notify: function () {
		this.watchers.forEach(function (watcher) {
			watcher.update();//也是文章开头提到的更新函数,当解析器解析指令之后,会进行对应的编译,然后当数据发生变化时会触发对应的更新数据
		})
	}
};
//这里主要是为了遍历子属性,当数据劫持的时候,很有可能是{1:{2:}}这种形式的,那么里面的对象2也是需要进行劫持监听的
function observe(value) {
    if (!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
};

发布订阅者模式Watcher

function Watcher (vm, attrName, callback) {
	this.vm = vm;
	this.attrName = attrName;
	this.callback = callback;
	this.value = this.get()//读取数据,添加上面的订阅者列表中dep
}
Watcher.prototype = {
	update: function (){
		this.start();
	},
	start: function (){
		let oldVal = this.value,nowVal = this.vm.data[this.attrName];
		if (nowVal !== oldVal) {
			this.value = nowVal;
		}
		this.callback.call(vm, nowVal, oldVal);
	},
	get: function (){//读取并添加到订阅者
		Dep.needWatch = this;
		let value = this.vm.data[this.attrName];
		Dep.needWatch = null;
		return value;
	},
};

解析器Complier

function Complier (vm, el) {
	this.vm = vm;
	this.el = el;
	this.fragment= null;//创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片一次性添加到document中
	this.init();
}
Complier.prototype = {
	init: function () {
		if (this.ele) {
			this.fragment= this.eleProducer(this.ele);
			this.goComplier(this.fragment);
			this.el.appendChild(this.fragment);
		}
	},
	//碎片化
	eleProducer: function (ele){
		let fragment= document.createDocumentFragment();
		let child = ele.firstChild;
		while (child) {
			// 将Dom元素移入fragment中
            fragment.appendChild(child);
            child = ele.firstChild
		}
		return fragment;
	},
	//开始解析
	goComplier: (ele) {
		let childNodes = ele.childNodes,self = this;
		childNodes.each(function (index, node) {
			self.goComplierByNodeType(node);
		})
	},
	compileOrder: function (node) {
		let attrs= node.attributes, self = this;
        Array.prototype.forEach.call(attrs, function(attr) {
            let attrName = attr.name;
            if (attrName.indexOf('v-') !== -1) {//vue指令开头都是以v-开头,假设事件都是v-on:先不考虑简写情况
                let attrVal = attr.value, order = attrName.substring(2);
                if (order.indexOf('on:') !== -1) {  // 事件指令
                    self.eventComplier(node, self.vm, attrValue, order);
                } else {  // v-model 指令
                     self.modelComplier(node, self.vm, attrValue, order);
                }
                node.removeAttribute(attrName);
            }
        });
	},
	eventComplier: function (node, vm, attrValue, order) {
		let eventType = dir.split(':')[1], callback = vm.methods && vm.methods[order];
        if (eventType && callback ) {
            node.addEventListener(eventType, callback.bind(vm), false);
        }
	}, 
	modelComplier: function (node, vm, attrValue, dataName) {
		let self = this,  val = this.vm[dataName];
       node.value = typeof value == 'undefined' ? '' : value;//更新视图
        new Watcher(this.vm, dataName, function (value) {
            node.value = typeof value == 'undefined' ? '' : value;//更新视图
        });
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }
            self.vm[dataName] = newValue;
            val = newValue;
        });
	}, 
	textComplier: function (node, dataName) {
		let self = this,  initText = this.vm[dataName];
        node.textContent = typeof value == 'undefined' ? '' : value;
        new Watcher(this.vm, dataName, function (value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        });
	},
	//根据不同的节点类型调用对应的更新函数
	goComplierByNodeType: function (node) {
		 	let reg = /\{\{(.*)\}\}/, text = node.textContent;
		 	//元素节点1,属性节点2,文本3,一共12种,其余的用不到
            if (node.nodeType == 1) {  //判断指令 
            	this.compileOrder(node);
            } else if (node.nodeType == 3 && reg.test(text)) {//文本
                this.textComplier(node, reg.exec(text)[1]);
            }
            if (node.childNodes && node.childNodes.length) {//遍历解析子节点
                this.goComplier(node);
            }
	}
}

最后就是通过暴露一个全局对象,将这些全部关联起来,也就是vue源码中的index.js,肯定也是要先引入上面的watcher和observer还有complier


function qtVue (options) {
    let self = this;
    this.data = options.data;
    this.methods = options.methods;
    Object.keys(this.data).forEach(function(key) {
        self.proxyEveryKey(key);
    });
    observe(this.data);
    new Compile(options.el, this);
    options.mounted.call(this); //就像vue一样,数据全部准备好以后进入mounted渲染dom
}

qtVue.prototype = {
    proxyEveryKey: function (key) {
        let self = this;
        Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get: function getter () {
                return self.data[key];
            },
            set: function setter (newVal) {
                self.data[key] = newVal;
            }
        });
    }
}

最后的最后,在index.html或者main.js中直接使用就可以啦,当然要引入index.js

new qtVue({
        el: '#app',
        data: {
            title: 'my vue',
            value: 'qt'
        },
        methods: {
            clickFun: function () {
                this.title = 'my click';
            },
            modelFun: function () {
                this.value= 'qtt';
            }
        },
        mounted: function () {
           console.log(this.value);
        },
        created: function () {}
    });

参考链接https://blog.csdn.net/qq_24122593/article/details/53284161

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值