一、模版解析之一般指令的分析
- 一般指令:在
vue
中是比较常见的,比如v-text
、v-html
等等的一般指令。
- 模版解析之一般指令的实现过程:
- 得到指令名和指令值(表达式)
text/html/class msg/myClass
- 从
data
中根据表达式得到对应的值
- 根据指令名确定需要操作元素节点的什么属性 *
v-text---textContent
属性 *v-html---innerHTML
属性 * v-class---className
属性
- 将得到的表达式的值设置到对应的属性上
- 移除元素的指令属性
- 模版解析之一般指令的关键过程:
text: function (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
html: function (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model: function (node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class: function (node, vm, exp) {
this.bind(node, vm, exp, 'class');
}
- 对于实现解析指令的核心过程:
bind: function (node, vm, exp, dir) {
var updaterFn = updater[dir + 'Updater'];
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
new Watcher(vm, exp, function (value, oldValue) {
updaterFn && updaterFn(node, value, oldValue);
});
},
- 通过
_getVMVal()
和_setVMVal()
的方法进行获取到值,以及对于值进行监听变化,相应进行值的变化,核心过程如下:
_getVMVal: function (vm, exp) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k) {
val = val[k];
});
return val;
},
_setVMVal: function (vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k, i) {
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
- 对于更新节点方法的对象,核心过程如下:
textUpdater: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function (node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function (node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function (node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
二、模版解析之一般指令的实现
- Compile.js
function Compile(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);
}
}
Compile.prototype = {
node2Fragment: function (el) {
var fragment = document.createDocumentFragment(),
child;
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
init: function () {
this.compileElement(this.$fragment);
},
compileElement: function (el) {
var childNodes = el.childNodes,
me = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent;
var reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
me.compileText(node, RegExp.$1);
}
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
compile: function (node) {
var nodeAttrs = node.attributes,
me = this;
[].slice.call(nodeAttrs).forEach(function (attr) {
var attrName = attr.name;
if (me.isDirective(attrName)) {
var exp = attr.value;
var dir = attrName.substring(2);
if (me.isEventDirective(dir)) {
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
node.removeAttribute(attrName);
}
});
},
compileText: function (node, exp) {
compileUtil.text(node, this.$vm, exp);
},
isDirective: function (attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function (dir) {
return dir.indexOf('on') === 0;
},
isElementNode: function (node) {
return node.nodeType == 1;
},
isTextNode: function (node) {
return node.nodeType == 3;
}
};
var compileUtil = {
text: function (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
html: function (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model: function (node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class: function (node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
bind: function (node, vm, exp, dir) {
var updaterFn = updater[dir + 'Updater'];
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
new Watcher(vm, exp, function (value, oldValue) {
updaterFn && updaterFn(node, value, oldValue);
});
},
eventHandler: function (node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
},
_getVMVal: function (vm, exp) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k) {
val = val[k];
});
return val;
},
_setVMVal: function (vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k, i) {
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
};
var updater = {
textUpdater: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function (node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function (node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function (node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};
- 模版解析之一般指令的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>模版解析之一般指令_MVVM</title>
<style>
.aClass {
color: red;
}
.bClass {
color: greenyellow;
}
</style>
</head>
<body>
<div id="test">
<p v-text="msg"></p>
<p v-html="msg"></p>
<p class="aClass" v-class="myClass">测试类名</p>
</div>
</body>
<script type="text/javascript" src="./js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="./js/mvvm/observer.js"></script>
<script type="text/javascript" src="./js/mvvm/watcher.js"></script>
<script type="text/javascript">
new MVVM({
el: "#test",
data: {
msg: '<a href="www.baidu.com">百度</a>',
myClass: 'bClass'
}
})
</script>
</html>
- 关于
vue
的源码分析,我在github
上建立了一个项目,项目地址如下https://github.com/jiuchengTk279/vueSourceCode.git
,欢迎大家访问下载,也希望可以多给予一些建议交流。