请在 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 的警告。

Chrome 抱怨:"未优化。参数值上下文不正确",进一步查看 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/数组的做法看起来有些 hackish?

我对整体影响不太确定,但鉴于cljs.core/str在我的配置文件中出现得相当频繁,我认为应该进一步调查。

0 投票
by

由: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 投票
by

由: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 投票
by

由:dnolen 发表的评论

我至少可以接受前4个参数后进行可变参数调用。

0 投票
by

由: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('');
`

鉴于只有1个参数的str实际上会展开为

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

我认为可以完全删除宏,因为cljs.core/str会做同样的事情,JIT可能足够智能以解决这个问题(或者 Closure 在编译时)。

0 投票
评论者:favila_

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

Chrome 对同一个变长函数分发代码也有类似的抱怨,参见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(可能是仅-unboxed数字?肯定不是字符串)会抛出TypeError。这是Safari中无疑的一个bug。大卫通过使单参数 cljs.core/str 函数调用js-str而不是toString来修复它。js-str使用字符串连接 {{''+x}}。
# 然而,这对定义valueOf的对象会出问题(当前补丁中的问题),因为在js中{{''+x}}等同于{{''+x.valueOf().toString()}}而不是{{''+x.toString()}}。
# 大卫考虑使用{{String()}}及其变体,但因其性能影响而拒绝。
# 大卫将 CLJS-801 从字符串连接撤回到数组连接样式以修复。
# Nikita和我指出,撤回 CLJS-801 只修复了 str *宏*,而不是字符串 *函数*,后者仍然使用{{js-str}}和因此字符串连接。
# 大卫修复了 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 上只运行在(Lion)机器上,这些机器使用软件更新直到 2012 年晚些时候和 2013 年初,然后停止。我想这不会是很多人。

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

我认为选择有
# 为所有字符串化使用 array.join() 并承担性能损失(我们应该量化这一点)。包括一个注释,指出这只是为了 Safari 6.0.x(仅在 6.0.4 和 6.0.5 上经二手消息证实)供后人了解,他们可能会觉得这很奇怪。
# 使用 CLJS-801 和 toString(CLJS-847 之前的现状),并忽略 Safari 6.0.x 中的这个问题。
使用CLJS-801,但为 Safari 6.0.5 增加一个 {{number?}} 检查(有注释)到 {{cljs.core/str$arity$1}}。数字情况应该使用 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 或 number,即使在函数调用中!例如,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的系统以某种方式缓解了这个 bug,这意味着我们需要一个“真实的”Lion Safari 6.0.x版本来进行测试。
  3. 这些测试只在满月的正确阶段会失败。

所以我可以为str编写一个补丁,使用str-switch实现(这至少在有些浏览器上稍快一些),但我不知道它是否可能在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似乎是 vector(未测试其他集合)上 IReduce+seq+stringbuilder 的真偏好,但在其他浏览器中没有差异或轻微的减速。不确定是否值得。

我还更新了(链接:http://jsperf.com/cljs-core-str-possibilities/2 文本:一元案例)关于 str 使用 switch 和永远不使用 toString。在 Chrome 上几乎慢了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。

...