本文能帮你做什么?
1、相识vue的双向数据绑定道理以及中心代码模块
2、减缓好奇心的同时相识怎样完成双向绑定
如今几种主流的mvc(vm)框架都完成了单向数据绑定,而我所明白的双向数据绑定不过就是在单向绑定的基本上给可输入元素(input、textare等)增加了change(input)事宜,来动态修正model和 view,并没有多深邃。所以无需太甚介意是完成的单向或双向绑定。
完成数据绑定的做法有大抵以下几种:
宣布者-定阅者形式(backbone.js)
脏值搜检(angular.js)
数据挟制(vue.js)
宣布者-定阅者形式: 平常经由过程sub, pub的体式格局完成数据和视图的绑定监听,更新数据体式格局一般做法是 vm.set('property', value)
,不太熟悉去问一下度娘
这类体式格局如今毕竟太low了,我们更愿望经由过程 vm.property = value
这类体式格局更新数据,同时自动更新视图,因而有了下面两种体式格局
脏值搜检: angular.js 是经由过程脏值检测的体式格局比对数据是不是有更改,来决议是不是更新视图,最简朴的体式格局就是经由过程 setInterval()
定时轮询检测数据更改,固然Google不会这么low,angular只要在指定的事宜触发时进入脏值检测,大抵以下:
数据挟制: vue.js 则是采纳数据挟制连系宣布者-定阅者形式的体式格局,经由过程Object.defineProperty()
来挟制各个属性的setter
,getter
,在数据更改时宣布音讯给定阅者,触发响应的监听回调。
已相识到vue是经由过程数据挟制的体式格局来做数据绑定的,其中最中心的要领就是经由过程Object.defineProperty()
来完成对属性的挟制,到达监听数据更改的目标,无疑这个要领是本文中最主要、最基本的内容之一,假如不熟悉defineProperty,猛戳这里 整顿了一下,要完成mvvm的双向绑定,就必须要完成以下几点:
不多赘述,一言不合就上图
人人可去下载去详细文件内里看,我写了详实的解释,每一个模块的功用,分工,每一个要领使命,等等
上图为小编我依据本身的明白后从新绘制,本盘算绘制再细一些,觉得会让人明白更庞杂然后就有了上图,代码中假如题目,迎接斧正,一同进修,你们的start是小编的动力
下面为详细代码完成,为了人人轻易我照样粘贴在readme内里,每一个文件不多说了,前面做了案牍及脑图思绪梳理,文件里我也了详实的解释
{{msg}}
/**
* -----------------------------------------------------
* 1、完成数据代办
* 2、模版剖析
* 3、挟制监一切的属性
* -----------------------------------------------------
*/
class MVVM {
/**
*Creates an instance of MVVM.
* @param {*} options 当前实例通报过来的参数
* @memberof MVVM
*/
constructor(options){
this.$opt = options|| {}
this.$data = options.data;
// 完成数据代办
Object.keys(this.$data).forEach((key)=>{
this._proxyData(key)
})
// 挟制监一切的属性
observe(this.$data,this)
// 模版编译
new Compile(options.el || document.body,this)
}
_proxyData(key){
Object.defineProperty(this,key,{
configurable:false,
enumerable:true,
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal
}
})
}
}
/**
* -----------------------------------------------------
* 1、完成一个数据监听器Observer
* 2、关照和增加定阅者
* -----------------------------------------------------
*/
class Observer {
/**
*Creates an instance of Observer.
* @param {*} data 须要挟制监听的数据
* @memberof Observer
*/
constructor(data){
this.$data = data || {}
this.init()
}
init(){
Object.keys(this.$data).forEach(key=>{
this.defineReative(key,this.$data[key])
})
}
defineReative(key,val){
// 建立宣布者-定阅者
let dep = new Dep()
// 再去视察子对象
observe(val)
Object.defineProperty(this.$data,key,{
configurable:false,
enumerable:true,
get(){
// 增加定阅者
Dep.target && dep.addSub(Dep.target)
return val
},
set(newVal){
if( newVal == val ) return false;
val = newVal
// 新的值是object的话,举行监听
observe(newVal)
// 关照定阅者
dep.notfiy()
}
})
}
}
/**
* 是不是举行挟制监听
*
* @param {*} value 监听对象
* @param {*} vm 当前实例
* @returns 返回 监听实例
*/
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
class Dep{
constructor(){
this.subs = []
}
/**
*保护定阅者数组
*
* @param {*} sub 定阅实例
* @memberof Dep
*/
addSub(sub){
this.subs.push(sub)
}
notfiy(){
this.subs.forEach(sub=>{
// 关照数据更新
sub.update()
})
}
}
/**
* -----------------------------------------------------
* 1、取实在dom节点
* 2、我们fragment 建立文档碎片,将真是dmo,挪动指缓存
* 3、编译假造dom,剖析模版语法
* 4、回填至真是dom,完成模版语法剖析,更新试图
* -----------------------------------------------------
*/
class Compile{
/**
*
*Creates an instance of Compile.
* @param {*} el dmo选择器
* @param {*} vm 当前实例
* @memberof Compile
*/
constructor(el,vm){
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el)
if(this.$el){
this.$fragment = this.node2Fragment(this.$el)
this.init()
this.$el.appendChild(this.$fragment)
}
}
init(){
this.compileElement(this.$fragment)
}
/**
*
* 编译element
* @param {*} el dmo节点
* @memberof Compile
*/
compileElement(el){
// 1、取一切子节点
let childNodes = el.childNodes
// 2、轮回子节点
Array.from(childNodes).forEach((node)=>{
// 推断是文本节点照样dom节点
if(this.isElementNode(node)){
this.compileDom(node)
}else if (this.isTextNode(node)){
this.compileText(node)
}
// 推断当前节点是不是有子节点,假若有,递归查找
if(node.childNodes && node.childNodes.length){
this.compileElement(node)
}
})
}
/**
*
* 编译元素节点
* @param {*} node 须要编译的当前节点
* @memberof Compile
*/
compileDom(node){
// 取当前节点的属性鸠合
let attrs = node.attributes
// 轮回属性数组
Array.from(attrs).forEach(attr => {
let attrName = attr.name
// 推断当前属性是不是是指令
if(this.isDirective(attrName)){
let [,dir] = attrName.split("-")
let expr = attr.value
//推断当前属性是一般指令照样事宜指令
if(this.isEventDirective(dir)){
compileUtil.eventHandler(node,expr,dir,this.$vm)
}else{
compileUtil[dir] && compileUtil[dir](node,expr,this.$vm)
}
}
});
}
/**
*
* 编译文本节点
* @param {*} node 须要编译的当前节点
* @memberof Compile
*/
compileText(node){
var text = node.textContent;
var reg = /\{\{(.*)\}\}/;
if(reg.test(text)){
compileUtil.text(node,RegExp.$1,this.$vm)
}
}
/**
* 推断是不是是元素节点
*
* @param {*} el 节点
* @returns 是不是
* @memberof Compile
*/
isElementNode(el){
return el.nodeType == 1
}
/**
* 过滤是不是是指令
*
* @param {*} name 属性名
* @returns 是不是
* @memberof Compile
*/
isDirective(name){
return name.indexOf("v-") == 0
}
/**
* 推断是不是是事宜指令
*
* @param {*} dir 指令,on:click
* @returns 是不是
* @memberof Compile
*/
isEventDirective(dir){
return dir.indexOf("on") == 0
}
/**
* 推断是不是是文本节点
*
* @param {*} el 节点
* @returns 是不是
* @memberof Compile
*/
isTextNode(el){
return el.nodeType == 3
}
/**
* 将实在dom拷贝到内存中
*
* @param {*} el 实在dom
* @returns 文档碎片
* @memberof Compile
*/
node2Fragment(el){
let fragment = document.createDocumentFragment();
let children
while(children = el.firstChild){
fragment.appendChild(el.firstChild)
}
return fragment
}
}
// 指令处置惩罚东西
let compileUtil = {
/**
* 处置惩罚文本节点
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
*/
text(node,expr,vm){
this.buid(node,expr,vm,"text")
},
/**
* 处置惩罚表单元素节点
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
*/
model(node,expr,vm){
this.buid(node,expr,vm,"model")
var me = this,
val = this.getVMVal(vm, expr);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me.setVMVal(vm, expr, newValue);
val = newValue;
});
},
/**
* 事宜处置惩罚
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} dir 指令
* @param {*} vm 当前实例
*/
eventHandler(node,expr,dir,vm){
let [,eventType] = dir.split(":");
let fn = vm.$opt.methods && vm.$opt.methods[expr]
if(eventType && fn){
node.addEventListener(eventType,fn.bind(vm),false)
}
},
/**
* 绑定事宜一致处置惩罚要领抽离,增加watcher
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
* @param {*} dir 指令
*/
buid(node,expr,vm,dir){
let updateFn = update[dir+'Update']
updateFn && updateFn(node,this.getVMVal(vm,expr))
new Watcher(vm, expr, function(value, oldValue) {
updateFn && updateFn(node, value, oldValue);
});
},
/**
* 猎取表达式代表的值
*
* @param {*} vm 当前实例
* @param {*} expr 表达式
* @returns
*/
getVMVal(vm,expr){
// return vm[expr] 要斟酌,a.b.c的状况
let exp = expr.split(".");
let val = vm
exp.forEach((k)=>{
val = val[k]
})
return val
},
/**
* 设置更新数据里对应的表达式的值
*
* @param {*} vm
* @param {*} expr
* @param {*} newValue
*/
setVMVal(vm,expr,newValue){
let exp = expr.split('.');
let val = vm
exp.forEach((key,i)=>{
if(i
}else{
val[key] = newValue
}
})
}
}
// 数据更新
let update = {
textUpdate(node,value){
node.textCOntent= typeof value == 'undefined' ? '' : value
},
modelUpdate(node,value){
node.value = typeof value == 'undefined' ? '' : value
}
}
/**
* -----------------------------------------------------
* 1、完成一个Watcher,作为衔接Observer和Compile的桥梁
* 2、关照和增加定阅者
* -----------------------------------------------------
*/
class Watcher {
/**
*Creates an instance of Watcher.
* @param {*} vm 当前实例
* @param {*} expOrFn 表达式
* @param {*} cb 更新回挪用
* @memberof Watcher
*/
constructor(vm,expOrFn,cb){
this.$vm = vm
this.$expOrFn = expOrFn
this.$cb = cb
this.value = this.get()
}
get(){
// 增加定阅者
Dep.target = this;
// let dep = new Dep()
// 去modal中取值,这个时刻必然会触发defineProperty的getter,真正的push定阅者
let value = this.getVMVal(this.$vm,this.$expOrFn)
// 用完了,重置归去
Dep.target = null
return value
}
/**
* 取modal里的值
*
* @param {*} vm 当前实例
* @param {*} expr 表达式
* @returns 返回指
* @memberof Watcher
*/
getVMVal(vm,expr){
// return vm[expr] 要斟酌,a.b.c的状况
let exp = expr.split(".");
let val = vm
exp.forEach((k)=>{
val = val[k]
})
return val
}
// 对外暴露的跟新要领,比较新老值,获得定阅关照举行更新
update(){
let oldVal = this.value;
let newVal = this.getVMVal(this.$vm,this.$expOrFn)
if (newVal !== oldVal) {
this.value = newVal;
this.$cb(newVal, oldVal);
}
}
}
末了感谢您的浏览