搭建一个小型vue,此处不用闭包,直观一点
测试例子
<div id="app">
<h2 v-text='hello' v-show='1'></h2>
<input type="text" v-model='counter'>
<button v-on-click='add' type="button">add</button>
<h2 v-if=isRender>hellow world</h2>
<p v-text='counter'></p>
</div>
<script src='./mymvvm.js'></script>
<script>
var vm = new MVVM({//自定义的一个名字
el: 'app',//在id为app里面的所有元素
data: {
counter: 1,
hello: 'ahahah!',
isShow: true,
isRender: false
},
methods: {
add: function () {
vm.counter += 1;
}
},
// 钩子,初步渲染后就触发
ready () {
let self = this;
self.hello = 'Ready, go!';
setTimeout(function () {
self.hello = 'Done!';
self.isRender =true
}, 3000)
}
})
</script>
在mymvvm内
- 首先创建构造器MVVM
- 定义指令对象的结构
class Directive { // 此使用typescript定义对象来说明
el:Element //目标元素
attr:{//元素属性
name:string,
value:string //绑定的对象名称
},
key:string, //绑定的对象名称
dirname: string, //指令的名称
definition: object, //指令的定义
argument: string // 指令的参数
}
- 定义指令的前缀
prefix
; - 定义支持的指令名及其方法,创建对象
Directives
- 创建方法
getDirSelectors
由自定义的指令名称组成dom的选择器 - 由选择器获得所有需要绑定的dom元素
- 对以上dom元素及根元素通过
processNode
方法绑定指令方法,parseDireactive
获得指令对象 - 对指令与元素进行绑定的具体方法
bindDirective
,使用中转桥梁bindings
- 定义钩子函数(
defineProperty
),双向数据绑定bindAccessors
,根据bindings
去及时更新视图 -
MVVM
对象继承opts.data
和opts.methods
的属性(简单合并实现,会有重名覆盖问题),就会触发钩子函数并更新视图 - 触发生命周期
ready
函数
具体代码
var prefix ='v';
var Directives = {
/**
* 更改目标元素的文本内容
* @param {Element} el 目标元素
* @param {string} value 新的文本内容
*/
text: function(el,value) {
el.textContent = value || '';
},
/**
* 更改目标dom元素是否隐藏
* @param {Element} el 目标元素
* @param {object} value 被用来作判定的值
*/
show: function(el,value) {
el.style.display = value? '':'none';
},
/**
* 更改目标dom元素是否插入到dom树里
* @param {Element} el 目标元素
* @param {any} value 新的数值
* @param {string} dirAgr 指令的参数,如v-on-click,click就是参数
* @param {object} dir 对应指令的对象
* @param {object} vm mvvm new出来的对象
*/
if: function(el,value,dirAgr,dir,vm) {
var idx = el.idx;
if(!value) {
el.remove();
} else {
var els = vm.$els;
var length = els.length;
var root = vm.$el;
while(idx<length) {
// 获得后一个元素
var nextEl = els[idx+1];
// 到最后一个直接append,终止while
if(idx === length-1) {
root.append(el);
break;
} else {
// 判定后一个dom是否也被remove掉了,没有则在它前面插入el,终止while
if(nextEl.parentNode === root) {
root.insertBefore(el, nextEl);
break;
}
}
idx++;
}
}
},
/**
* 双向数据绑定
* @param {Element} el 目标元素
* @param {any} value 新的数值
* @param {string} dirAgr 指令的参数,如v-on-click,click就是参数
* @param {object} dir 对应指令的对象
* @param {object} vm mvvm new出来的对象
* @param {string} key 监听的变量名
*/
model: function(el,value,dirAgr,dir,vm,key) {
var eventName = 'keyup';
el.value = value || '';
// 绑定方法时把方法与元素变量建立连接,方便后期remove
if(el.handlers && el.handlers[eventName]) {
el.removeEventListener(eventName,el.handlers[eventName]);
} else {
el.handlers = {};
}
el.handlers[eventName] = function(e) {
vm[key] = e.target.value;
};
el.addEventListener(eventName,el.handlers[eventName]);
},
on: {
/**
* 绑定方法
* @param {Element} el 目标元素
* @param {function} handler 绑定的方法
* @param {string} eventName 事件名称(指令on的参数)
* @param {Direactive} directive 指令对象
*/
update: function(el,handler,eventName,directive) {
if(!directive.handlers){
directive.handlers = {};
}
var handlers = directive.handlers;
//把之前的取消
if(handlers[eventName]) {
el.removeEventListener(eventName,handlers[eventName]);
}
if(handlers) {
handler = handler.bind(el);
el.addEventListener(eventName,handler);
handlers[eventName] = handler;
}
}
}
};
function MVVM(opts) {
if(!opts.el) throw new Error('未提供目标dom的id');
var self = this;
var root = this.$el = document.getElementById(opts.el);
var _data = this.$data = Object.assign(opts.data,opts.methods);
// 获取有有效的指令的元素,getDirSelectors:返回所有的选择器
var els = this.$els = root.querySelectorAll(getDirSelectors(Directives));
var bindings = {}; // 指令与data关联的桥梁,记录绑定属性共绑定了多少个指令
this._bindings = bindings;
[].forEach.call(els,processNode);
processNode(root);
self = Object.assign(self,_data);
// 触发生命周期ready
if(opts.ready && typeof opts.ready == 'function') {
this.ready = opts.ready;
this.ready();
}
function processNode (el,idx) {
// 记录所在位置,方便重新插入(if指令)
el.idx = idx;
// 对每个指令分别操作
getAttributes(el.attributes).forEach(function(attr) {
var directive = parseDireactive(attr,el);//获得指令对象
if(directive) {
bindDirective(self,el,bindings,directive);
}
});
}
}
/**
* 对vm的属性绑定指令
* @param {mvvm} vm mvvm对象
* @param {Element} el 目标对象
* @param {object} bindings 绑定的桥梁对象
* @param {Direactive} directive 指令对象
*/
function bindDirective(vm,el,bindings,directive) {
el.removeAttribute(directive.attr.name);
var key = directive.key;
// 筛选属性,对于非show 或者 if 的指令,绑定的是没有提供的属性则报错,否则进行判断;
if(!vm.$data.hasOwnProperty(key)) {
var booleanDirective =['show','if'];
if((booleanDirective.indexOf( directive.dirname))>-1) {
// 把key当表达式javascript运行使用,并覆盖key;
try {
eval('key='+key);
} catch(e) {
console.log(e);
}
// 更新视图
directive.update(directive.el,key);
// 返回,不对此属性进行双向绑定
return;
} else {
throw new Error(key+'未定义');
}
}
var binding = bindings[key];
if(!binding) {
bindings[key]=binding={
value:'',
directives:[]
};
}
// 对属性增加指令对象
binding.directives.push(directive);
if(!vm.hasOwnProperty(key)) {// 判定属性是否存在这个对象
// binding 与 bindings 相等,所以bindings改变,set里的binding页改变
bindAccessors(vm,key,binding);//双向绑定,定义钩子函数
}
}
/**
* 在defineProperty插入钩子函数,根据指令更新视图
* @param {Element} el 目标dom对象
* @param {string} key 需要观察的属性名称
* @param {object} bindings 绑定的桥梁对象
*/
function bindAccessors(vm,key,binding) {
Object.defineProperty(vm,key,{
get:function(){
return binding.value;
},
set:function(nValue) {
binding.value = nValue;
binding.directives.forEach(function(directive) {
directive.update(directive.el,nValue,directive.argument,directive,vm,key);
});
}
});
}
/**
* 由dom元素属性生成Directive对象
* @param {object} attr dom元素属性
* @param {Element} el 目标dom对象
* @return {Directive} 自定义对象
*/
function parseDireactive(attr,el) {
// 前缀不对返回
if(attr.name.indexOf(prefix) === -1) {
return;
}
var directiveStr = attr.name.slice(prefix.length+1);
argIndex = directiveStr.indexOf('-');
directiveName = argIndex === -1? directiveStr: directiveStr.slice(0,argIndex);
directiveDef = Directives[directiveName];//指令的定义
// 获得on的参数
arg = argIndex === -1? null : directiveStr.slice(argIndex+1);
key = attr.value;
return directiveDef? {
attr:attr,key:key,el:el,dirname:directiveName,definition:directiveDef,argument:arg,
update:typeof directiveDef == 'function'? directiveDef:directiveDef.update
}: null;
}
function getAttributes(attributes) {
return [].map.call(attributes, function(attr) {
return {
name:attr.name,
value:attr.value
};
});
}
function getDirSelectors(directives) {
var eventArr = ['click','change','blur']; // 定义指令on支持的参数
return Object.keys(directives).map(function(directive) {
return '[' + prefix + '-' + directive +']';
}).join() + ',' + eventArr.map(function(eventName) {
return '[' + prefix + "-on-" + eventName + ']';
});
}
Done!
如果觉得文章对你有点用的话,麻烦拿出手机,这里有一个你我都有的小福利(每天一次): 打开支付宝首页搜索“8601304”,即可领红包。谢谢支持