../this returns 当父子具有相同的值时,内部循环中的视图对象
../this returns the view object inside inner loop when the parent and child have the same value
我刚从 handlebars
开始,我正在尝试做一个简单的双 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
实际上 returns 类似于 ../../this
的东西,它是视图对象本身,我认为。在我的具体情况下,这种情况发生了两次,在 00:00 和 15:15.
上
这是一个错误吗?
编辑:我用 Handlebars 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 and I found the relevant code was to be found on line 176 of lib/handlebars/runtime.js.
Handlebars 维护一堆上下文对象,其顶部对象是当前正在执行的 Handlebars 模板块中的 {{this}}
引用。当在执行模板中遇到嵌套块时,一个新对象被压入堆栈。这就是允许如下代码的原因:
{{#each this.list}}
{{this}}
{{/each}}
在第一行,this
是一个带有可枚举 属性 的对象,名为 "list"。在第二行,this
是 list
对象的当前迭代项。正如问题所指出的,Handlebars 允许我们使用 ../
来访问堆栈中更深的上下文对象。在上面的示例中,如果我们想从 #each
帮助程序中访问 list
对象,我们将不得不使用 {{../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
.
这就是为什么当外部和内部 #for
块具有相同值时,您的 {{../this}}
似乎跳过了一个级别。其实是因为里面#for
的值没有push到上下文栈!
正是在这一点上,我们应该问为什么 Handlebars 会这样,并确定这是一个功能还是一个错误。
碰巧有意添加此代码以解决用户在使用 Handlebars 时遇到的 issue。这个问题可以通过例子来证明:
假设上下文对象为:
{
config: {
showName: true
},
name: 'John Doe'
}
用户发现以下用例违反直觉:
{{#with config}}
{{#if showName}}
{{../../name}}
{{/if}}
{{/with}}
具体问题是双 ../
访问根对象的必要性:{{../../name}}
而不是 {{../name}}
。用户觉得既然{{#if showName}}
里面的context对象是config
对象,那么往上一层../
应该访问config
的"parent"——根对象。之所以需要两个步骤,是因为 Handlebars 正在为每个 块助手创建一个上下文堆栈对象 。这意味着需要两个步骤才能到达根上下文;第一步获取{{#with config}}
的上下文,第二步获取根
的上下文
A commit was made that prevents the pushing of a context object to the available context stack when the new context object is loosely equal to the object at the top of the context stack. The responsible code is the source code we looked at above. As of version 4.0.0 of Handlebars.js,我们的配置示例将失败。它现在只需要一个 ../
步骤。
回到原始问题中的代码示例,将外部 #for
块中的 15
确定为等于内部 [=] 中的 15
的原因32=] 块是由于 JavaScript 中数字类型的比较方式;如果两个 Number 类型的对象都具有相同的 value,则它们是相等的。这与 Object 类型相反,对于后者,只有当它们 reference 内存中的同一对象时,两个对象才相等。这意味着如果我们重写原始示例代码以使用 Object 类型而不是 Number 类型作为上下文,那么我们将永远不会满足条件比较语句并且我们将 always 得到预期的结果我们内部 #for
块中的上下文堆栈。
我们的助手中的 for 循环将更新为传递对象类型作为它创建的框架的上下文:
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 代码对对象类型 仅 进行比较可能是合理的。我认为 open an issue.
这里有一个合理的案例
我刚从 handlebars
开始,我正在尝试做一个简单的双 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
实际上 returns 类似于 ../../this
的东西,它是视图对象本身,我认为。在我的具体情况下,这种情况发生了两次,在 00:00 和 15:15.
这是一个错误吗?
编辑:我用 Handlebars 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 and I found the relevant code was to be found on line 176 of lib/handlebars/runtime.js.
Handlebars 维护一堆上下文对象,其顶部对象是当前正在执行的 Handlebars 模板块中的 {{this}}
引用。当在执行模板中遇到嵌套块时,一个新对象被压入堆栈。这就是允许如下代码的原因:
{{#each this.list}}
{{this}}
{{/each}}
在第一行,this
是一个带有可枚举 属性 的对象,名为 "list"。在第二行,this
是 list
对象的当前迭代项。正如问题所指出的,Handlebars 允许我们使用 ../
来访问堆栈中更深的上下文对象。在上面的示例中,如果我们想从 #each
帮助程序中访问 list
对象,我们将不得不使用 {{../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
.
这就是为什么当外部和内部 #for
块具有相同值时,您的 {{../this}}
似乎跳过了一个级别。其实是因为里面#for
的值没有push到上下文栈!
正是在这一点上,我们应该问为什么 Handlebars 会这样,并确定这是一个功能还是一个错误。
碰巧有意添加此代码以解决用户在使用 Handlebars 时遇到的 issue。这个问题可以通过例子来证明:
假设上下文对象为:
{
config: {
showName: true
},
name: 'John Doe'
}
用户发现以下用例违反直觉:
{{#with config}}
{{#if showName}}
{{../../name}}
{{/if}}
{{/with}}
具体问题是双 ../
访问根对象的必要性:{{../../name}}
而不是 {{../name}}
。用户觉得既然{{#if showName}}
里面的context对象是config
对象,那么往上一层../
应该访问config
的"parent"——根对象。之所以需要两个步骤,是因为 Handlebars 正在为每个 块助手创建一个上下文堆栈对象 。这意味着需要两个步骤才能到达根上下文;第一步获取{{#with config}}
的上下文,第二步获取根
A commit was made that prevents the pushing of a context object to the available context stack when the new context object is loosely equal to the object at the top of the context stack. The responsible code is the source code we looked at above. As of version 4.0.0 of Handlebars.js,我们的配置示例将失败。它现在只需要一个 ../
步骤。
回到原始问题中的代码示例,将外部 #for
块中的 15
确定为等于内部 [=] 中的 15
的原因32=] 块是由于 JavaScript 中数字类型的比较方式;如果两个 Number 类型的对象都具有相同的 value,则它们是相等的。这与 Object 类型相反,对于后者,只有当它们 reference 内存中的同一对象时,两个对象才相等。这意味着如果我们重写原始示例代码以使用 Object 类型而不是 Number 类型作为上下文,那么我们将永远不会满足条件比较语句并且我们将 always 得到预期的结果我们内部 #for
块中的上下文堆栈。
我们的助手中的 for 循环将更新为传递对象类型作为它创建的框架的上下文:
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 代码对对象类型 仅 进行比较可能是合理的。我认为 open an issue.
这里有一个合理的案例