仿VUE写个框架
仅仅是我个人的Vue学习笔记,用来记录我Vue的学习成果,只是个仿写,功能肯定不全
代码
Html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="./js/my/Compile.js"></script>
<script type="text/javascript" src="./js/my/Sub.js"></script>
<script type="text/javascript" src="./js/my/Pub.js"></script>
<script type="text/javascript" src="./js/my/Observer.js"></script>
<script type="text/javascript" src="./js/my/Lei.js"></script>
<style>
.cssTest{
color:red
}
.cssFont{
font-size: 100px;
}
.cssBlue{
color: blue;
}
</style>
<body>
<div id="app" class="aa bb cc">
<button v-on:click="clickTest">测试</button>
<p v-text="text"></p>
<p v-html="html"></p>
<p v-class="myClass">类测试</p>
<p>
姓名{{name}}性别{{user.sex}}
</p>
<p>
姓名{{user.name}}
</p>
</div>
</body>
<script>
var lei = new Lei({
el:'#app',
data:{
name:'爱学习的小萌新',
user:{
name:'海贼王',
sex:'男'
},
text:'测试text',
html:'<button>测试</button>',
myClass:['cssTest','cssFont']
},
methods:{
getJob(){
return 'program'
},
clickTest(){
console.log('clickTest');
this.name = 'nakura';
this.user.name = 'adventure';
this.user.sex = '女';
this.text = 'text内容改变';
this.myClass = 'cssBlue';
}
}
})
</script>
</html>
Lei.js
function Lei(options) {
this.$options = options;
this.data = this._data = options.data || {};
this.$methods = options.methods;
Object.keys(this.data).forEach(key => {
this._proxy(key);
});
Object.keys(this.$methods).forEach(key => {
Lei.prototype[key] = this.$methods[key];
});
observe(this.data);
new Compile(options.el,this)
}
Lei.prototype = {
_proxy(key){
Object.defineProperty(this,key,{
configurable:false,
enumerable:true,
get(){
return this._data[key]
},
set(newVal){
this._data[key] = newVal;
}
})
}
}
Compile.js
function Compile(el, lei) {
this.$lei = lei;
let srcNode = document.querySelector(el) || document.getElementsByName('body')[0];
this.getAllNodes(el);
this.init();
srcNode.appendChild(this.$fragment);
}
Compile.prototype = {
getAllNodes(el) {
let ele = document.querySelector(el);
let fragment = document.createDocumentFragment();
while (child = ele.firstChild) {
fragment.appendChild(child);
}
this.$fragment = fragment;
},
init() {
this._compileNodes(this.$fragment);
},
_compileNodes(fragment) {
let childNodes = fragment.childNodes
Array.prototype.slice.call(childNodes).forEach(node => {
if (compileUtil.isElement(node)) {
this.compileElement(node);
} else if (compileUtil.isText(node)) {
this.compileText(node);
}
if (node.childNodes && node.childNodes.length) {
this._compileNodes(node);
}
})
},
compileElement(node) {
const nodeAttrs = node.attributes
Array.prototype.slice.call(nodeAttrs).forEach(attr => {
const attrName = attr.name;
const exp = attr.nodeValue;
const dir = attrName.substring(2);
if (compileUtil.isDirective(attrName)) {
if (compileUtil.isEventAttr(attrName)) {
compileUtil.eventHandler(node, this.$lei, dir, exp);
} else {
compileUtil.bind(node, this.$lei, dir, exp);
}
}
})
},
compileText(node) {
var reg = /{{(.*?)}}/g;
let textContent = node.textContent.trim();
node._template = textContent;
compileUtil.updater.innerTextUpdater(node,this.$lei);
while (temp = reg.exec(textContent)) {
new Sub(this.$lei, temp[1], (newVal, oldVal) => {
compileUtil.updater.innerTextUpdater(node,this.$lei,newVal, oldVal);
});
}
}
}
const compileUtil = {
isElement(node) {
return node.nodeType === 1
},
isText(node) {
return node.nodeType === 3
},
isEventAttr(name) {
return name.indexOf('v-on') == 0
},
isDirective(name) {
return name.indexOf('v-') == 0
},
eventHandler(node, lei, dir, exp) {
const fn = lei[exp]
const eventType = dir.split(':')[1];
if (fn && eventType) {
node.addEventListener(eventType, fn.bind(lei), false);
}
},
bind(node, lei, dir, exp) {
const dirs = dir.split(':');
const dirType = dirs[0];
const attrType = dirs[1];
const updater = compileUtil.updater[dirType + 'Updater'];
const newVal = lei[exp]
updater && updater(node, newVal);
new Sub(lei, exp, (newVal, oldVal) => {
updater && updater(node, newVal, oldVal);
});
},
updater: {
innerTextUpdater(node,lei) {
const textContent = node._template;
var reg = /{{(.*?)}}/g;
let tempStr = textContent;
while (temp = reg.exec(textContent)) {
tempStr = tempStr.replace(temp[0], compileUtil._getVal(lei, temp[1]));
}
node.textContent = tempStr;
},
textUpdater(node, newVal) {
node.textContent = newVal ? newVal : '';
},
htmlUpdater(node, newVal) {
node.innerHTML = newVal ? newVal : '';
},
classUpdater(node, newVal) {
if (!newVal) return;
if (Object.prototype.toString.call(newVal) == '[object Array]') {
newVal.forEach(item => {
if (Object.prototype.toString.call(item) == '[object String]') {
node.classList.add(item);
}
})
} else if (Object.prototype.toString.call(newVal) == '[object String]') {
node.classList.add(newVal);
}
}
},
_getVal(lei, exp) {
var exp = exp.split('.');
var val = lei._data;
exp.forEach(k => {
val = val[k];
});
return val
}
}
const directives = {
'text': function (node, lei, exp) {
compileUtil.bind(node, lei, 'text', exp);
},
'html': function () {
},
'class': function () {
},
'model': function () {
},
}
Observer.js
function Observer(data){
this.data = data;
this.init(data);
}
function observe(data){
if(!data || typeof data != 'object')return;
new Observer(data);
}
Observer.prototype = {
init(){
Object.keys(this.data).forEach( key => {
this.defineReactive(key,this.data[key]);
});
},
defineReactive(key,value){
let pub = new Pub();
let ob = observe(value);
Object.defineProperty(this.data,key,{
configurable:false,
enumerable:true,
get(){
if(Pub.target){
pub.addSub()
}
return value;
},
set(newVal){
if(newVal == value)return;
value = newVal;
ob = observe(newVal);
pub.update();
}
});
}
}
Pub.js
var uid = 0;
function Pub(){
this.id = uid++;
this.subs = [];
}
Pub.prototype = {
pushSub(sub){
this.subs.push(sub);
},
addSub(){
Pub.target.addSub(this);
},
update(){
this.subs.forEach(sub => {
sub.update();
});
}
}
Pub.target = undefined;
Sub.js
function Sub(lei,exp,callback){
this.callback = callback;
this.lei = lei;
this.exp = exp;
this.pubIds = {};
this.value = this.bind();
}
Sub.prototype = {
update(){
const oldVal = this.value;
const newVal = this._getVal();
if(oldVal != newVal){
this.value = newVal;
this.callback.call(this.lei,newVal,oldVal);
}
},
bind(){
Pub.target = this;
const val = this._getVal();
Pub.target = undefined;
return val;
},
addSub(pub){
if(!this.pubIds.hasOwnProperty(pub.id)){
pub.pushSub(this);
this.pubIds[pub.id] = pub;
}
},
_getVal() {
var exp = this.exp.split('.');
var val = this.lei._data;
exp.forEach(k => {
val = val[k];
});
return val
}
}