nodom2.js中出现的所有类
- Class
- Complier
- Directive
- DirectiveManager
- DirectiveFactory
- Element
- Error
- Event
- Expression
- ExpressionFacory
- ExternalEvent
- Factory
- Filter
- FilterManager
- Linker
- MessageFactory
- MethodFactory
- Model
- ModelFactory
- Module
- ModuleFactory
- nodom
- Renderer
- Route
- Router
- RouterTree
- Scheduler
---------------------------各个类的功能------------------------------
Class:模拟java的反射
补充知识点:java的反射知识:java中类与对象,在运行状态中,对于任意的一个类,都能知道其属性与方法,对于任意的对象,都能够调用它的任意方法和属性
Class.items = new Map();
静态方法,添加类名与类,
/**
* 添加class
* @param clsName 类名
* @param cls class
*/
static add(classsName,classt){
this.items.set(classsName,classs);
}
上面add函数的用处
Class.add('Directive',Directive);
Class.add('Filter',Filter);
Class.add('Expression',Expression);
Class.add('Element',Element);
Class.add('Module',Module);
Class.add('Model',Model);
Class.add('FilterFactory',FilterFactory);
Class.add('DirectiveFactory',DirectiveFactory);
Class.add('ExpressionFactory',ExpressionFactory);
Class.add('Route',Route);
Class.items存储了10个类,
/**
* 通过类名获取class
* @param clsName 类名
* @return 类
*/
static getClass(clsName){
return this.items.get(clsName);
}
可以看见上面函数,通过类名,获取对应的类,也是一个静态的方法
/**
* 创建实例(对象)
* @param clsName 类名
* @param
* @return 根据类创建的实例
*/
static newInstance(clsName,config){
let cls = this.items.get(clsName);
if (cls === undefined) { }
//Reflect.construct() 方法的行为有点像 new 操作符 构造函数 ,
//相当于运行 new target(...args)
return Reflect.construct(cls);
}
参数config似乎没有用,不过很有可能是用于创建不同类的实例时,初始化需要的参数
以上代码中出现了Reflect.construct(cls),查MDN知,Reflect.construct(className:String,args:Array)
我们知道func.apply({},args:Array)与func.call(this,..args)是函数绑定this,并写参。Reflect.construct()有可能是调用封装apply对construct函数的调用。其最后返回的是一个对象,等同与new className(...args)
new 是call 对constrcut 的封装。,Refletc.construct是apply对constrcut的封装
Complier:编译器,负责模板的编译
/**
* 编译
* @param view 指定的view
* @param module 模块
* @return view
*/
static compile(view){
return compileEl(view);
}
上面这个函数,好像没有用,因为下面有同样的函数给覆盖了,js中不存在,java/c++那样函数名相同,参数不同,实际用的哪一个函数由编译器去判断这样的便捷方法
/**
* 编译
* @param elementStr 待编译element
* @param module 模块
* @return 虚拟element
*/
static compile(module,elementStr){
let dom = new Element();//元素实例
const div = nodom.newEl('div');
div.innerHTML = elementStr;
dom.isRoot = true;
//调用编译
this.compileDom(module,div,dom);
return dom;
}
看看,这段代码的大意是:用一个div包裹要编译的elementStr,创建一个Element的实例,最后调一个函数去
this.compileDom(module,div,dom);
static compileDom(module,ele,parent){
...
}
上面的函数比较长就不放出来了,这是一个编译Dom的函数
在函数中创建 Element的实例,将ele的属性,子节点,文本一个一个的,判断,取值,扩展,生成Element实例。
static compileExpression(module,exprStr){
....
}
编译字符串如“{{name}}",首先使用正则得到一个迭代器,遍历。注意在遍历里面存在对在不在{{}}的字符串进行的不同的处理,在{{}}中的字符串作为表达式,之外的以此分割,压入数组。
<statement>:="<String>"
<String>:=<String>|{{<expression>}}|
Directive 指令类
/**
* 构造方法
* @param type 类型
* @param value 指令值
* @param vdom 虚拟dom
* @param module 模块
*/
constructor(type,value,vdom,module){
this.className = 'Directive';
this.type = type;
if(value !== undefined){
this.value = value.trim();
}
if(type !== undefined){
nodom.apply(DirectiveManager.init,DirectiveManager,[this,vdom,module]);
}
this.id = nodom.genId();
}
构造函数,首先该对象的类名,与指令的类型,存在类型就调用,指令管理类的静态函数init,参数,改指令实例,指令管理类,虚拟dom,模块。
nodom.apply是对Reflect.apply(foo,obj,args);
的封装,类似funcName.apply(obj,args:Array),不过是直接使用一个Reflect.apply函数,将需要调用的函数,函数需要的参数,作为其参,使用
/**
* 执行
* @param value
* @return
*/
exec(value){
let args = [this.module,this.type,value].concat(this.params);
return nodom.apply(DirectiveManager.exec,DirectiveManager,args);
}
[this.module,this.type,value].concat(this.params); concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
类似上一个函数,,在参数与调用的函数上有些不同
DirectiveManager 指令管理类
/**
* 创建指令类型
* @param name 指令类型名
* @param config 配置对象{order:优先级,init:初始化函数,handler:渲染处理函数}
*/
static addType(name, config) {
//重复,便抛出错误
if(this.directiveTypes.has(name)){
throw Error.handle('exist1',nodom.words.directiveType,name);
}
if(!nodom.isObject(config)){
throw Error.handle('invoke','DirectiveManager.addType',1,'Function');
}
//默认优先级10
//没有初始化,就默认为10
config.prio = config.prio || 10;
//
this.directiveTypes.set(name,config);
}
一个指令名,对应一个配置对象config,config对象中,有,优先级,初始化函数,渲染处理函数。优先级默认为10,指令名+配置对象config使用map对象的实例存储。
/**
* 移除过滤器类型
* @param name 过滤器类型名
*/
static removeType(name) {
//can not edit types
if(this.cantEditTypes.indexOf(name) !== -1){//是不可编辑对象
throw Error.handle('notupd',nodom.words.system + nodom.words.directiveType,name);
}
if(!this.directiveTypes.has(name)){//不存在改指令
throw Error.handle('notexist1',nodom.words.directiveType,name);
}
delete this.directiveTypes.delete(name);//移除
}
删操作
/**
* 获取类型
*/
static getType(name){
return this.directiveTypes.get(name);
}
查操作
/**
* 是否有某个指令类型
* @param type 指令类型名
* @return true/false
*/
static hasType(name){
return this.directiveTypes.has(name);
}
判断
/**
* 指令初始化
* @param diective 指令
* @param dom 虚拟dom
* @param module 模块
* @return
*/
static init(directive,dom,module){
let dt = this.directiveTypes.get(directive.type);
if(dt === undefined){
throw Error.handle('notexist1',nodom.words.directiveType,name);
}
return dt.init(directive,dom,module);//队列,父出子继
}
指令初始化,是静态函数,在Map中查询到入参的指令,返回config对象,后调用config.init函数,去初始化指令。
/**
* 执行指令
* @param arguments 参数数组 0模块 1指令类型 2待处理值 3-n处理参数
*/
//execute执行
static exec(directive,ele,parent){
if(!this.directiveTypes.has(directive.type)){//不存在
throw Error.handle('notexist1',nodom.words.directiveType,type);
}
//调用
return nodom.apply(this.directiveTypes.get(directive.type).handle,null,arguments);
}
执行函数,与上一个函数的思想相同,不过这里有个特殊的对象,arguments对象那个,这个对象是参数对象,具有以参数为数组元素的类数组,同时有一个代替本身函数的函数。arguments.callee
DirectiveManager.directiveTypes = new Map();//Map对像
//不可编辑类型
DirectiveManager.cantEditTypes = ['model','repeat','if','else','show','class','field'];
一个是指令的存储结构,一个是禁止修改项(框架保留字)
Element类 虚拟dom element
constructor(){
const me = this;
me.className = 'Element';
me.directives = [];
me.props = {}; //属性集合
me.events = {}; //事件集合
me.exprProps = {}; //含表达式的属性集合
me.changeProps = []; //修改后的属性
me.removeProps = []; //待删除属性
me.children = []; //子element
me.parentKey = undefined; //父对象key
me.tagName = undefined; //元素名
me.dontRender = false; //不渲染标志,不渲染到html
me.key = nodom.genId();
}
赋予创建的dom元素,指令,属性,事件,表达式属性集合,修改后的属性,待删除的属性,子元素,父级的key,元素名,不渲染的标志,元素的key
render(module,parent){
const me = this;
// 设置父对象
if (parent) {
//模块的父key,这个element实例的父的key值
me.parentKey = parent.key;
// 设置modelId
if(!me.modelId){
me.modelId = parent.modelId;
}
}
//根据元素名便能判断?
if(me.tagName !== undefined){ //element//元素名存在
me.handleProps(module);//元素的属性处理,参数是模块
//某些指令可能会终止渲染,如果返回false,则不继续渲染
me.handleDirectives(module,parent);
}else{ //textContent
me.handleTextContent(module);
}
//dontrender 为false才渲染子节点
/*
下面if语句的作用:
当前元素需要渲染,便执行
当前元素的子元素,同样要被渲染
但是子元素有些不希望被渲染,就从当前元素移除子元素
这里可以改改
*/
if(!me.dontRender){
//子节点渲染
for(let i=0;i<me.children.length;i++){
let item = me.children[i];
item.render(module,me);
//dontRender 删除
if(item.dontRender){
me.removeChild(item);
i--;
}
}
}
return true;
}
元素的渲染,核心在根据tagName是否有意义,进行不同的处理,对子元素的渲染处理
/**
* 渲染到html element
* @param module 模块
* @param el 对应的element
* @param type 类型
* @param parent 父虚拟dom
*/
renderToHtml(module,params){
............
}
模块是渲染的位置的抽象化,me是虚拟dom,在该函数中通过,模块定位el,使用newEl或newText生成dom节点。其中有三个重要函数
function newEl(vdom,parent,parentEl){
//创建element
let el = document.createElement(vdom.tagName);
//设置属性
nodom.getOwnProps(vdom.props).forEach((p)=>{
el.setAttribute(p,vdom.props[p]);
});
el.setAttribute('key', vdom.key);
//以上生成了dom元素
vdom.handleEvents(module,el,parent,parentEl);
return el;
}
/**
* 新建文本节点
* el1 = newText(me.textContent,me);
*/
function newText(text,dom){
if('html' === dom.type){ //html fragment 或 element
let div = nodom.newEl('div');
div.setAttribute('key',dom.key);
div.appendChild(text);
return div;
}else{
return document.createTextNode(text);
}
}
/**
* 生成子节点
* @param pEl 父节点
* @param vNode 虚拟dom父节点
*/
function genSub(pEl,vNode){
if(vNode.children && vNode.children.length>0){
vNode.children.forEach((item)=>{
let el1;
if(item.tagName){
el1 = newEl(item,vNode,pEl);
genSub(el1,item);
}else{
el1 = newText(item.textContent,item);
}
pEl.appendChild(el1);
});
}
}
以vdom,为模板,使用存储的prop与tagName生成对应的html标签对象,没有tagName便使用一个div包裹虚拟dom的textcontent内容
clone(module){
const me = this;
let dst = new Element();
//简单属性
nodom.getOwnProps(me).forEach((p)=>{
if(typeof me[p] !== 'object'){
dst[p] = me[p];
}
});
me.directives.forEach((d)=>{
dst.directives.push(d);
});
//普通
nodom.getOwnProps(me.props).forEach((d)=>{
dst.props[d]=me.props[d];
});
//表达式属性
nodom.getOwnProps(me.exprProps).forEach((d)=>{
dst.exprProps[d]=me.exprProps[d];
});
//事件
nodom.getOwnProps(me.events).forEach((d)=>{
dst.events[d]=me.events[d].clone();
});
//表达式
dst.expressions = me.expressions;
me.children.forEach((d)=>{
dst.children.push(d.clone(module));
});
return dst;
}
克隆从普通属性,表达式属性,事件,表达式,都遍历一遍后赋值,这是一个深拷贝
handleDirectives(module,parent){
const me = this;
if(me.dontRender){//没有被渲染,代表不会处理指令
return false;
}
let dirs = me.directives;
for(let i=0;i<dirs.length && !me.dontRender;i++){
DirectiveManager.exec(dirs[i],me,module,parent);//执行指令
}
return true;
}
使用指令管理类处理指令
/**
* 表达式预处理,添加到expression计算队列
*
*/
handleExpression(exprArr,module){
const me = this;
if(me.dontRender){
return;
}
let value = '';
let model = module.modelFactory.get(me.modelId);
exprArr.forEach((v)=>{
if(typeof v === 'number'){ //处理表达式
// 统一添加到表达式计算队列
let v1 = module.expressionFactory.get(v).val(model);
//html或 fragment
if(v1 instanceof DocumentFragment || nodom.isEl(v1)){
// 设置类型
me.type = 'html';
return v1;
}
value += v1;
}else{
value += v;
}
});
return value;
}
似乎是,将字符,使用model数据替换
/**
* 处理属性(带表达式)
* me.handleProps(module);
*/
handleProps(module){
const me = this;
if(me.dontRender){
return;
}
nodom.getOwnProps(me.exprProps).forEach((item)=>{
//属性值为数组,则为表达式
if(nodom.isArray(me.exprProps[item])){
me.props[item] = me.handleExpression(me.exprProps[item],module);
}else if(me.exprProps[item] instanceof Expression){ //单个表达式
me.props[item] = me.exprProps[item].val(module.modelFactory.get(me.modelId));
}
});
}
将表达式中的元素处理后,加入props中成为普通属性
/**
* 处理文本(表达式)
* me.handleTextContent(module);
*/
handleTextContent(module){
const me = this;
if(me.dontRender){
return;
}//这里的expressions属性,在定义的时候并不存在
//在complieDom时,会赋值给expression
if(me.expressions !== undefined){
me.textContent = me.handleExpression(me.expressions,module);
}
}
处理文本中的表达式
/**
* 处理事件
* @param module
* @param model
* @param el
* @param parent
* vdom.handleEvents(module,el,parent,parentEl);
*/
handleEvents(module,el,parent,parentEl){
const me = this;
if(me.events.length === 0){
return;
}
nodom.getOwnProps(me.events).forEach((en)=>{
let ev = me.events[en];
if(ev.delg && parent){ //代理到父对象
ev.delegateTo(module,me,el,parent,parentEl);
}else{
ev.bind(module,me,el);
}
});
}
存在代理情况,便将事件代理给父级,否则调用Event对象的绑定方法,有两种方法,第一种是扩展事件中的触摸事件,在扩展事件中注册,第二种是,在根据虚拟dom创建的dom上添加事件监听器
/**
* 移除指令
* @param directives 待删除的指令集
*/
removeDirectives(delDirectives){
const me = this;
for(let i=me.directives.length-1;i>=0;i--){
let d = me.directives[i];
for(let j=0;j<delDirectives.length;j++){
if(d.type===delDirectives[j]){
me.directives.splice(i,1);
}
}
}
}
/**
* 是否有某个类型的指令
*
* @param directiveType 指令类型名
* @return true/false
*/
hasDirective(directiveType){
const me = this;
for(let i=0;i<me.directives.length;i++){
if(me.directives[i].type === directiveType){
return true;
}
}
return false;
}
/**
* 获取某个类型的指令
* @param directiveType 指令类型名
* @return directive
*/
getDirective(directiveType){
const me = this;
for(let i=0;i<me.directives.length;i++){
if(me.directives[i].type === directiveType){
return me.directives[i];
}
}
}
删,查,操作
/**
* 从虚拟dom树和html dom树删除自己
* @param module 模块
* @param html 删除html中的
*/
remove(module,html){
const me = this;
// 从父树中移除
if(me.parentKey !== undefined){
let p = module.renderTree.query(me.parentKey);
if(p){
p.removeChild(me);
}
}
// 删除html dom节点
if(html && module && module.container){
let el = module.container.querySelector("[key='"+ me.key +"']");
if(el !== null){
nodom.remove(el);
}
}
me.free();
}
删除一个Element,要从虚拟dom删除,也要从html文档中删除,最后,回收本对象分配的内存
/**
* 从html删除
*/
removeFromHtml(module){
const me = this;
let el = module.container.querySelector("[key='"+ me.key +"']");
if(el !== null){
nodom.remove(el);
}
}
是上面的一部分,抽出来做为一个函数存在
/**
* 移除子节点
*/
removeChild(dom){
const me = this;
let ind = -1;
// 移除
if(nodom.isArray(me.children) && (ind = me.children.indexOf(dom)) !== -1) {
me.children.splice(ind,1);
}
}
删除子节点,主要是splice函数的使用
/**
* 替换目标节点
* @param dst 目标节点
*/
replace(dst){
const me = this;
if(!dst.parent){
return false;
}
let ind = dst.parent.children.indexOf(dst);
if(ind === -1){
return false;
}
//替换
dst.parent.children.splice(ind,1,me);
return true;
}
替换的思路是,找到目标节点的父级节点,父级节点,删去目标节点,然后加上本节点
/**
* 是否包含节点
* @param dom 包含的节点
*/
contains(dom){
const me = this;
/*
从节点,沿父级向上查询
me包含dom则dom不会为undefined
如果不包含dom会被赋值为undefined
因为,根结点没有父级
*/
for(;dom!==undefined && dom!==me;dom=dom.parent);
return dom !== undefined;
}
这个函数的思路有点不一样,一般是从本节查找孩子节点,但是本节点不同是从查找的孩子节点,顺着父级查找,这样的好处是,不会访问其他兄弟节点。查找速度会更快。
/**
* 查找子孙节点
* @param key element key
* @return vdom element/undefined
* dom1.compare(dom2,retArr,me);
*/
query(key){
const me = this;
if(me.key === key){
return me;
}
for(let i=0;i<me.children.length;i++){
let dom = me.children[i].query(key);
if(dom){
return dom;
}
}
}
查找子孙节点递归法深度优先搜素
compare(dst,retArr,parentNode){
........
}
对当前节点与子节点有着这样的处理,当前节点,比较类型,类型相同,分为文本与元素,文本比较textcontent,元素比较属性,属性有两种,改变的与不存在的(删除的)在,改变与删除的操作不再此函数中,此函数是一个相当于回收站,存储需要的改变的元素属性,同时遍历子节点,比较key值,比较属性,最后都会归于一个回收站对象。
Error
/**
* 按照消息编号进行处理并返回消息内容
* @param 异常名
* @param args1,args2,args3,... 待替换的参数
* @return 转换后的消息
*/
static handle(errname){//{}中的字符
var reg = new RegExp(/\{.+?\}/);
var msg = nodom.ErrorMsgs[errname];
if(msg === undefined){
return "未知错误";
}
var args = [msg];
for(var i=1;i<arguments.length;i++){
args.push(arguments[i]);
}
//处理字符串
return nodom.compileStr.apply(null,args);
}
可以看见,Error本身没对错误进行处理,只是使用nodom中的函数,Error只是提供一个使用的时机
Event
/**
* @param eventName 事件名
* @param eventStr 事件串(可空) 方法名[:delg(代理到父对象):nopopo(禁止冒泡):once(只执行一次):useCapture]
*/
constructor(eventName,eventStr){
const me = this;
me.events = undefined; //子事件,存储代理事件集合,结构为{'click':[ev1,ev2],'swipe':[],...}
me.name = eventName;
//如果事件串不为空,则不需要处理
if(eventStr){
eventStr.split(':').forEach((item,i)=>{
item = item.trim();
if(i===0){ //事件方法
me.handler = item;
}else{ //事件附加参数
me[item] = true;
}
});
}
//触屏事件根据设备类型进行处理
if(nodom.config.deviceType === 1){
switch(me.name){
case 'click':
me.name = 'tap';
break;
case 'mousedown':
me.name = 'touchstart';
break;
case 'mouseup':
me.name = 'touchend';
break;
case 'mousemove':
me.name = 'touchmove';
break;
}
}
}
构造event,提取事件字符串中的关键字如:delg,使用switch语句连接PC端与手机端的鼠标事件(触摸)。
/ *
*@param e 事件
*/
fire(e){
.....
}
触发事件,根据本类的capture选择不同的处理事件的顺序,
/**
* 绑定事件
* @param el element
* @param vdom view模型
* @param module 模块
* ev.bind(module,me,el);
*/
bind(module,vdom,el){
const me = this;
me.virtualDom = vdom;
me.el = el;
me.module = module;
//触屏事件
if(ExternalEvent.TouchEvents[me.name]){
ExternalEvent.regist(me);//注册当前事件
}else{//处理事件函数是fire函数的封装
me.handleEvent = function(e){
me.fire(e);
}
el.addEventListener(me.name,me.handleEvent,me.capture);
}
}
事件绑定模块,虚拟dom,element.,根据扩展事件的有无,注册/监听事件
/**
*
* 事件代理到父对象
* @param parent 父虚拟dom
* @param el 事件作用的html element
* @param vdom view模型
* @param parentEl 父对象
* @param module 模块
*/
delegateTo(module,vdom,el,parent,parentEl){
const me = this;
me.virtualDom = vdom;
me.el = el;
me.module = module;
//如果不存在父对象,则用body
if(!parentEl){
parentEl = document.body;
}
//父节点如果没有这个事件,则新建,否则直接指向父节点相应事件
if(!parent.events[me.name]){
let ev = new Event(me.name);
ev.bind(module,parent,parentEl);
parent.events[me.name] = ev;
}
parent.events[me.name].addSubEvt(me);
}
代理给父级,存入父虚拟dom的事件数组
/**
* 添加子事件
* @param ev 事件
*/
addSubEvt(ev){
const me = this;
if(!me.events){
me.events = Object.create(null);
}
//事件类型对应的数组
if(!me.events[me.name]){
me.events[me.name] = new Array();
}
me.events[me.name].push(ev);
}
removeSubEvt(ev){
const me = this;
if(me.events === undefined || me.events[ev.name] === undefined){
return;
}
let ind = me.events[ev.name].indexOf(ev);
if(ind !== -1){
me.events[ev.name].splice(ind,1);
if(me.events[ev.name].length === 0){
me.events.delete(ev.name);
}
}
}
clone(){
const me = this;
let evt = new Event(me.name);
let arr = ['delg','once','nopopo','useCapture','handler','handleEvent','module'];
arr.forEach((item)=>{
evt[item] = me[item];
});
return evt;
}
let arr = ['delg','once','nopopo','useCapture','handler','handleEvent','module'];一个event对象包含的所用属性的名称
Expression
/**
* @param exprStr 表达式串
*/
constructor(exprStr,module){
const me = this;
//旧值
me.fields = []; // 字段数组
me.modelMap = {}; //一个expression可能被多次使用,以modelid进行区分,针对不同的模型id构建对象{modelId:{fieldValue:,value:}
me.id = nodom.genId();
if(module){
me.moduleName = module.name;
module.expressionFactory.add(me.id,me);
}
if (exprStr) {
//堆
me.stack = this.init(exprStr);
}
}
表达式与存储表达式的结构,表达是中的栈
init(exprStr)(
....
}
从字符串中,提取变量,函数
/**
* 表达式计算
* @param model 模型 或 fieldObj对象
* @param modelId 模型id(model为fieldObj时不能为空)
*/
val(model,modelId){
const me = this;
if(!model){return '';}
if(me.stack === null){
return '';
}
let fieldObj;
// 模型
if(model instanceof Model){
modelId = model.id;
fieldObj = Object.create(null);
//字段值
me.fields.forEach((field)=>{
fieldObj[field] = me.getFieldValue(model,field);
});
}else{
fieldObj = model;
}
let newFieldValue = '';
me.fields.forEach((field)=>{
newFieldValue += fieldObj[field];
});
//如果对应模型的值对象不存在,需要新建
if(me.modelMap[modelId]===undefined){
me.modelMap[modelId] = Object.create(null);
}
//field值不一样,需要重新计算
if(me.modelMap[modelId].fieldValue !== newFieldValue){
me.modelMap[modelId].value = me.cacStack(me.stack,fieldObj,modelId);
}
me.modelMap[modelId].fieldValue = newFieldValue;
return me.modelMap[modelId].value;
}
....
/**
* 添加变量
*/
addVar(field,stack){
const me = this;
let values = ['null','undefined','true','false','NaN'];
//判断是否为值表达式 null undefined true false
let addFlag = values.indexOf(field) === -1 ? false:true;
addFlag = addFlag || nodom.isNumberString(field);
//作为字符串处理
if(addFlag){
this.addStr(field,stack);
}else{
stack.push({
val:field.trim(),
type:'field'
});
me.addField(field);
}
}
变量的有无定义,分出两种处理,添加字符串还是入栈
- addFilter(filterArr,stack)
- cacStack(stack,fieldObj,modelId)
以后看