CFC中的函数局部变量

4

我之前没有接触过ColdFusion,被要求调查一个ColdFusion应用程序中的一些奇怪的间歇性错误。

在阅读有关作用域的信息后,我认为问题是因为我的CFC函数中没有使用var关键字的变量,并且相同的变量名在各个函数中都被使用。因此,我理解这些变量是在页面级别上具有作用域的,调用这些函数的不同线程将覆盖该变量,从而导致“奇怪”的问题。

我的问题是,正确的方法是什么?

 <cfset var listCount = 0>
 <cfquery name="qGetElementsByType" dbtype="query" maxrows="#arguments.num_to_return#">
    SELECT elementId,
           title, PIhtml, Rerhtml,
           text, url, image, Rank, isPoll, pollId, subjectId
    FROM   arguments.element_query
    WHERE  <cfloop list="#arguments.element_type_id#" index="lcv">
               <cfif listCount GT 0>
                  OR
               </cfif>
               subjectid =  #lcv#
              <cfset listCount = listCount + 1>
           </cfloop>
</cfquery>

每次设置listCount变量时,var需要添加吗?还是只在初始声明时添加?


你正在运行哪个版本的ColdFusion? - Evik James
3
这与你的问题无关,但是上面的代码实际上不需要循环。使用cfqueryparam不仅可以简化代码,还有助于防止SQL注入攻击:例如WHERE subjectid IN (<cfqueryparam value="#arguments.element_type_id#" cfsqltype="cf_sql_integer" list="true">) - Leigh
你正在使用的版本非常重要。ColdFusion 9专门为函数引入了一个新的LOCAL作用域。 - Evik James
3个回答

10

(希望我的回答不太冗长。我认为现有的答案没有提供足够的信息,但希望我没有走得太远...)


在CF中,变量可以放置在各种作用域中(应用程序、会话、url、cgi等)。

其中一些需要明确声明才能使用(例如,会话变量必须始终被限定在作用域内),而其他一些变量可以在读取变量时自动访问(例如,通过未限定作用域的变量可以读取表单和url变量)- 在这里,有一个优先级顺序来确定哪些作用域被用于查找未限定作用域的变量。

这个优先级排序的最低作用域是variables作用域,它是适用于整个当前页面/对象实例的作用域。

当设置一个新的变量时,如果未限定作用域,它将被创建在variables作用域中。由于这是全局作用域,因此可以从同一函数的不同实例以及不同函数中访问,这会引起您所知道的问题。


要防止变量进入全局变量作用域,必须将其放置在函数的local作用域中。(从技术上讲,您可以将其放置在函数的arguments作用域中,但这可能会使人们感到困惑。)

在早期版本的CF中,没有明确访问局部作用域的方法-您需要使用var关键字以便在局部作用域内创建变量 - 一旦创建后,它将始终优先于变量作用域(无论是读取还是写入)。

使用CF9,local作用域现在是一个“真正”的作用域,可以被显式地访问,所以你可以写成<cfset local.x = 0 />而不是使用<cfset var x = 0 />。这样做的主要好处是,在创建变量时无法使用var关键字的情况下,例如在<cfquery name="local.qGetElementsByType" ...><cfloop index="local.lcv"...>中创建变量。
在每个变量首次创建时仍然只需要应用本地作用域,以防止它进入variables作用域——如果您愿意,后续的读取/更新可以取消作用域限制,就像在使用var作用域时一样。
(尽管取消作用域的变量存在其他潜在与作用域相关的问题,例如在<cfloop query="queryname">块内部,因此有些人认为您应该始终对所有变量进行作用域限制。) 总之,为了使您展示的代码安全,您需要对以下内容进行作用域限制:
  • 来自cfquery标记的qGetElementsByType
  • 来自cfloop标记的lvc
由于这些变量不是通过cfset创建的,因此最容易进行作用域限制的方法是在名称前加上local.
由于您已经对listCount变量使用了var 声明,因此不需要在同一个函数中再次进行声明,您可以选择使用<cfset local.listCount = local.listCount + 1>(或者<cfset local.listCount++ >)但这只是个人喜好,并不需要为了避免泄漏到variables作用域。< p >(附注:理想情况下,您应在#lcv#周围使用cfqueryparam标签来防止SQL注入-即使这是一个查询语句,这仍可能是一个问题,总是更好地在安全方面保持谨慎。)< /p> < p >当然,这只是这个函数-您还需要修复其他函数-而一个简单的方法是使用varscoper 工具扫描您的整个代码库并确定需要范围限定的变量。< /p>

1
谢谢,这很有帮助。作为一个专门从事.NET的背景,这个coldfusion范围需要我花一点时间来理解。所以这不会太冗长 :) - Omiron
Var scoper似乎认为我的local.varname是一个未定义的结构体。这样可以吗?如果我首先执行var local = {},显然可以解决这个问题,但这只是因为varscoper不知道我是否在使用高于9的CF版本吗? - Leeish
是的,如果你使用CF9或更高版本,那就没问题。如果你使用CF8,你需要 var local = {}。我猜在varscoper中应该有一个选项来考虑代码所属的版本。哦,我想varscoper可能也存在一些与脚本相关的问题,这可能是导致这个问题的原因。 - Peter Boughton

2

除了Evik在他的回答中提到的之外,你应该使用VAR来声明所有函数内部变量。qGetElementsByTypelcv也应该使用VAR。如果你正在使用CF9及以上版本,你可以使用LOCAL作用域来声明它们,例如:local.qGetElementsByType等。


0

我假设你的问题是关于这行代码的:

<cfset listCount = listCount + 1>

不,你在这行代码中不需要再次使用 var 关键字。

然而,如果在页面的后面你想要访问一个名为 listCount 的变量,它已经有了刚才运行的代码的值,所以你需要重新创建它。


我以为使用 var 的目的是将其限制在函数作用域内,因此如果我尝试在该函数之外访问名为“listCount”的变量,它将不存在? - Omiron
第一次创建变量时,您需要使用 var 进行声明。然后它将绑定到函数的上下文中(是的,在函数外部将不存在)。 - Peter Boughton
@Omiron,你没有将代码放在函数内部展示。你提到了一个函数,但是你没有展示这个函数。这就是为什么我无法完全回答你的问题的原因。 - Evik James
抱歉,我应该提到我粘贴的代码是一个函数的内容。 - Omiron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接