Juicer.js源码解读
Version: 0.6.9-stable
Date: 8th of Aug, 2015
个人能力有限,若有剖析不当的处所,恳请斧正!
第一部份: 参数设置
要领与参数
参数设置要领是 juicer.set
,该要领吸收两个参数或一个参数:
当传入两个参数时,如
juicer.set('cache',false)
,等于设置cache
为false
当传入一个参数时,该参数应为一个对象,如
juicer.set({cache:false})
,体系将遍历这个对象的属性来设值
可以设置的内容
我们可以设置一些参数选项,包含 cache
、strip
、errorhandling
、detection
;其默许值都是true
;我们还可以修正模板的语法边境符,如 tag::operationOpen
等。详细可设置的项可以参看其源代码。
事情道理
juicer.options = {
// 是不是缓存模板编译效果
cache: true,
// 是不是消灭空缺
strip: true,
// 是不是处置惩罚毛病
errorhandling: true,
// 是不是检测变量是不是定义
detection: true,
// 自定义函数库
_method: __creator({
__escapehtml: __escapehtml,
__throw: __throw,
__juicer: juicer
}, {})
};
选项剖析以下:
-
cache
是不是缓存编译效果(引擎对象)。缓存的效果存于juicer.__cache
-
strip
是不是消灭模板中的空缺,包含换行、回车等 -
errorhandling
是不是处置惩罚毛病 -
detection
开启后,假如变量未定义,将用空缺字符串替换变量位置,不然照旧输出,所以假如封闭此项,有可以形成输出undefined
-
_method
存储的是用户注册的自定义函数,体系内部建立的自定义函数或对象有__escapehtml
处置惩罚HTML转义、__throw
抛出毛病、__juicer
援用juicer
。__creator
要领本文最末解说
在 Node.js 环境中,cache
默许值是 false
,请看下面代码
if(typeof(global) !== 'undefined' && typeof(window) === 'undefined') {
juicer.set('cache', false);
}
这段代码在末端处可以找到。
另外,另有一个属性是 juicer.options.loose
,默许值为 undefined
(没有设置),当其值不为 false
(此亦体系默许)时,将对 {@each}
、{@if}
、{@else if}
、${}
、{@include}
等中的变量名和自定义函数名举行校验,给个中运用到的变量、函数定义并添加到模板的开首,以保证可以顺遂运用。
所以,假如我们变动此设置,可以形成体系毛病
// 这些操纵应该防止,不然会形成体系毛病
// 将`juicer.options.loose`设为`false`
// juicer.set('loose',false);
下面来看 juicer.set
要领的源代码
juicer.set = function(conf, value) {
// 援用`juicer`
var that = this;
// 反斜杠转义
var escapePattern = function(v) {
// 婚配 $ ( [ ] + ^ { } ? * | . *
// 这些标记都须要被转义
return v.replace(/[\$\(\)\[\]\+\^\{\}\?\*\|\.]/igm, function($) {
return '\\' + $;
});
};
// 设置函数
var set = function(conf, value) {
// 语法边境符婚配
var tag = conf.match(/^tag::(.*)$/i);
if(tag) {
// 由于体系这里没有推断语法边境符是不是是体系所用的
// 所以一定要拼写正确
that.tags[tag[1]] = escapePattern(value);
// 从新天生婚配正则
// `juicer.tagInit`剖析见下面
that.tagInit();
return;
}
// 其他设置项
that.options[conf] = value;
};
// 假如传入两个参数,`conf`示意要修正的属性,`value`是要修正的值
if(arguments.length === 2) {
set(conf, value);
return;
}
// 假如传入一个参数,且是对象
if(conf === Object(conf)) {
// 遍历该对象的自有属性设置
for(var i in conf) {
if(conf.hasOwnProperty(i)) {
set(i, conf[i]);
}
}
}
};
解释内里已提醒,经由过程 juicer.set
要领可以掩盖任何属性。
假如修正了语法边境符设定,将会从新天生婚配正则,下面看婚配正则的源代码
juicer.tags = {
// 操纵开
operationOpen: '{@',
// 操纵闭
operationClose: '}',
// 变量开
interpolateOpen: '\\${',
// 变量闭标签
interpolateClose: '}',
// 制止对其内容转义的变量开
noneencodeOpen: '\\$\\${',
// 制止对其内容转义的变量闭
noneencodeClose: '}',
// 解释开
commentOpen: '\\{#',
// 解释闭
commentClose: '\\}'
};
juicer.tagInit = function() {
/**
* 婚配each轮回最先,以下都是OK的
* `each VAR as VALUE`, 如 {@each names as name}
* `each VAR as VALUE ,INDEX`,如 {@each names as name,key}
* `each VAR as`,如 {@each names as}
* 须要申明后两种状况:
* `,key` 是一同被捕捉的,所以在编译模板的时刻,体系会用`substr`去掉`,`
* as 后没有指定别号的话,默许以`value`为别号,所以
* {@each names as} 等价于 {@each names as value}
*/
var forstart = juicer.tags.operationOpen + 'each\\s*([^}]*?)\\s*as\\s*(\\w*?)\\s*(,\\s*\\w*?)?' + juicer.tags.operationClose;
// each轮回完毕
var forend = juicer.tags.operationOpen + '\\/each' + juicer.tags.operationClose;
// if前提最先
var ifstart = juicer.tags.operationOpen + 'if\\s*([^}]*?)' + juicer.tags.operationClose;
// if前提完毕
var ifend = juicer.tags.operationOpen + '\\/if' + juicer.tags.operationClose;
// else前提最先
var elsestart = juicer.tags.operationOpen + 'else' + juicer.tags.operationClose;
// eles if 前提最先
var elseifstart = juicer.tags.operationOpen + 'else if\\s*([^}]*?)' + juicer.tags.operationClose;
// 婚配变量
var interpolate = juicer.tags.interpolateOpen + '([\\s\\S]+?)' + juicer.tags.interpolateClose;
// 婚配不对其内容转义的变量
var noneencode = juicer.tags.noneencodeOpen + '([\\s\\S]+?)' + juicer.tags.noneencodeClose;
// 婚配模板内容解释
var inlinecomment = juicer.tags.commentOpen + '[^}]*?' + juicer.tags.commentClose;
// for辅佐轮回
var rangestart = juicer.tags.operationOpen + 'each\\s*(\\w*?)\\s*in\\s*range\\(([^}]+?)\\s*,\\s*([^}]+?)\\)' + juicer.tags.operationClose;
// 引入子模板
var include = juicer.tags.operationOpen + 'include\\s*([^}]*?)\\s*,\\s*([^}]*?)' + juicer.tags.operationClose;
// 内联辅佐函数最先
var helperRegisterStart = juicer.tags.operationOpen + 'helper\\s*([^}]*?)\\s*' + juicer.tags.operationClose;
// 辅佐函数代码块内语句
var helperRegisterBody = '([\\s\\S]*?)';
// 辅佐函数完毕
var helperRegisterEnd = juicer.tags.operationOpen + '\\/helper' + juicer.tags.operationClose;
juicer.settings.forstart = new RegExp(forstart, 'igm');
juicer.settings.forend = new RegExp(forend, 'igm');
juicer.settings.ifstart = new RegExp(ifstart, 'igm');
juicer.settings.ifend = new RegExp(ifend, 'igm');
juicer.settings.elsestart = new RegExp(elsestart, 'igm');
juicer.settings.elseifstart = new RegExp(elseifstart, 'igm');
juicer.settings.interpolate = new RegExp(interpolate, 'igm');
juicer.settings.noneencode = new RegExp(noneencode, 'igm');
juicer.settings.inlinecomment = new RegExp(inlinecomment, 'igm');
juicer.settings.rangestart = new RegExp(rangestart, 'igm');
juicer.settings.include = new RegExp(include, 'igm');
juicer.settings.helperRegister = new RegExp(helperRegisterStart + helperRegisterBody + helperRegisterEnd, 'igm');
};
详细语法边境符的用法请参照官方文档:http://www.juicer.name/docs/docs_zh_cn.html
平常地,不发起对默许标签举行修正。固然,假如默许语法边境符划定规矩与正在运用的其他言语语法划定规矩争执,修正 juicer
的语法边境符就很有效了。
须要注重,{@each names as}
等价于 {@each names as value}
,只管我们仍要坚持正确誊写的划定规矩,防止应用体系自动纠错机制
// 以下模板的写法是不引荐的
/**
{@each list as}
<a href="${value.href}">${value.title}</a>
{@/each}
*/
第二部份: 注册自定义函数
上面说,juicer.options._method
存储了用户的自定义函数,那末我们怎样注册以及怎样运用自定义函数呢?
注册/销自定义函数
juicer.register
要领用来注册自定义函数
juicer.unregister
要领用来注销自定义函数
// `fname`为函数名,`fn`为函数
juicer.register = function(fname, fn) {
// 自定义函数均存储于 `juicer.options._method`
// 假如已注册了该函数,不许可掩盖
if(_method.hasOwnProperty(fname)) {
return false;
}
// 将新函数注册进入
return _method[fname] = fn;
};
juicer.unregister = function(fname) {
var _method = this.options._method;
// 没有检测是不是注销的是体系自定义函数
// 用户不要注销错了
if(_method.hasOwnProperty(fname)) {
return delete _method[fname];
}
};
自定义函数都是存储在juicer.options._method
中的,因此以下要领可以跳过函数是不是注册的磨练强行变动自定义函数,这些操纵很风险:
// 这些操纵应该防止,不然会形成体系毛病
// 转变`juicer.options._method`
// juicer.set('_method',{});
// juicer.unregister('__juicer');
// juicer.unregister('__throw');
// juicer.unregister('__escapehtml');
第三部份: 编译模板
先看下 juicer
的定义部份。
var juicer = function() {
// 将通报参数(伪数组)切成数组后返回给`args`,以便挪用数组的要领
var args = [].slice.call(arguments);
// 将`juicer.options`推入`args`,示意衬着运用当前设置
args.push(juicer.options);
/**
* 下面将猎取模板内容
* 模板内容取决于我们通报给`juicer`函数的首参数
* 可所以模板节点的id属性值
* 也可所以模板内容本
*/
// 起首会试着婚配,婚配胜利就先看成id处置惩罚
// 摆布两侧的空缺会被疏忽
// 假如是`#`开首,背面随着字母、数字、下划线、短横线、冒号、点号都可婚配
// 所以这么写都是可以的:`id=":-."`
if(args[0].match(/^\s*#([\w:\-\.]+)\s*$/igm)) {
// 假如传入的是模板节点的id,会经由过程`replace`要领正确婚配并猎取模板内容
// 回调函数的首参`$`是婚配的全部内容(首参),$id是婚配的节点id
args[0].replace(/^\s*#([\w:\-\.]+)\s*$/igm, function($, $id) {
// node.js环境没有`document`,所以会先推断`document`
var _document = document;
// 找寻节点
var elem = _document && _document.getElementById($id);
// 假如该节点存在,节点的`value`或`innerHTML`就是模板内容
// 等于说,寄存模板的内容节点只需有`value`或`innerHTML`属性即可
// <script>可以,<div>可以,<input>也可以
// 假如没有节点,照样把首参值作为模板内容
args[0] = elem ? (elem.value || elem.innerHTML) : $;
});
}
// 假如是浏览器环境
if(typeof(document) !== 'undefined' && document.body) {
// 先编译`document.body.innerHTML`一次
juicer.compile.call(juicer, document.body.innerHTML);
}
// 假如只传入了模板,仅返回编译效果,而不会马上衬着
if(arguments.length == 1) {
return juicer.compile.apply(juicer, args);
}
// 假如传入了数据,编译以后马上衬着
if(arguments.length >= 2) {
return juicer.to_html.apply(juicer, args);
}
};
juicer.compile
要领是模板内容编译进口,其返回一个编译引擎对象,引擎对象的 render
要领将实行衬着.
juicer.to_html
要领就是实行 juicer.compile
后马上实行 render
。我们在向 juicer
函数传入两个参数的时刻,就会马上实行这一要领。
先看 juicer.to_html
juicer.to_html = function(tpl, data, options) {
// 假如没有传入设置或许有新设置,先从新天生设置
if(!options || options !== this.options) {
options = __creator(options, this.options);
}
// 衬着
return this.compile(tpl, options).render(data, options._method);
};
下面看 juicer.compile
是怎样编译模板内容的
juicer.compile = function(tpl, options) {
// 假如没有传入设置或许有新设置,先从新天生设置
if(!options || options !== this.options) {
options = __creator(options, this.options);
}
try {
// 组织引擎对象,假如已缓存则优先运用缓存
var engine = this.__cache[tpl] ?
this.__cache[tpl] :
new this.template(this.options).parse(tpl, options);
// 除非设定`juicer.options.cache`为`false`,不然缓存引擎对象
if(!options || options.cache !== false) {
this.__cache[tpl] = engine;
}
// 返回引擎对象
return engine;
} catch(e) {
// 抛出毛病,此要领在本文末引见
__throw('Juicer Compile Exception: ' + e.message);
// 返回一个新对象,该对象仍有`render`要领,但操纵为空
return {
render: function() {}
};
}
};
第四部份: 引擎对象
juicer.compile
要领在一般状况下会返回模板引擎对象,继而实行该对象的 render
要领就可以取得我们的模板编译效果(HTML)。那引擎对象是怎样被组织出来的呢?
看这句 new this.template(this.options).parse(tpl, options);
由此,我们进入了 juicer
的中心组织函数,juicer.template
。由于该组织函数篇幅很长,我们先看下简单版的构造,然后拆开来剖析。
juicer.template = function(options) {
// 由于`juicer.template`是作为组织器运用的
// 因此`this`援用的是`juicer.template`组织的实例
var that = this;
// 援用选项设置`juicer.options`
this.options = options;
// 变量剖析要领
this.__interpolate = function(_name, _escape, options) {};
// 模板剖析要领
this.__removeShell = function(tpl, options) {};
// 依据`juicer.options.strip`推断是不是消灭过剩空缺
// 然后挪用`juicer.template.__convert`
this.__toNative = function(tpl, options) {};
// 词法剖析,天生变量和自定义函数定义语句
this.__lexicalAnalyze = function(tpl) {};
// 为`juicer.template.__toNative`所挪用
// 将模板剖析为可实行的JavaScript字符串
this.__convert = function(tpl, strip) {};
// 衬着模板的进口
this.parse = function(tpl, options) {};
};
好,下面我们一点点地看
juicer.template.__interpolate
this.__interpolate = function(_name, _escape, options) {
/**
* `_define` 切割`_name`
* `_fn`为变量名,这里先暂取值为 `_define[0]`
* 当传入的首参没有`|`支解变量和函数时
* `_fn` === `_define[0]` === `_name`
* 表明是 ${name} 情势
* 当有`|`支解时,`_fn`的初始值会被掩盖
* 情势是 ${name|function} 或 ${name|function,arg1,arg2}
* `_cluster`为函数及传参
*/
var _define = _name.split('|'), _fn = _define[0] || '', _cluster;
// 假如有`|`支解,即有函数和传参
// 举个例子: `VAR|FNNAME,FNVAR,FNVAR2
if(_define.length > 1) {
// VAR
_name = _define.shift();
// [FNNAME,FNVAR,FNVAR2]
_cluster = _define.shift().split(',');
// `[_name].concat(_cluster)`是数组会自动挪用`toString()`要领
// 效果就是:_metod.FNNAME.call({},VAR,FNVAR,FNVAR2)
_fn = '_method.' + _cluster.shift() + '.call({}, ' + [_name].concat(_cluster) + ')';
}
/**
* 返回效果
* 假如`_escape`为真,将转义内容
* 假如`juicer.options.detection`为真,将检测变量是不是定义
* 返回效果举例(转义内容且检测变量定义)
* <%=_method.__escapehtml.escaping(_method.__escapehtml.detection(`_fn`))%>
*/
return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' +
(!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' +
_fn +
')' +
')' +
' %>';
};
这个要领用来剖析变量的。这也许可我们去运用自定义函数。如我们建立自定义函数
// 经由过程`juicer.register`直接建立
juicer.register('echoArgs',function(a,b){
return a + b;
});
// 或许在模板内经由过程内联辅佐函数间接建立
// 实质仍然是运用了`juicer.register`
{@helper echoArgs}
function(a,b){
return a+b;
}
{@/helper}
我们在模板里就可以这么用了:
// 运用自定义函数
${value.href|echoArgs|value.title}
juicer.template.__removeShell
this.__removeShell = function(tpl, options) {
// 计数器
// 应用计数器防止遍用时建立的暂时变量与其他变量争执
var _counter = 0;
// 剖析模板内容
tpl = tpl
// 剖析模板里的内联辅佐函数并注册
.replace(juicer.settings.helperRegister, function($, helperName, fnText) {
// `annotate`函数返回形参称号和函数语句数组,本文末引见
var anno = annotate(fnText);
// 内联辅佐函数参数
var fnArgs = anno[0];
// 内敛辅佐函数语句
var fnBody = anno[1];
// 组织内联辅佐函数
var fn = new Function(fnArgs.join(','), fnBody);
// 注册到自定义函数库`juicer.options._method`
juicer.register(helperName, fn);
// 没有消灭{@helper}{@/helper}
return $;
})
/**
* 剖析each轮回语句
* 举个例子: {@each names as name,index}
* `_name` => names
* `alias` => name
* `key` => ,index 注重正则婚配后前面有逗号
*/
.replace(juicer.settings.forstart, function($, _name, alias, key) {
// `alias` 假如木有,取为`value`,如 {@each names as} 状况
// `key` 假如须要属性名,取之
var alias = alias || 'value', key = key && key.substr(1);
// 防止反复
var _iterate = 'i' + _counter++;
/**
* 返回替换效果,举例以下
* <% ~function(){
for(var i0 in names){
if(names.hasOwnProperty(i0)){
var name = names[i0];
var index = i0;
%>
*/
return '<% ~function() {' +
'for(var ' + _iterate + ' in ' + _name + ') {' +
'if(' + _name + '.hasOwnProperty(' + _iterate + ')) {' +
'var ' + alias + '=' + _name + '[' + _iterate + '];' +
(key ? ('var ' + key + '=' + _iterate + ';') : '') +
' %>';
})
// 剖析each轮回完毕
.replace(juicer.settings.forend, '<% }}}(); %>')
// 剖析if前提最先
.replace(juicer.settings.ifstart, function($, condition) {
return '<% if(' + condition + ') { %>';
})
// 剖析if前提完毕
.replace(juicer.settings.ifend, '<% } %>')
// 剖析else前提
.replace(juicer.settings.elsestart, function($) {
return '<% } else { %>';
})
// 剖析else if前提
.replace(juicer.settings.elseifstart, function($, condition) {
return '<% } else if(' + condition + ') { %>';
})
// 剖析制止对其内容转义的变量
.replace(juicer.settings.noneencode, function($, _name) {
return that.__interpolate(_name, false, options);
})
// 剖析变量
.replace(juicer.settings.interpolate, function($, _name) {
return that.__interpolate(_name, true, options);
})
// 消灭批评
.replace(juicer.settings.inlinecomment, '')
// 剖析辅佐轮回
.replace(juicer.settings.rangestart, function($, _name, start, end) {
var _iterate = 'j' + _counter++;
return '<% ~function() {' +
'for(var ' + _iterate + '=' + start + ';' + _iterate + '<' + end + ';' + _iterate + '++) {{' +
'var ' + _name + '=' + _iterate + ';' +
' %>';
})
// 载入子模板
.replace(juicer.settings.include, function($, tpl, data) {
// 假如是node.js环境
if(tpl.match(/^file\:\/\//igm)) return $;
// 返回 <% _method.__juicer(tpl,data);%>
return '<%= _method.__juicer(' + tpl + ', ' + data + '); %>';
});
// 当`juicer.options.errorhandling`不为`false`
if(!options || options.errorhandling !== false) {
tpl = '<% try { %>' + tpl;
tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';
}
return tpl;
};
计数器的作用已在解释中申明,其定名体式格局是字符串 i
或 j
加数字。
因此,以下模板就可以毛病:
// 以下模板的写法是不引荐的
// 应该防止遍历中的变量名与计数器建立的变量名争执
/**
// 模板
${i0}
{@each data as value}
{# i0 可以会由于上面的遍历建立的暂时变量而被替换}
${i0}
{@/each}
${i0}
${j1}
{@each i in range(1,5)}
{# j1 可以会由于上面的轮回建立的暂时变量而被替换}
${j1}
{@/each}
${j1}
// 数据
{
data: {
temp1: 'value1',
temp2: 'value2'
},
i0: 'i0',
j1: 'j1'
}
// 效果
i0 temp1 temp2 i0 j1 1 2 3 4 j1
*/
誊写变量在遍历中涌现的变量时刻,一定要防止与体系制造的暂时变量重名。不过,由于在编译模板的时刻,遍历是在一个闭包中实行的,因此暂时变量不会影响到遍历外的变量。
另外,引荐运用 juicer.register
注册自定义函数,而非运用{@helper}{/@helper}
。由于内联函数的代码在天生HTML的时刻没有被消灭。
假如要消灭之,须将第 #297 行
return $;
变动为
return '';
juicer.template.__toNative
this.__toNative = function(tpl, options) {
// 当`juicer.options.strip`不为`false`时消灭过剩空缺
return this.__convert(tpl, !options || options.strip);
};
juicer.template.__toNative
挪用 juicer.template.__convert
要领,juicer.template.__convert
就不作剖析了,经由过程不停替换切割等构成函数语句。
juicer.__lexicalAnalyze
this.__lexicalAnalyze = function(tpl) {
// 变量
var buffer = [];
// 要领,已存储到`juicer.options.__method`才被采纳
var method = [];
// 返回效果
var prefix = '';
// 保存辞汇,由于这些辞汇不能用作变量名
var reserved = [
'if', 'each', '_', '_method', 'console',
'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do',
'finally', 'for', 'function', 'in', 'instanceof', 'new', 'return', 'switch',
'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'null', 'typeof',
'class', 'enum', 'export', 'extends', 'import', 'super', 'implements', 'interface',
'let', 'package', 'private', 'protected', 'public', 'static', 'yield', 'const', 'arguments',
'true', 'false', 'undefined', 'NaN'
];
// 查找要领
var indexOf = function(array, item) {
// 假如在数组中查找,直接用数组的`indexOf`要领
if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) {
return array.indexOf(item);
}
// 假如在伪数组中查找,遍历之
for(var i=0; i < array.length; i++) {
if(array[i] === item) return i;
}
return -1;
};
// 变量名剖析函数
var variableAnalyze = function($, statement) {
statement = statement.match(/\w+/igm)[0];
// 假如没有剖析过,而且非保存字符
if(indexOf(buffer, statement) === -1 && indexOf(reserved, statement) === -1 && indexOf(method, statement) === -1) {
// 跳过window内置函数
if(typeof(window) !== 'undefined' && typeof(window[statement]) === 'function' && window[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
return $;
}
// 跳过node.js内置函数
if(typeof(global) !== 'undefined' && typeof(global[statement]) === 'function' && global[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
return $;
}
// 假如是自定义函数
if(typeof(juicer.options._method[statement]) === 'function' || juicer.options._method.hasOwnProperty(statement)) {
// 放进 `method`
method.push(statement);
return $;
}
// 存为变量
buffer.push(statement);
}
return $;
};
// 剖析涌现在for/变量/if/elseif/include中的变量名
tpl.replace(juicer.settings.forstart, variableAnalyze).
replace(juicer.settings.interpolate, variableAnalyze).
replace(juicer.settings.ifstart, variableAnalyze).
replace(juicer.settings.elseifstart, variableAnalyze).
replace(juicer.settings.include, variableAnalyze).
replace(/[\+\-\*\/%!\?\|\^&~<>=,\(\)\[\]]\s*([A-Za-z_]+)/igm, variableAnalyze);
// 遍历要定义的变量
for(var i = 0;i < buffer.length; i++) {
prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';';
}
// 遍历要建立的函数表达式
for(var i = 0;i < method.length; i++) {
prefix += 'var ' + method[i] + '=_method.' + method[i] + ';';
}
return '<% ' + prefix + ' %>';
};
juicer.template.parse
this.parse = function(tpl, options) {
// 指向组织的引擎实例
// `that`和`_that`都是一个援用,暂不明为什么如许写
var _that = this;
// `juicer.options.loose` 不为 `false`
if(!options || options.loose !== false) {
tpl = this.__lexicalAnalyze(tpl) + tpl;
}
// 编译模板,取得可实行的JavaScript字符串
tpl = this.__removeShell(tpl, options);
tpl = this.__toNative(tpl, options);
// 组织为函数
this._render = new Function('_, _method', tpl);
// 衬着要领
this.render = function(_, _method) {
// 搜检自定义函数
if(!_method || _method !== that.options._method) {
_method = __creator(_method, that.options._method);
}
// 实行衬着
return _that._render.call(this, _, _method);
};
// 返回实例,轻易链式挪用
return this;
};
_that
引起了我迷惑。为什么不直接用 that
?在 juicer.template
剖析时我指出,作为组织器被运用的 juicer.template
,var that = this;
的 that
就是指向这个被建立出来的模板引擎对象的,和 _that
起一样的作用。
那为什么要用 _that
或许 that
来替换 this
呢?我想是为了尽量保证衬着一般。假如我们云云运用:
var render = juicer('#:-.').render;
render({});
代码是可以一般运转的。
但假如我们把语句改成 return this._render.call(this, _, _method);
,则会报错,由于这时刻,render
作为全局高低文中的变量,函数中的 this
指针指向了全局对象,而全局对象是没有衬着要领的。
第五部份 辅佐函数
末了剖析下 juicer
里的一些辅佐性函数。
用于转义的对象
var __escapehtml = {
// 转义列表
escapehash: {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": ''',
'/': '/'
},
// 猎取要转义的效果,如传入`<`返回`<`
escapereplace: function(k) {
return __escapehtml.escapehash[k];
},
// 对传参举行转义
escaping: function(str) {
return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace);
},
// 检测,假如传参是undefined,返回空字符串,不然返回传参
detection: function(data) {
return typeof(data) === 'undefined' ? '' : data;
}
};
抛出毛病要领
// 吸收的参数是`Error`组织的实例
var __throw = function(error) {
// 假如控制台可用
if(typeof(console) !== 'undefined') {
// 假如控制台可以抛出正告
if(console.warn) {
console.warn(error);
return;
}
// 假如控制台可以纪录
if(console.log) {
console.log(error);
return;
}
}
// 除此之外都直接抛出毛病
throw(error);
};
兼并对象要领
传入两个对象,并返回一个对象,这个新对象同时具有两个对象的属性和要领。由于 o
是援用通报,因此 o
会被修正
var __creator = function(o, proto) {
// 假如`o`不是对象,则新建空对象
o = o !== Object(o) ? {} : o;
// 仅在一些高等浏览器中有效
if(o.__proto__) {
o.__proto__ = proto;
return o;
}
// 空函数
var empty = function() {};
// 运用原型形式建立新对象
var n = Object.create ?
Object.create(proto) :
new(empty.prototype = proto, empty);
// 将`o`的自有属性赋给新对象
for(var i in o) {
if(o.hasOwnProperty(i)) {
n[i] = o[i];
}
}
// 返回新对象
return n;
};
字符串情势函数剖析要领
传入字符串情势的函数或函数,假如是函数,会应用函数tostring
要领即可取得其字符串情势,继而剖析提取函数的参数名和函数代码块内的语句。
var annotate = function(fn) {
// 婚配函数括号里的参数称号
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
// 婚配逗号,用来支解参数名
var FN_ARG_SPLIT = /,/;
// 婚配参数,假如开首有下划线末端也得有下划线
// 因此自定义函数应防止运用`_X_`情势作为形参
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
// 婚配函数的代码块里语句
var FN_BODY = /^function[^{]+{([\s\S]*)}/m;
// 婚配解释
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
// 函数的参数
var args = [],
// 函数字符串情势
fnText,
// 函数代码块内的语句
fnBody,
// 函数的情势参数婚配效果
// 不是直接的参数称号,以后会经由过程`replace`操纵将真正的称号推入`args`
argDecl;
// 假如传入是函数且函数吸收参数,`toString`转成字符串
if (typeof fn === 'function') {
if (fn.length) {
fnText = fn.toString();
}
// 假如传入的是字符串,即函数字符串情势
} else if(typeof fn === 'string') {
fnText = fn;
}
// 消灭双方空缺
// 低版本浏览器没有 `String.prototype.trim`
fnText = fnText.trim();
// 猎取函数参数称号数组
argDecl = fnText.match(FN_ARGS);
// 猎取函数语句
fnBody = fnText.match(FN_BODY)[1].trim();
// `argDecl[1].split(FN_ARG_SPLIT)` 就是函数的参数名
// 遍历函数参数称号数组
for(var i = 0; i < argDecl[1].split(FN_ARG_SPLIT).length; i++) {
// 赋值为参数称号
var arg = argDecl[1].split(FN_ARG_SPLIT)[i];
// 经由过程替换操纵来将正确的函数称号推入`arg`
arg.replace(FN_ARG, function(all, underscore, name) {
// 过滤下划线前缀
args.push(name);
});
}
// 返回形参称号和函数语句
return [args, fnBody];
};
假如运用 {@helper}
建立自定义函数,_X_
形参将被过滤为 X
,即
// 模板
{@helper userFunc}
function (_X_){
}
{@/helper}
// 编译效果
juicer.options._method.userFunc = function(X){
};
不过,运用 juicer.register
要领将不会过滤下划线。