../这将返回内部循环内的视图对象,当父和子具有相同的值
我只是开始与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”的可枚举属性的对象。 在第二行, this
是list
对象的当前迭代的项目。 正如问题所指出的,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 != 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
。
这就是为什么当外部和内部#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
块被确定为等于内部#for
块15
的原因是由于在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上进行比较可能是合理的。 我认为这里有一个合法的案例来解决问题 。