../这将返回内部循环内的视图对象,当父和子具有相同的值 [英] ../this returns the view object inside inner loop when the parent and child have the same value

查看:67
本文介绍了../这将返回内部循环内的视图对象,当父和子具有相同的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我只是以 handlebars 开头,我正在尝试做一个简单的double for循环,以便每天以15分钟的时间间隔完成所有工作。一个非常奇怪的事情发生在如果孩子和父母具有相同值的地方,视图对象被返回。这是我的代码:

  var handlebarsExpress = require('express-handlebars')。create({
helpers: {
for:(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');

...
...

在视图

 < 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 helper在终端中报告以下内容:

  {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':[功能: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 b b 2 b b 2 b b 2 b b 3 b b 3 b b 3 b b 3 b b 4 b b b ...
...
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':'
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 ,这是视图对象本身,我想。这在我的具体情况下会发生两次,在00:00和15:15。



这是一个错误吗?



编辑:我用Handlebars 3的新块参数功能解决了问题,但最初的问题仍然存在。这是我解决这个问题的方法:

.handlebars file:

  {{#for 0 23 1 as | hour |}} 
{{#for 0 45 15 as | minute |}}
<跨度> {{小时}}:{{分钟}}< /跨度>
{{/ for}}
{{/ for}}

和帮助者:
$ b $ pre $ 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]}) ;
}
返回累积;
},


解决方案

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

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

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

第一行这个是一个具有名为list的可枚举属性的对象。在第二行中,这个 list 对象的当前迭代项。正如问题所指出的那样,Handlebars允许我们使用 ../ 来访问堆栈中更深的上下文对象。在上面的例子中,如果我们想从 #each 帮助程序中访问 list 对象,使用 {{../ this.list}}



通过这个简短的总结,问题依然存在:为什么当循环的外部循环的当前迭代值等于内部循环的当前迭代值时,为循环?



相关代码来自Handlebars源代码如下:

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

深度是内部堆栈的上下文对象, context 是传递给当前正在执行的块的对象, currentDepths 是上下文堆栈可用于执行块。如您所见,只有在 context 时,才会将当前上下文推送到可用堆栈是不是宽松地等于到当前堆栈顶端, depths [0]



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

当外部 #for 块的上下文为 15 且上下文内部 #for 区块为 0




  • depths 是: [15,{ROOT_OBJECT}] (其中{ROOT_OBJECT}表示
    对象是模板方法调用的参数。)
  • 因为 0!= 15 currentDepths 变成: [0,15,{ROOT_OBJECT}]

  • c $ c> #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}} 跳过一个级别$ c>块具有相同的值。这实际上是因为内部 #for 的值不会被推送到上下文堆栈中!


$ b $在这一点上,我们应该问为什么Handlebars以这种方式行事,并确定这是一个功能还是一个bug。



碰巧代码被有意添加来解决用户使用Handlebars时遇到的问题。例如:

假设上下文对象为:

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






$ b

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

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

具体问题是双<$ code> ../ 来访问根对象: {{../../ name}} 而不是 {{名称../}} 。用户认为,由于 {{#if showName}} 中的上下文对象是 config 对象,因此提升一个级别 ../ 应该访问 config - 根对象的父级。之所以需要两个步骤是因为Handlebars为每个块助手创建一个上下文堆栈对象。这意味着需要两个步骤才能到达根环境;第一步获取 {{#with config}} 的上下文,第二步获取根的上下文。



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



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



我们的帮助程序中的for循环将更新为传递Object类型它创建的框架的上下文:

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

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

  {{log ../this.value}} 
< span> {{../ this.value }}:{{THIS.VALUE}}< /跨度><峰; br>

通过这些编辑,您可以发现自己的代码能够按照您的预期执行。



宣布这是否是Handlebars的bug是有点主观的。条件性是有意添加的,而最终的行为完成了它打算做的事情。然而,当涉及的上下文是基元和不是对象类型时,我发现很难想象这种情况会出现这种行为。 Handlebars代码可以用来比较对象类型 。我认为这里有一个合法的案例来开放问题


I am just starting with handlebars and I am trying to do a simple double for loop in order to have all the day's time with 15 minute intervals. A very weird thing is happening where if the child and parent have the same values, the view object is being returned instead. This is my code:

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');

...
...

In the view file (sth.handlebars) i have this:

<div class="row columns large-12">
    {{#for 0 23 1}}
        {{#for 0 45 15}}
            {{log ../this}}
            <span>{{../this}}:{{this}}</span><br>
        {{/for}}
    {{/for}}
</div>

The html output is something like this:

[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

The log helper reports the following in the 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

It is apparent that when this and ../this are equal, ../this actually returns something like ../../this which is the view object itself, I suppose. This happens twice in my specific case, on 00:00 and on 15:15.

Is this a bug?

EDIT: I solved the problem with the new Block Parameters feature of Handlebars 3, but the initial question remains. This is what I did to solve the problem:

.handlebars file:

{{#for 0 23 1 as |hour|}}
    {{#for 0 45 15 as |minute|}}
        <span>{{hour}}:{{minute}}</span>
    {{/for}}
{{/for}}

and the helper:

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

解决方案

This was an intriguing discovery and I really wanted to find the reason for such seemingly strange behavior. I did some digging into the Handlebars source code and I found the relevant code was to be found on line 176 of lib/handlebars/runtime.js.

Handlebars maintains a stack of context objects, the top object of which is the {{this}} reference within the currently executing Handlebars template block. As nested blocks are encountered in the executing template a new object is pushed to the stack. This is what permits code like the following:

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

On the first line, this is an object with an enumerable property called "list". On line two, this is the currently iterated item of the list object. As the question points out, Handlebars allows us to use ../ to access context objects deeper in the stack. In the example above, if we want to access the list object from within the #each helper, we would have had to use {{../this.list}}.

With this brief summary out of the way, the question remains: Why does our context stack appear to break when the value of the current iteration of the outer for loop is equal to that of the inner for loop?

The relevant code from the Handlebars source is the following:

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

depths is the internal stack of context objects, context is the object passed to the currently executing block, and currentDepths is the context stack that is made available to the executing block. As you can see, the current context is pushed to the available stack only if context is not loosely equal to the current top of the stack, depths[0].

Let's apply this logic to the code in the question.

When the context of the outer #for block is 15 and the context of the inner #for block is 0:

  • depths is: [15, {ROOT_OBJECT}] (Where {ROOT_OBJECT} means the object that was the argument to the template method call.)
  • Becuase 0 != 15, currentDepths becomes: [0, 15, {ROOT_OBJECT}].
  • Within the inner #for block of the template, {{this}} is 0, {{../this}} is 15 and {{../../this}} is {ROOT_OBJECT}.

However, when the outer and inner #for blocks each have a context value of 15, we get the following:

  • depths is: [15, {ROOT_OBJECT}].
  • Because 15 == 15, currentDepths = depths = [15, {ROOT_OBJECT}].
  • Within the inner #for block of the template, {{this}} is 15, {{../this}} is {ROOT_OBJECT}, and {{../../this}} is undefined.

This is why it appears that your {{../this}} skips a level when the outer and inner #for blocks have the same value. It is actually because the value of the inner #for is not pushed to the context stack!

It is at this point that we should ask why Handlebars behaves this way and to determine whether this is a feature or a bug.

It so happens that this code was added intentionally to solve an issue that users were experiencing with Handlebars. The issue can be demonstrated by way of example:

Assuming a context object of:

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

Users found the following use case to be counter-intuitive:

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

The specific issue was with the necessity for the double ../ to access the root object: {{../../name}} rather than {{../name}}. Users felt that since the context object within {{#if showName}} was the config object, then stepping-up one level, ../, should access the "parent" of config - the root object. The reason that two steps were necessary was because Handlebars was creating a context stack object for each block helper. This means that two steps are required to get to the root context; the first step gets the context of {{#with config}}, and the second step gets the context of the root.

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, our config example will fail. It now requires only a single ../ step.

Getting back to the code example in the original question, the reason that the 15 from the outer #for block is determined as equal to the 15 in the inner #for block is due to how number types are compared in JavaScript; two objects of the Number type are equal if they each have the same value. This is in contrast to the Object type, for which two objects are equal only if they reference the same object in memory. This means that if we re-wrote the original example code to use Object types instead of Number types for the contexts, then we would never meet the conditional comparison statement and we would always have the expected context stack within our inner #for block.

The for loop in our helper would be updated to pass an Object type as the context of the frame it creates:

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

And our template would now need to access the relevant property of this object:

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

With these edits, you should find your code performing as you expected.

It's somewhat subjective to declare whether or not this is a bug with Handlebars. The conditional was added intentionally and the resultant behavior does what it was intended to do. However, I find it hard to imagine a case in which this behavior would be expected or desirable when the contexts involved are primitives and not Object types. It might be reasonable for the Handlebars code to be made to do the comparison on Object types only. I think there is a legitimate case here to open an issue.

这篇关于../这将返回内部循环内的视图对象,当父和子具有相同的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆