我只是从handlebars
开始,我正在尝试做一个简单的double for循环,以使整天的时间间隔为15分钟。如果子代和父代具有相同的值,则会返回view
对象,这是一件很奇怪的事情。这是我的代码:
var handlebarsExpress = require('express-handlebars').create({
helpers: {
for: function(from, to, incr, block) {
var accum = '';
for(var i = from; i <= to; i += incr) {
accum += block.fn(i);
}
return accum;
}
}
});
app.engine('handlebars', handlebarsExpress.engine);
app.set('view engine', 'handlebars');
...
...
在查看文件(
sth.handlebars
)中,我具有以下内容:<div class="row columns large-12">
{{#for 0 23 1}}
{{#for 0 45 15}}
{{log ../this}}
<span>{{../this}}:{{this}}</span><br>
{{/for}}
{{/for}}
</div>
html输出是这样的:
[object Object]:0
0:15
0:30
0:45
1:0
...
...
13:45
14:0
14:15
14:30
14:45
15:0
[object Object]:15
15:30
15:45
16:0
16:15
log
帮助器在终端中报告以下内容:{ settings:
{ 'x-powered-by': true,
etag: 'weak',
'etag fn': [Function: wetag],
env: 'development',
'query parser': 'extended',
'query parser fn': [Function: parseExtendedQueryString],
'subdomain offset': 2,
'trust proxy': false,
'trust proxy fn': [Function: trustNone],
view: [Function: View],
views: '/home/elsa/Projects/rental-borg/project/views',
'jsonp callback name': 'callback',
'view engine': 'handlebars',
port: 8765 },
flash: { info: undefined, error: undefined },
_locals: { flash: { info: undefined, error: undefined } },
cache: false }
0
0
0
1
1
1
1
2
2
2
2
3
3
3
3
4
...
...
13
13
13
13
14
14
14
14
15
{ settings:
{ 'x-powered-by': true,
etag: 'weak',
'etag fn': [Function: wetag],
env: 'development',
'query parser': 'extended',
'query parser fn': [Function: parseExtendedQueryString],
'subdomain offset': 2,
'trust proxy': false,
'trust proxy fn': [Function: trustNone],
view: [Function: View],
views: '/home/elsa/Projects/rental-borg/project/views',
'jsonp callback name': 'callback',
'view engine': 'handlebars',
port: 8765 },
flash: { info: undefined, error: undefined },
_locals: { flash: { info: undefined, error: undefined } },
cache: false }
15
15
16
16
16
16
...
23
23
23
23
很明显,当
this
和../this
相等时,我想../this
实际上会返回类似../../this
的视图对象本身。在我的特定情况下,这两次发生的时间分别是00:00和15:15。这是一个错误吗?
编辑:我用把手3的新块参数功能解决了问题,但最初的问题仍然存在。这是我为解决问题所做的:
.handlebars
文件:{{#for 0 23 1 as |hour|}}
{{#for 0 45 15 as |minute|}}
<span>{{hour}}:{{minute}}</span>
{{/for}}
{{/for}}
和助手:
for: function(from, to, incr, block) {
var args = [], options = arguments[arguments.length - 1];
for (var i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
var accum = '';
for(var i = from; i <= to; i += incr) {
accum += options.fn(i, {data: options.data, blockParams: [i]});
}
return accum;
},
最佳答案
这是一个有趣的发现,我真的很想找到这种看似奇怪的行为的原因。我对Handlebars source code进行了深入研究,发现在line 176 of lib/handlebars/runtime.js上可以找到相关的代码。
Handlebars维护一堆上下文对象,其顶部对象是当前正在执行的Handlebars模板块中的{{this}}
引用。当执行模板中遇到嵌套块时,会将新对象推入堆栈。这就是允许如下代码的方式:
{{#each this.list}}
{{this}}
{{/each}}
在第一行,
this
是一个具有名为“ list”的可枚举属性的对象。在第二行,this
是list
对象的当前迭代项。正如问题所指出的那样,Handlebars允许我们使用../
访问堆栈中较深的上下文对象。在上面的示例中,如果要从list
帮助器中访问#each
对象,则必须使用{{../this.list}}
。有了这个简短的摘要,问题仍然存在:当外部
for
循环的当前迭代的值等于内部for
循环的当前迭代的值时,为什么我们的上下文堆栈似乎中断了?来自Handlebars源的relevant code是以下内容:
let currentDepths = depths;
if (depths && context != depths[0]) {
currentDepths = [context].concat(depths);
}
depths
是上下文对象的内部堆栈,context
是传递给当前正在执行的块的对象,并且currentDepths
是可供执行块使用的上下文堆栈。如您所见,仅当context
不是loosely equal到堆栈的当前顶部context
时,当前的depths[0]
才被压入可用堆栈。让我们将此逻辑应用于问题中的代码。
当外部
#for
块的上下文是15
并且内部#for
块的上下文是0
时:depths
是:[15, {ROOT_OBJECT}]
(其中{ROOT_OBJECT}表示对象,它是模板方法调用的参数。)
因为
0 != 15
,currentDepths
变为:[0, 15, {ROOT_OBJECT}]
。在模板的内部
#for
块中,{{this}}
是
0
,{{../this}}
是15
并且{{../../this}}
是{ROOT_OBJECT}。但是,当外部和内部
#for
块的上下文值分别为15
时,我们得到以下信息:depths
是:[15, {ROOT_OBJECT}]
。因为
15 == 15
,currentDepths = depths = [15, {ROOT_OBJECT}]
。在模板的内部
#for
块中,{{this}}
是15
,{{../this}}
是{ROOT_OBJECT},而{{../../this}}
是undefined
。这就是为什么当外部和内部
{{../this}}
块具有相同值时,您的#for
似乎跳过一个级别的原因。实际上是因为内部#for
的值未推送到上下文堆栈!在这一点上,我们应该问为什么把手具有这种行为并确定这是功能还是错误。
碰巧的是,有意添加了此代码来解决用户使用车把时遇到的issue问题。该问题可以通过示例来证明:
假设一个上下文对象:
{
config: {
showName: true
},
name: 'John Doe'
}
用户发现以下用例违反直觉:
{{#with config}}
{{#if showName}}
{{../../name}}
{{/if}}
{{/with}}
特定的问题是必须使用双
../
来访问根对象:{{../../name}}
而不是{{../name}}
。用户认为,由于{{#if showName}}
中的上下文对象是config
对象,因此升一级../
应该访问config
的“父级”-根对象。之所以需要两个步骤,是因为Handlebars正在为每个块助手创建一个上下文堆栈对象。这意味着到达根上下文需要两个步骤。第一步获取{{#with config}}
的上下文,第二步获取根的上下文。做了commit可以防止当新的上下文对象与上下文堆栈顶部的对象松散地相等时,将上下文对象推送到可用的上下文堆栈。负责的代码是我们在上面查看的源代码。从Handlebars.js的version 4.0.0开始,我们的配置示例将失败。现在只需要一个
../
步骤。回到原始问题中的代码示例,将外部
15
块中的#for
确定为等于内部15
块中的#for
的原因是由于在JavaScript;如果两个数字类型的对象的值相同,则它们相等。这与“对象”类型相反,“对象”类型仅当两个对象引用内存中的同一对象时才相等。这意味着,如果我们重新编写原始示例代码以将Object类型而不是Number类型用于上下文,那么我们将永远不会满足条件比较语句,并且始终在内部#for
块中拥有预期的上下文堆栈。我们的助手中的for循环将被更新,以将Object类型作为其创建的框架的上下文传递:
for(var i = from; i <= to; i += incr) {
accum += block.fn({ value: i });
}
现在,我们的模板将需要访问该对象的相关属性:
{{log ../this.value}}
<span>{{../this.value}}:{{this.value}}</span><br>
通过这些编辑,您应该发现您的代码按预期执行。
声明这是否是Handlebars的错误有点主观。有条件地添加了条件,并且所产生的行为执行了预期的操作。但是,我很难想象当涉及的上下文是基元而不是对象类型时,这种行为是预期的还是期望的。使Handlebars代码仅对Object类型进行比较可能是合理的。我认为open an issue这里有一个合理的案例。