2024 Clojure 状态调查! 中分享您的想法。

欢迎!请查看 关于 页面以了解更多关于如何使用此工具的信息。

0
ClojureScript

示例

(str #js {"toString" (fn [] "hello") "valueOf" (fn [] 42)}) ; => "42"

问题在于 ClojureScript 使用连接来将值转换为字符串,这在与覆盖了 valueOf() 方法的对象不兼容。

JavaScript 示例

`
var obj = {

toString: function() { return 'hello'; },
valueOf: function() { return 42; }

};
console.log(String(obj)); => 'hello'
console.log(obj.toString()); => 'hello'
console.log('' + obj); => '42'
`

可能的解决方案可能是使用 String() 函数。使用 toString() 不会像此问题中所描述的那样工作:http://dev.clojure.org/jira/browse/CLJS-847

35 答案

0

由 dnolen 发表的评论

我们在 master 中切换到 {{goog.string.buildString}} https://github.com/clojure/clojurescript/commit/94eb8a960fef6aaca4ba44b251cefbfa04d0f6ac

0

由 nbeloglazov 发表的评论

是的,这有效。太棒了,谢谢!

0

由 thheller 发表的评论

我为重新打开这个话题道歉。

我正在对我的代码进行性能分析,并注意到了关于 cljs.core/str 的性能警告。

Chromes 抱怨:"未优化。参数值范围对于 Argentina 不良",进一步查看 goog.string.buildString 的实现。

goog.string.buildString = function(var_args) { return Array.prototype.join.call(arguments, ''); };

考虑到我们从不使用超过一个参数调用它,这可能不是最佳实现选择。

可能可以跳过调用并内联,例如:

`
(defn str
"如果没有参数,则返回空字符串。有一个参数 x,则返回
x.toString()。(str nil)返回空字符串。如果有多个
参数,则返回参数中字符串值的连接。"
([] "")
([x] (if (nil? x)

     ""
     (.join #js [x] "")))

([x & ys]

(loop [sb (StringBuffer. (str x)) more ys]
  (if more
    (recur (. sb  (append (str (first more)))) (next more))
    (.toString sb)))))

`

我没有跟进这个问题,但为什么我们不使用 .toString?buildString/数组的做法看起来有点黑客式?

我不太确定整体影响,但自从 cljs.core/str 出现在我个人资料的较高位置,我认为应该进一步调查。

0

由 thheller 发表的评论

在此之前

;;; str [], (str "1"), 1000000 运行,254 毫秒 [], (str 1), 1000000 运行,266 毫秒 [], (str nil), 1000000 运行,80 毫秒 [], (str "1" "2" "3"), 1000000 运行,753 毫秒

之后

;;; str [], (str "1"), 1000000 运行,82 毫秒 [], (str 1), 1000000 运行,86 毫秒 [], (str nil), 1000000 运行,79 毫秒 [], (str "1" "2" "3"), 1000000 运行,242 毫秒

但我只测试了 V8,可能需要验证。

0

由 thheller 发表的评论

`
(defn str
"如果没有参数,则返回空字符串。有一个参数 x,则返回
x.toString()。(str nil)返回空字符串。如果有多个
参数,则返回参数中字符串值的连接。"
([] "")
([x1]

 (.join #js [x1] ""))

([x1 x2]

 (.join #js [x1 x2] ""))

([x1 x2 x3]

 (.join #js [x1 x2 x3] ""))

([x1 x2 x3 x4]

 (.join #js [x1 x2 x3 x4] ""))

...)
`

性能甚至在更优。

;;; str [], (str "1"), 1000000 运行,40 毫秒 [], (str 1), 1000000 运行,43 毫秒 [], (str nil), 1000000 运行,96 毫秒 [], (str "1" "2" "3"), 1000000 运行,117 毫秒

应该内联多少个参数?

0

由 dnolen 发表的评论

我同意最多 4 个参数然后变为可变参数。

0

由 thheller 发表的评论

在 cljs.core/str 宏和函数生成的代码之间存在一些奇怪的交互。

宏生成:

`
(str "hello" 1 "world" :yo nil)

产生:

[cljs.core.str("hello"),cljs.core.str((1)),cljs.core.str("world"),cljs.core.str(new cljs.core.Keyword(null,"yo","yo",1207083126)),cljs.core.str(null)].join('');
`

考虑到 str 有一个参数将基本上展开为:

[["hello"].join(""), ...]

我认为可以安全地完全移除这个宏,因为在这种情况下, cljs.core/str 也会做同样的事情,JIT可能会足够智能地处理这个问题(甚至在编译时 Closure 也可能会这么做)。

0
评论者:favila_

bq. Chrome 抱怨:"优化失败。参数值上下文错误",进一步查看 goog.string.buildString 的实现。

Chrome 对于 variadic 函数分发代码的抱怨方式相同,参见 CLJS-916 以及补丁。

bq. 我认为可以安全地完全移除这个宏,因为在这种情况下, cljs.core/str 也会做同样的事情,JIT可能会足够智能地处理这个问题(甚至在编译时 Closure 也可能会这么做)。

Closure 编译器还不够聪明,不能移除中间数组,这就是为什么我提出了 CLJS-801(该问题已撤销)。我认为 JIT 的也不能这样做。

我开始怀疑我们是否应该忽略 CLJS-847 中的 Safari 6.0.5 问题,这引发了所有的字符串混乱。简而言之

# CLJS-801 已接受,它在 str 宏的情况下从 {{\[123, x].join('')}} 移除,并使用 {{''\+123\+(cljs.core/str$arity$1 x)}} 风格代码, Closure 编译器可以预计算。此时,一参 cljs.core/str 函数(不是宏)对其参数调用 toString。
# CLJS-847 已提交。在 Safari 6.0.5 的高 JIT 级别,对一些事物(可能是未装箱的数字?绝对不是字符串)调用 toString 会抛出 TypeError。这无疑是 Safari 中的一个 bug。David 修复了这一点,让一参 cljs.core/str 函数调用 js-str 而不是 toString。js-str 使用字符串连接 {{''+x}}。
# 然而,对于定义了 valueOf 的对象(当前问题中的问题),它失败了,因为在这种情况下,js {{''+x}} 与 {{''+x.valueOf().toString()}} 相同,而不是 {{''+x.toString()}}。
# David 考虑使用 {{String()}} 和变体,但因为没有性能损失而予以拒绝。
# David 将 CLJS-801 从字符串连接回数组连接风格以修复问题。
# Nikita 和我指出,撤回 CLJS-801 只修复了 str 宏,而没有修复字符串函数,该函数仍然使用 {{js-str}} 和因此字符串连接。
# David 将 str 函数修复为使用 goog.string.buildString,它具有 array.join 的行为。现在即使是在 Safari 6.0.5 上,行为也是正确的。
# Thomas 指出,buildString 以一种 v8 无法优化的方式使用参数,因此,str 函数(非宏)出现了性能退步。他建议直接使用 {{\[].join()}}。

所以,在这件事上有很多来回讨论,这一切都源于 Safari 6.0.5 中的一个 bug,没有人能够第一次手接触到它,因为 Safari 6.0.5 已经很旧了且很少见。为了有一点视角,Safari 6.0.x 只在 2012 年 7 月 25 日到 2013 年 6 月 11 日期间在 Lion 和 Mountain Lion 上可用。在 2012 年 7 月 25 日之前,Lion 使用 Safari 5.1.x,且没有 Mountain Lion。在 2013 年 6 月 11 日,Lion 和 Mountain Lion 都切换到 Safari 6.1.x,它没有遭受 toString TypeError bug(我检查过--我有一个内置 Lion 的 iMac)。在 Safari 6.0.5 上唯一的是(Mountain)Lion 机器,它们使用了软件更新直到 2012 年中后期,然后停止了。我想这不会是很多人。

理论上我可以在我的 Lion 机器上运行 Safari 6.0.x 来实际测试这个问题,但我找不到从 6.1.x 回退的方法。

我认为选择是
# 对于所有字符串化使用 array.join() 并接受性能损失(我们应该量化它)。包括一个注释说明这是仅为 Safari 6.0.x(只在 6.0.4 和 6.0.5 间接证实)的,留给未来 generations,他们可能会认为这很奇怪。
使用 CLJS-801 和 toString (CLJS-847 之前的状态),忽略 Safari 6.0.x 的这个问题。
使用 CLJS-801,但为 Safari 6.0.5 的 cljs.core/str$arity$1 添加一个 {{number?}} 检查(带有注释)。数字情况应使用 js-str,其余使用 toString。我认为这将有效,但同样我们没有测试的方法——我们真的需要一台 Safari 6.0.x 浏览器。

当然,我们应该对这些方法进行基准测试,但我的直觉是 2 比 3 比 1 快。
0

由 dnolen 发表的评论

我们不会忽略 Safari 6.0.X。关于这个票据做出的任何决定都将包括对其的支持。

0
评论者:favila_

关于我对这个问题的研究的一些更新。

我创建了 [jsperf 上替代 str 实现的测试|http://jsperf.com/cljs-core-str-possibilities],我正在尝试。现在我只看了单个参数的 str。我发现了一些事情

* {{''+[x]}} 是 {{[x].join('')}} 的更快的替代方案。
* 高级编译可以在编译时计算 {{''+[x]}},即使 {{x}} 是 bool、str、undefined、null 或数字,甚至通过函数调用!即 str_test.str_arr(123) 编译为 "123" 而不是宏魔法。
然而,使用中间数组(即使是一个预分配的单例)仍然比旧的 {{(if (nil? x) "" (.toString x))}} 慢。
使用 switch 语句至少与 str-tostr 基准测试一样快,通常更快。
我 99% 确信所有这些实现(除了 str-tostr 基准测试,它肯定失败)都在可怕的 Safari 6.0.x 上工作。如果任何人有这个版本,请将其指向上面的 jsperf 链接并运行测试。我认为 [Browserstack|http://www.browserstack.com] 有这个版本的 Safari。

我仍在研究可变参数情况(str x y z a b c)。可能最好使用 reduce 而不是 Stringbuffer+seq。(Stringbuffer 现在只是 {{''+x}},而不是数组连接。)
0

由 thheller 发表的评论

对不起,我了一会儿分心。

@Francis: 谢谢总结。

我也没有 Safari 6,但是看起来我们所有人都必须因为这个只有小部分人还在使用它(我的网站上 190k+ 用户中的 667 个)而忍受。我没有解决方案,因为我不能测试是否有效,我们可能尝试 String.concat。

"".concat(obj); // "42" "".concat(obj, ""); // "hello" String.prototype.concat(obj, "") // "hello" String.prototype.concat("", obj) // "hello"

但不知道 String.concat 是否有效,它关于 valueOf 的行为也很奇怪。

http://jsperf.com/js-string-concat-variants

性能测试结果也不确定,因为Firefox看起来像是作弊。

0

评论者:favila

使用Browserstack通过Safari 6.0.5进行了jsperf测试,结果已提供。

注意:我无法重现CLJS-847,因为str-tostr未按预期失败。既然我现在有一个浏览器可以测试,我将更加努力。

0

评论者:favila

仍然无法重现CLJS-847。

(链接: https://dl.dropboxusercontent.com/u/18386249/code/strtest.html 文本: 这个脚本) 包含了我尝试的最小可重复案例。我的理论是在更高的JIT级别上,某些类型会失败。我没有得到任何失败的案例。我还尝试了在类型之间交替使用,以及一次只使用一种类型,但仍然没有失败。

在 (链接: https://github.com/wycats/handlebars.js/pull/440 文本: 这个帖子) 我找到了 (链接: https://dl.dropboxusercontent.com/u/18386249/code/handlebar_str_typeerror.html 文本: 这个“最小化”脚本) ,发帖者说可以可靠地让它失败。我无法让它失败。然而原文帖子是从2013年2月15日发布的,这意味着他使用的Safari版本必须是6.0.2或更低。

假设

  1. 这个错误不影响6.0.5,但可能影响6.0.4或更低版本。
  2. BrowserStack的系统以某种方式缓解了这个错误,这意味着我们需要一个“真实的”Lion Safari 6.0.x来进行测试。
  3. 这些测试只在正确的月相下失败。

因此,我可以使用str-switch实现来编写修复str的补丁(这至少在某些浏览器上要快一些),但我不确定它会不会在Safari 6.0.5上失败。我只知道到目前为止它正常工作。CLJS-801也可以安全地重新应用,因为所有问题的根源都是cljs.core/str函数的实现1-arity。

我还在CLJS-847中请求了Kevin的帮助。(Kevin是Safari 6.0.x问题的原始报告者。)

0

评论者:favila

制作了 (链接: http://jsperf.com/cljs-core-str-variadic-possibilities 文本: 可变参数案例) 的jsperf。Chrome似乎真的更喜欢对于向量(未测试其他集合)使用IReduce与seq+stringbuilder。但是,在其它浏览器上没有差异,或者只是轻微的速度下降。不确定这是否值得。

还更新了 (链接: http://jsperf.com/cljs-core-str-possibilities/2 文本: 一元案例) ,使用带switch的str从不使用toString。Chrome上使用switch或toString大约慢了50%,在Safari上更少。

在安全性方面,str-switch-notostr完全不使用toString方法,因此可能更安全。不过,我认为str-switch也可能工作,而且速度要快得多。然而,我无法在Safari 6.0.5中得到任何TypeError,所以这是未知数。

我建议把这样的东西作为一个新的str(它不使用reduce,但可以):

`
(defn str
([x]
(case (js "typeof ~{}" x)
"string" x
"object" (if (identical? nil x) "" (.toString x))
("boolean" "number") (js-str x)
"undefined" ""
(js-str #js [x]))) ;; 针对Safari 6.0.x TypeError错误的保险。
([a b] (js
"~{}+~{}" (str a) (str b)))
([a b c] (js "~{}+~{}+~{}" (str a) (str b) (str c)))
([a b c d] (js
"~{}+~{}+~{}+~{}" (str a) (str b) (str c) (str d)))
([a b c d & more]
(loop [s (str a b c d) [e f g h & r] more]
(let [s' (js* "~{}+~{}+~{}+~{}+~{}" s e f g h)]

(if (nil? r)
 s'
 (recur s' r))))))

`

0

评论者:favila

这个可能的补丁的第一稿可以解决这个问题,同时不会破坏CLJS-847。应等待确认这不会破坏Safari 6.0.x。

...