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) 返回空字符串。如果有多个参数,则返回
参数的 str 值的连接。"
([] "")
([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/array 方法似乎有点奇怪?

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

0
by

由thheller发表的评论

之前

;;; str [], (str "1"), 1000000 runs, 254 msecs [], (str 1), 1000000 runs, 266 msecs [], (str nil), 1000000 runs, 80 msecs [], (str "1" "2" "3"), 1000000 runs, 753 msecs

之后

;;; str [], (str "1"), 1000000 runs, 82 msecs [], (str 1), 1000000 runs, 86 msecs [], (str nil), 1000000 runs, 79 msecs [], (str "1" "2" "3"), 1000000 runs, 242 msecs

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

0
by

由thheller发表的评论

`
(defn str
"如果没有参数,返回空字符串。有一个参数 x,则返回
x.toString()。((str nil) 返回空字符串。如果有多个参数,则返回
参数的 str 值的连接。"
([] "")
([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 runs, 40 msecs [], (str 1), 1000000 runs, 43 msecs [], (str nil), 1000000 runs, 96 msecs [], (str "1" "2" "3"), 1000000 runs, 117 msecs

应该内联多少个参数?

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

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

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

我认为可以完全删除这个宏,因为cljs.core/str将会执行相同的操作,并且JIT可能足够智能以理解这一点(甚至在编译时 Closure 可能也会这样)。

0
by
_评论者: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)}}风格的代码,这样关闭编译器可以预先计算。此时,独占参数的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错误的影响(我已经检查了——我有一台带有Lion的iMac)。 使用Safari 6.0.5的机器是(Mountain)Lion机器,这些机器使用软件更新,直到2012年底至2013年初,然后停止了。我想不出这会有多少人。

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

我认为选择是
使用数组的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_

关于我正在进行的这项研究的最新进展。

我创建了一个替代str实现的[JavaScript性能测试] [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)。可能 better 使用reduce而不是Stringbuffer+seq。 (Stringbuffer现在只是 {{''+x}} 而不是数组合并.)
0

由thheller发表的评论

抱歉,差点走题了。

@Francis: 感谢总结。

我也没有Safari 6,但看来我们所有人都应该因此受到惩罚,因为尽管只有少数人(在我的网站上667用户中的190k+)使用它。没有解决方案,因为我无法测试它是否有效,我们或许可以尝试使用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, but maybe 6.0.4 or lower.
  2. BrowserStack的系统以某种方式减轻了错误,这意味着我们需要一个“真实”的Lion Safari 6.0.x来测试。
  3. 这些测试仅在正确的月相阶段失败。

因此,我可以用str-switch实现为str编写补丁(在某些浏览器上至少快点),但我不知道它是否可能在Safari 6.0.5上失败。我只知道到目前为止它有效。CLJS-801也应该安全重用,因为所有问题的根本原因都是cljs.core/str函数的实现1-arity。

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

0

评论人:favila

制作了一个 http://jsperf.com/cljs-core-str-variadic-possibilities 文本:变长情况)jsperf。Chrome似乎真的喜欢使用IReduce在来回的向量(其他集合未测试),但其他浏览器没有区别或轻微的减速。不确定是否值得。

还更新了 http://jsperf.com/cljs-core-str-possibilities/2 文本:单一参数情况)与str和始终不使用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
by

评论人:favila

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

...