前言
Moon目前实现的内置指令有八个:if/show/for/on/model/html/literal/mask,也可以让用户自定义指令。
beforeGenerate ->duringPropGenerate->afterGenerate
所有指令都是在generateProps或generateNode里调用,分别按beforeGenerate ->duringPropGenerate->afterGenerate 的状态执行指令逻辑,我们节选相关的代码段:
let beforeGenerate = null;
for(propKey in props) {
const prop = props[propKey];
const name = prop.name;
if((specialDirective = specialDirectives[name]) !== undefined
&& (beforeGenerate = specialDirective.beforeGenerate) !== undefined) {
beforeGenerate(prop, node, parent, state);
}
}
顾名思义,这个beforeGenerate 值得就是compile过程中generate执行之前,这时ast还没有转换成code。我们可以看到,传入了四个参数:prop, node, parent, state,从state参数我们可以联想到ast的state变量,所以更加确认这是ast层面的操作无疑了。
let afterGenerate = null;
let duringPropGenerate = null;
for(propKey in props) {
const prop = props[propKey];
const name = prop.name;
if((specialDirective = specialDirectives[name]) !== undefined) {
if((afterGenerate = specialDirective.afterGenerate) !== undefined) {
specialDirectivesAfter[name] = {
prop: prop,
afterGenerate: afterGenerate
};
hasSpecialDirectivesAfter = true;
}
if((duringPropGenerate = specialDirective.duringPropGenerate) !== undefined) {
propsCode += duringPropGenerate(prop, node, state);
}
node.meta.shouldRender = true;
}
......
if (specialDirectivesAfter !== null) {
var specialDirectiveAfter;
for (var specialDirectiveKey in specialDirectivesAfter) {
specialDirectiveAfter = specialDirectivesAfter[specialDirectiveKey];
call = specialDirectiveAfter.afterGenerate(specialDirectiveAfter.prop, call, node, state);
}
}
}
而在afterGenerate和 duringPropGenerate这两个时期,code已经生成了或者正在生成中,操作的就是code了。这时我们发现duringPropGenerate比beforeGenerate少传了个parent参数,因为树结构中才有父元素嘛~
而afterGenerate又比duringPropGenerate多传了个call参数(实际上它是一段code),这更加说明了afterGenerate是在generate完code后再往上面加code的过程。
内置指令
m-if
m-if在afterGenerate里生成一个三元表达式,若条件为真就生成vnode的code,否则是空的VNode:
specialDirectives["m-if"] = {
afterGenerate: function(prop, code, vnode, state) {
var value = prop.value;
compileTemplateExpression(value, state.dependencies);
return (value + " ? " + code + " : " + emptyVNode);
}
}
m-show
m-show也是生成一个三元表达式,但它是直接修改el.style.display,和那三个过程无关:
directives["m-show"] = function(el, val, vnode) {
el.style.display = (val ? '' : 'none');
}
m-for
m-for在beforeGenerate里设置flatten数组的标志位,在afterGenerate里迭代生成code:
specialDirectives["m-for"] = {
beforeGenerate: function(prop, vnode, parentVNode, state) {
// Setup Deep Flag to Flatten Array
parentVNode.deep = true;
},
afterGenerate: function(prop, code, vnode, state) {
// Get dependencies
var dependencies = state.dependencies;
// Get Parts
var parts = prop.value.split(" in ");
// Aliases
var aliases = parts[0].split(",");
// The Iteratable
var iteratable = parts[1];
compileTemplateExpression(iteratable, dependencies);
// Get any parameters
var params = aliases.join(",");
// Add aliases to scope
for (var i = 0; i < aliases.length; i++) {
var aliasIndex = dependencies.indexOf(aliases[i]);
if (aliasIndex !== -1) {
dependencies.splice(aliasIndex, 1);
}
}
// Use the renderLoop runtime helper
// 等同于return `Moon.renderLoop(${iteratable}, function(${params}) {return${code};})`
return ("Moon.renderLoop(" + iteratable + ", function(" + params + ") { return " + code + "; })");
}
}
我们可以发现它会把形似"m-if=(item,key) in iteratable"中的item/key当做params解析出来,然后返回一份由renderLoop和code生成的新code。
renderLoop
这里用到了renderLoop来迭代生成code:
Moon.renderLoop = function(iteratable, item) {
var items = null;
if (Array.isArray(iteratable)) {
items = new Array(iteratable.length);
// Iterate through the array
for (var i = 0; i < iteratable.length; i++) {
items[i] = item(iteratable[i], i);
}
} else if (typeof iteratable === "object") {
items = [];
// Iterate through the object
for (var key in iteratable) {
items.push(item(iteratable[key], key));
}
} else if (typeof iteratable === "number") {
items = new Array(iteratable);
// Repeat a certain amount of times
for (var i$1 = 0; i$1 < iteratable; i$1++) {
items[i$1] = item(i$1 + 1, i$1);
}
}
return items;
}
很明显,它对应着我们api中使用for指令时的三种可迭代情况:数组、对象、数字。就像lodash封装数组和对象为集合方法一样,针对数组和数字,就使用for循环迭代,针对对象就使用for...in迭代。
另外我们也明显地感受到了这位作者不是前端出身的,因为它担心i$1会覆盖i变量所以才这样命名的,实际上js的var可以重复声明的。
m-on
m-on在beforeGenerate里获取事件名和回调以及事件修饰符(如prevent),然后生成调用事件的代码并添加监听回调:
specialDirectives["m-on"] = {
beforeGenerate: function(prop, vnode, parentVNode, state) {
// Extract Event, Modifiers, and Parameters
var value = prop.value;
var meta = prop.meta;
var methodToCall = value;
var rawModifiers = meta.arg.split(".");
var eventType = rawModifiers.shift();
var params = "event";
var rawParams = methodToCall.split("(");
if (rawParams.length > 1) {
// Custom parameters detected, update method to call, and generated parameter code
methodToCall = rawParams.shift();
params = rawParams.join("(").slice(0, -1);
compileTemplateExpression(params, state.dependencies);
}
// Generate any modifiers
var modifiers = "";
for (var i = 0; i < rawModifiers.length; i++) {
var eventModifierCode = eventModifiersCode[rawModifiers[i]];
if (eventModifierCode === undefined) {
modifiers += "if(Moon.renderEventModifier(event.keyCode, \"" + (rawModifiers[i]) + "\") === false) {return null;};"
} else {
modifiers += eventModifierCode;
}
}
// Final event listener code
var code = "function(event) {" + modifiers + "instance.callMethod(\"" + methodToCall + "\", [" + params + "])}";
addEventListenerCodeToVNode(eventType, code, vnode);
}
}
m-model
m-model在beforeGenerate里获取依赖、根据html类型绑定事件,然后动态检测依赖的getter,生成代码(靠set改变值),添加事件监听:
specialDirectives["m-model"] = {
beforeGenerate: function(prop, vnode, parentVNode, state) {
......
}
因为实际上model指令是bind指令和on指令的结合语法糖,所以只能适用于特殊的表单元素:input、checkbox、radio等,而Moon并没有实现bind指令,所以在这里实现地颇为复杂,不过我们可以大致地知道bind value或者checked值它用getter和setter的方式实现了,on的部分监听change事件。
m-html
m-html对应v-html,它会在beforeGenerate里设置先通过compileTemplateExpression收集依赖,再设置innerHTML:
specialDirectives["m-html"] = {
beforeGenerate: function(prop, vnode, parentVNode, state) {
var value = prop.value;
var dom = vnode.props.dom;
if (dom === undefined) {
vnode.props.dom = dom = {};
}
compileTemplateExpression(value, state.dependencies);
dom.innerHTML = "(\"\" + " + value + ")";
}
}
它没有返回值,而是直接操作的dom。
m-literal
m-literal对应v-text,在duringPropGenerate里先收集依赖,再通过renderClass生成带样式的文本:
specialDirectives["m-literal"] = {
duringPropGenerate: function(prop, vnode, state) {
var propName = prop.meta.arg;
var propValue = prop.value;
compileTemplateExpression(propValue, state.dependencies);
if (state.hasAttrs === false) {
state.hasAttrs = true;
}
if (propName === "class") {
// Detected class, use runtime class render helper
return ("\"class\": Moon.renderClass(" + propValue + "), ");
} else {
// Default literal attribute
return ("\"" + propName + "\": " + propValue + ", ");
}
}
};
如果带样式,就通过renderClass生成样式代码,否则直接一段文本代码。
renderClass
它负责生成样式中class的code:
Moon.renderClass = function(classNames) {
if (typeof classNames === "string") {
// If they are a string, no need for any more processing
return classNames;
}
var renderedClassNames = "";
if (Array.isArray(classNames)) {
// It's an array, so go through them all and generate a string
for (var i = 0; i < classNames.length; i++) {
renderedClassNames += (Moon.renderClass(classNames[i])) + " ";
}
} else if (typeof classNames === "object") {
// It's an object, so to through and render them to a string if the corresponding condition is truthy
for (var className in classNames) {
if (classNames[className]) {
renderedClassNames += className + " ";
}
}
}
// Remove trailing space and return
renderedClassNames = renderedClassNames.slice(0, -1);
return renderedClassNames;
}
可以看出来它同样是分了三种情况:class是字符串、数组和对象的情况。如果是字符串直接返回class名,如果是数组就递归拼接,如果是对象(形似{'active':isTrue})就根据值的真假拼接是键的class名。
m-mask
m-mask目前是空的,按字面意思是面具指令,猜测是类似v-mask的输入自动格式化指令。
自定义指令
用户可以自定义一个指令,它存储在directives里。
比如我们定义一个m-my指令,让一个input元素在页面加载的时候自动聚焦:
Moon.directive('my',
function(node, vnode, parentVNode) {
node.focus()
}
)
它会先把m-my存储到directives里
Moon.directive = function(name, action) {
directives["m-" + name] = action;
}
在diffProps的里面会执行m-my对应的函数:
从这里可以看出,它传参数的方式和三种过程都不一样。
总结
指令在angular中出现的比vue略早一些,在ng里指令还分结构性指令和非结构性指令。但在moon中,指令也分为操作ast或code和不操作的指令。