../这将返回内部循环内的视图对象,当父和子具有相同的值

我只是开始与handlebars ,我想做一个简单的双循环为了有一整天的时间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助手在terminal中报告以下内容:

 { 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实际上是返回类似../../this这是视图对象本身,我想。 在我的具体情况下,在00:00和15:15发生两次。

这是一个错误?

编辑:我解决了与Handlebars 3新的块参数function的问题,但最初的问题仍然存在。 这是我所做的解决这个问题:

.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; }, 

这是一个有趣的发现,我真的想find这种看似奇怪的行为的原因。 我做了一些挖掘Handlebars的源代码 ,我发现相关代码可以在lib / handlebars / runtime.js的第176行find 。

把手维护一堆上下文对象,其顶层对象是当前正在执行的把手模板块中的{{this}}引用。 由于在执行模板中遇到嵌套块,所以将新对象推送到堆栈。 这是什么允许代码如下所示:

 {{#each this.list}} {{this}} {{/each}} 

在第一行, this是一个名为“list”的可枚举属性的对象。 在第二行, thislist对象的当前迭代的项目。 正如问题所指出的,Handlebars允许我们使用../来访问堆栈中更深的上下文对象。 在上面的例子中,如果我们想要从#each助手中访问list对象,我们将不得不使用{{../this.list}}

有了这个简短的总结,问题依然存在:当外部for循环的当前迭代的值等于inner for循环的值时,为什么我们的上下文堆栈会出现中断?

Handlebars来源的相关代码如下:

 let currentDepths = depths; if (depths && context != depths[0]) { currentDepths = [context].concat(depths); } 

depths是上下文对象的内部堆栈, context是传递给当前正在执行的块的对象, currentDepths是可用于执行块的上下文堆栈。 正如你所看到的, 只有当 context并不松散地等于当前堆栈顶部depths[0] ,当前context才会被推送到可用堆栈。

让我们将这个逻辑应用于问题中的代码。

当外部#for块的上下文是15并且内部#for块的上下文是0

  • depths为: [15, {ROOT_OBJECT}] (其中{ROOT_OBJECT}表示作为模板方法调用参数的对象。
  • 因为0 != 15currentDepths变为: [0, 15, {ROOT_OBJECT}]
  • 在模板的内部#for块中, {{this}}0{{../this}}15{{../../this}}为{ROOT_OBJECT}。

然而,当外部内部#for块每个具有15的上下文值时,我们得到以下结果:

  • depths是: [15, {ROOT_OBJECT}]
  • 因为15 == 15currentDepths = depths = [15, {ROOT_OBJECT}]
  • 在模板的内部#for块中, {{this}}15{{../this}}是{ROOT_OBJECT}, {{../../this}}undefined

这就是为什么当外部和内部#for块具有相同的值时, {{../this}}跳过一个级别。 实际上是因为内部#for不会被推到上下文栈中!

在这一点上,我们应该问为什么把手这样做,并确定这是一个function还是一个错误。

恰巧这个代码被有意添加来解决用户在使用Handlebars时遇到的问题 。 这个问题可以通过例子来说明:

假定一个上下文对象:

 { config: { showName: true }, name: 'John Doe' } 

用户发现下面的用例是违反直觉的:

 {{#with config}} {{#if showName}} {{../../name}} {{/if}} {{/with}} 

具体问题是双../访问根对象的必要性: {{../../name}}而不是{{../name}} 。 用户觉得既然{{#if showName}}的上下文对象是config对象,那么提升一个级别../ ,应该访问config的“父母” – 根对象。 之所以需要两个步骤是因为Handlebars正在为每个块助手创build一个上下文堆栈对象。 这意味着需要两个步骤才能到达根环境; 第一步获取{{#with config}}的上下文,第二步获取根的上下文。

当新的上下文对象松散地等于上下文堆栈顶部的对象时, 提交了一个提交 ,用于阻止将上下文对象推送到可用的上下文堆栈。 负责任的代码是我们在上面看到的源代码。 从Handlebars.js 版本4.0.0开始,我们的configuration示例将失败。 现在只需要一个../步骤。

回到原始问题中的代码示例, 15外部#for块被确定为等于内部#for15的原因是由于在JavaScript中如何比较数字types; 如果Numbertypes的两个对象具有相同的值,则它们是相等的。 这与Objecttypes相反,只有当它们引用内存中的同一个对象时,两个对象才是相同的。 这意味着,如果我们重写了原始示例代码,以便使用Objecttypes而不是Numbertypes作为上下文,那么我们永远不会满足条件比较语句,并且我们始终在我们的内部#for块中拥有预期的上下文堆栈。

我们的助手中的for循环将被更新,以传递一个Objecttypes作为它创build的框架的上下文:

 for(var i = from; i <= to; i += incr) { accum += block.fn({ value: i }); } 

我们的模板现在需要访问这个对象的相关属性:

 {{log ../this.value}} <span>{{../this.value}}:{{this.value}}</span><br> 

通过这些编辑,您应该可以发现自己的代码正常运行。

宣布这是否是Handlebars的bug是有点主观的。 有条件的添加是故意的,最终的行为完成了它打算做的事情。 然而,当涉及到的上下文是基元而不是对象types时,我发现很难想象这种行为会被期望或期望的情况。 Handlebars代码只能在Objecttypes上进行比较可能是合理的。 我认为这里有一个合法的案例来解决问题 。