作者:D之phper | 来源:互联网 | 2023-09-15 21:03
媒介mvvm形式即model-view-viewmodel形式简称,单项双向数据绑定的完成,让前端开发者们从冗杂的dom事宜中摆脱出来,很轻易的处置惩罚数据和ui之间的联动。本文将
媒介
mvvm形式即model-view-viewmodel形式简称,单项/双向数据绑定的完成,让前端开发者们从冗杂的dom事宜中摆脱出来,很轻易的处置惩罚数据和ui之间的联动。
本文将从vue的双向数据绑定入手,理会mvvm库设想的中心代码与思绪。
需求整顿与剖析
整顿
数据一旦转变则更新数据对应的ui
ui转变则触发事宜转变ui对应的数据
剖析
经由过程dom节点的指令猎取革新函数,用来革新指定的ui。
完成一个桥接的要领,让革新函数和须要的数据关联起来
监听数据变化,数据转变后经由过程桥接要领挪用革新函数
ui转变触发对应的dom事宜在转变特定的数据
完成思绪
完成observer,从新定义data,为data上每一个属性增添setter,getter以监听数据的变化
完成compile,扫描模版template,提取每一个dom节点中的指令信息
完成directive,经由过程指令信息是实例化对应的directive实例,差别范例的directive具有差别的革新函数update
完成watcher,让observer的属性监听函数与directive的update函数做一一对应,以完成数据变化后更新视图
模块分别
MVVM现在分别为observer,compile,directive,watcher四个模块
数据监听模块observer
经由过程es5范例中的object.defineProperty体式格局完成对数据的监听
完成思绪:
递归遍历data,将data下面一切属性都加上set,get要领,以完成对一切属性的阻拦.
注重:对象能够含有数组属性,数组的内置有push,pop,splice等要领转变内部数据.
此时做法是转变数组的原型链,在原型链中增添一层自定义的push,pop,splice要领做阻拦,这些要领内里加上我们本身的回调函数,然后在挪用原生的push,pop,splice等要领.
详细能够看我上一篇文章js对象监听完成
observer.js代码
export function Observer(obj) {
this.$observe = function(_obj) {
var type = Object.prototype.toString.call(_obj);
if (type == '[object Object]') {
this.$observeObj(_obj);
} else if (type == '[object Array]') {
this.$cloneArray(_obj);
}
};
this.$observeObj = function(obj) {
var t = this;
Object.keys(obj).forEach(function(prop) {
var val = obj[prop];
defineProperty(obj, prop, val);
if (prop != '__observe__') {
t.$observe(val);
}
});
};
this.$clOneArray= function(a_array) {
var ORP = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
var arrayProto = Array.prototype;
var newProto = Object.create(arrayProto);
ORP.forEach(function(prop) {
Object.defineProperty(newProto, prop, {
value: function(newVal) {
var dep = a_array.__observe__;
var re=arrayProto[prop].apply(a_array, arguments);
dep.notify();
return re;
},
enumerable: false,
configurable: true,
writable: true
});
});
a_array.__proto__ = newProto;
};
this.$observe(obj, []);
}
var addObserve = function(val) {
if (!val || typeof val != 'object') {
return;
}
var dep = new Dep();
if (isArray(val)) {
val.__observe__ = dep;
return dep;
}
}
export function defineProperty(obj, prop, val) {
if (prop == '__observe__') {
return;
}
val = val || obj[prop];
var dep = new Dep();
obj.__observe__ = dep;
var childDep = addObserve(val);
Object.defineProperty(obj, prop, {
get: function() {
var target = Dep.target;
if (target) {
dep.addSub(target);
if (childDep) {
childDep.addSub(target);
}
}
return val;
},
set: function(newVal) {
if(newVal!=val){
val = newVal;
dep.notify();
}
}
});
}
编译模块compiler
完成思绪:
1.将模版template上的dom遍历一遍,将其存入文档碎片frag
2.遍历frag,经由过程attributes猎取节点的属性信息,在经由过程正则表达式过滤属性信息,进而拿到元素节点和文档节点的指令信息
var complieTemplate = function (nodes, model) {
if ((nodes.nodeType == 1 || nodes.nodeType == 11) && !isScript(nodes)) {
paserNode(model, nodes);
if (nodes.hasChildNodes()) {
nodes.childNodes.forEach(node=> {
complieTemplate(node, model);
})
}
}
}; var paserNode = function (model, node) {
var attributes = node.attributes || [];
var direct_array = [];
var scope = {
parentNode: node.parentNode,
nextNode: node.nextElementSibling,
el: node,
model: model,
direct_array: direct_array
};
attributes = toArray(attributes);
var textCOntent= node.textContent;
var attrs = [];
var vfor;
attributes.forEach(attr => {
var name = attr.name;
if (isDirective(name)) {
if (name == 'v-for') {
vfor = attr;
} else {
attrs.push(attr);
}
removeAttribute(node, name);
}
});
//bug nodeType=3
var textValue = stringParse(textContent);
if (textValue) {
attrs.push({
name: 'v-text',
value: textValue
});
node.textCOntent= '';
}
if (vfor) {
scope.attrs = attrs;
attrs = [vfor];
}
attrs.forEach(function (attr) {
var name = attr.name;
var val = attr.value;
var directiveType = 'v' + /v-(\w+)/.exec(name)[1];
var Directive = directives[directiveType];
if (Directive) {
direct_array.push(new Directive(val, scope));
}
});
};
var isDirective = function (attr) {
return /v-(\w+)/.test(attr)
};
var isScript = function isScript(el) {
return el.tagName === 'SCRIPT' && (
!el.hasAttribute('type') ||
el.getAttribute('type') === 'text/Javascript'
)
}
指令模块directive
指令信息如:v-text,v-for,v-model等。
每种指令信息须要的初始化行动以及指令的革新函数update都能够不一样,所以我们把它笼统出来零丁做一个模块。固然也有公用的如大众属性,一致的watcher实例化,unbind.
update函数则详细定义所属指令怎样衬着ui
如简朴的vtext指令的update函数以下:
vt.update = function (textContent) {
this.el.textCOntent= textContent;
};
结构图
数据定阅模块watcher
watcher的功用是让directive和observer模块关联起来。
初始化的时刻做两件事:
将directive模块的update函数当参数传入,并将其存入本身update属性中
挪用getValue,从而猎取对象data的特定属性值,进而触发一次之前在observer定义的属性函数的getter要领。
因为在defineProperty函数中定义的dep变量在setter和getter函数里有援用,使dep变量处于闭包状况没有开释,此时在getter要领中经由过程推断Depend.target的存在,来猎取定阅者watcher,经由过程发布者dep储存起来。
数据的每一个属性都有一个唯一的的dep变量,记录著一切定阅者watcher的信息,一旦属性有变化,挪用setter函数的时刻触发dep.notify(),关照一切已定阅的watcher,进而实行一切与该属性关联的革新函数,末了更新指定的ui。
watcher 初始化部份代码:
Depend.target = this;
this.value = this.getValue();
Depend.target = null;
observer.js 属性定义代码:
export function defineProperty(obj, prop, val) {
if (prop == '__observe__') {
return;
}
val = val || obj[prop];
var dep = new Dep();
obj.__observe__ = dep;
var childDep = addObserve(val);
Object.defineProperty(obj, prop, {
get: function() {
var target = Dep.target;
if (target) {
dep.addSub(target);
if (childDep) {
childDep.addSub(target);
}
}
return val;
},
set: function(newVal) {
if(newVal!=val){
val = newVal;
dep.notify();
}
}
});
}
流程图
简朴的流程图以下:
效果图
代码地点总结
本文基础对mvvm库的需求整顿,拆分,以及对拆分模块的一一完成来到达团体双向绑定功用的完成,固然现在市场上的mvvm库功用绝不止于此,本文只是略举个人认为的中心代码。
假如思绪和完成上的题目,也请列位指正,感谢浏览!