javascript replace高级用法

在前端与后台交互的时候我们通常都需要将后台传递的数据绑定到html中,这个绑定数据的方式我们通常是使用jQuery或者使用原生的innerHTML进行绑定,当然也可以使用artTemplate模板来绑定数据,那么artTemplate模板它绑定数据的原理是什么呢?其实它就是利用了replace()方法。对于正则replace约定了一个特殊标记符”$”:1)、$i(i取值范围1~99):表示从左到右正则子表达式所匹配的文本2)、$&:表示与正则表达式匹配的全部文本3)、$(:1旁边的那个键):表示匹配字符串的左边文本4)、$’(’:单引号):表示匹配字符串的右边文本5)、$$:表示$转移1、replace基本用法<script type=“text/javascript”> /要求将字符串中所有的a全部用A代替/ var str = “javascript is great script language!”; //只会将第一个匹配到的a替换成A console.log(str.replace(“a”,“A”)); //只会将第一个匹配到的a替换成A。因为没有在全局范围内查找 console.log(str.replace(/a/,“A”)); //所有a都被替换成了A console.log(str.replace(/a/g,“A”));</script>1.1、replace基本用法之替换移除指定class类<script type=“text/javascript”> /要求将下面这个元素中的unabled类移除掉/ <div class=”confirm-btn unabled mb-10” id=”j_confirm_btn”>提交</div> var classname = document.getElementById(“j_confirm_btn”).className; /(^|\s)表示匹配字符串开头或字符串前面的空格,(\s|$)表示匹配字符串结尾或字符串后面的空格/ var newClassName = classname.replace(/(^|\s)unabled(\s|$)/,””); document.getElementById(“j_confirm_btn”).className = newClassName;</script>2、replace高级用法之 —- $i2.1、简单的$i用法<script> /要求:将字符串中的双引号用"-“代替/ var str = ‘“a”, “b”’; console.log(str.replace(/”[^"]"/g,"-$1-")); //输出结果为:-$1-, -$1- /解释:$1就是前面正则(/"[^"]"/g)所匹配到的每一个字符。/</script>2.2、$i与分组结合使用<script> /要求:将下面字符串替换成:javascript is fn.it is a good script language/ var str = “javascript is a good script language”; console.log(str.replace(/(javascript)\s*(is)/g,"$1 $2 fn.it $2")); /解释:每一对括号都代表一个分组,从左往右分别代表第一个分组,第二个分组…;如上"(javascript)“为第一个分组,"(is)“为第二个分组。$1就代表第一个分组匹配的内容,$2就代表第二个分组匹配的内容,依此类推…/</script>2.3、$i与分组结合使用—-关键字高亮显示当我们使用谷歌搜索的时候我们会发现我们搜索的关键字都被高亮显示了,那么这种效果用JavaScript能否显示呢?答案是可以的,使用replace()很轻松就搞定了。<script> /要求:将下列字符串中的"java"用红色字体显示/ var str = “Netscape在最初将其脚本语言命名为LiveScript,后来Netscape在与Sun合作之后将其改名为JavaScript。JavaScript最初受Java启发而开始设计的,目的之一就是“看上去像Java”,因此语法上有类似之处,一些名称和命名规范也借自Java。但JavaScript的主要设计原则源自Self和Scheme。”; document.write(str.replace(/(java)/gi,’<span style=“color: red;font-weight: 800;">$1</span>’)); /解释:必须要开启全局搜索和忽略大小写,否则匹配不到所有的”java”字符/</script>2.4、反向分组—-分组的反向引用在正则中,当我们需要匹配两个或多个连续的相同的字符的时候,就需要用到反向引用了,查找连续重复的字符是反向引用最简单却也是最有用的应用之一。上面的”$i”也是反向分组的一种形式,这里再介绍另一种反向分组。<script type=“text/javascript”> / /ab(cd)\1e/ 这里的 \1 表示把第1个分组的内容重复一遍*/ console.log(/ab(cd)\1e/.test(“abcde”));//false console.log(/ab(cd)\1e/.test(“abcdcde”));//true /要求:将下列字符串中相领重复的部分删除掉”/ var str = “abbcccdeee”; var newStr = str.replace(/(\w)\1+/g,"$1”); console.log(newStr); // abcde</script>3、replace高级用法之参数二为函数replace函数的第二个参数不仅可以是一个字符,还可以是一个函数!3.1、参数二为函数之参数详解<script> var str = “bbabc”; var newStr = str.replace(/(a)(b)/g,function (){ console.log(arguments);//[“ab”, “a”, “b”, 2, “bbabc”] /*参数依次为: 1、整个正则表达式所匹配到的字符串—-“ab” 2、第一个分组匹配到的字符串,第二个分组所匹配到的字符串….依次类推一直 到最后一个分组—-“a,b” 3、此次匹配在源字符串中的下标,返回的是第一个匹配到的字符的下标—-2 4、源字符串—-“bbabc” */ })</script>3.2、参数二为函数之首字母大写案例<script> /要求:将下列字符串中的所有首字母大写/ var str = “Tomorrow may not be better, but better tomorrow will surely come!”; var newStr = str.replace(/\b\w+\b/gi,function (matchStr){ console.log(matchStr);//匹配到的字符 return matchStr.substr(0,1).toUpperCase() + matchStr.substr(1); }); console.log(newStr);</script>3.3、参数二为函数之绑定数据—-artTemplate模板核心<h1>周星驰喜剧电影:</h1><div id=“content”></div><script type=“text/javascript”> var data = { name: “功夫”, protagonist: “周星驰” }, domStr = ‘<div><span>名称:</span><span>{{name}}</span></div><div><span>导演:</span><span>{{protagonist}}</span> </div>’; document.getElementById(“content”).innerHTML = formatString(domStr,data); /绑定数据的核心就是使用正则进行匹配/ function formatString(str,data){ return str.replace(/{{(\w+)}}/g,function (matchingStr,group1){ return data[group1]; }); }</script>4、replace高级用法之获取与正则表达式匹配的文本4.1、replace高级用法之获取与正则表达式进行匹配的源字符串<script> var str = “i am a good man”; var newStr = str.replace(/good/g,"$&”); console.log(newStr);//结果:输出i am a good man /解释:在这里”$&”就是与正则表达式进行匹配的那个源字符串/</script>4.2、replace高级用法之获取正则表达式匹配到的字符<script> /*要求:将"i am a good man"替换成"i am a good-gond man" / var str = “i am a good man”; var newStr = str.replace(/good/g,"$&-$&"); console.log(newStr); /解释:在这里”$&”可以获取到前面正则表达式匹配的内容,如上面的”$&”就是正则表达式匹配到的”good”/</script>5、replace高级用法之获取正则匹配的左边的字符<script> /要求:将下列字符串替换成"java-java is a good script"/ var str = “javascript is a good script”; var newStr = str.replace(/script/,"-$"); console.log(newStr) /*解释:"$“获取的是正则左边的内容,如上正则中"script"字符前面的是"java”,"-$"就是"-java","-$“会把script替换掉。/</script>6、replace高级用法之获取正则匹配的右边的字符<script> /要求:将下列字符替换成"java is a good language!it is a good script is a good script”/ var str = “javascript is a good script”; var newStr = str.replace(/script/," is a good language!it$’"); console.log(newStr) /解释:"$’“获取的就是str右边的内容,如上正则中”$’“就是” is a good script"。 " is a good language!it$’“会把正则匹配到的"script"替换掉/</script> ...

February 16, 2019 · 2 min · jiezi

【Go】strings.Replace 与 bytes.Replace 调优

原文链接:https://blog.thinkeridea.com/…标准库中函数大多数情况下更通用,性能并非最好的,还是不能过于迷信标准库,最近又有了新发现,strings.Replace 这个函数自身的效率已经很好了,但是在特定情况下效率并不是最好的,分享一下我如何优化的吧。我的服务中有部分代码使用 strings.Replace 把一个固定的字符串删除或者替换成另一个字符串,它们有几个特点:旧的字符串大于或等于新字符串 (len(old) >= len(new)源字符串的生命周期很短,替换后就不再使用替换前的字符串它们都比较大,往往超过 2k~4k本博文中使用函数均在 go-extend 中,优化后的函数在 exbytes.Replace 中。发现问题近期使用 pprof 分析内存分配情况,发现 strings.Replace 排在第二,占 7.54%, 分析结果如下:go tool pprof allocsFile: xxxType: alloc_spaceTime: Feb 1, 2019 at 9:53pm (CST)Entering interactive mode (type “help” for commands, “o” for options)(pprof) topShowing nodes accounting for 617.29GB, 48.86% of 1263.51GB totalDropped 778 nodes (cum <= 6.32GB)Showing top 10 nodes out of 157 flat flat% sum% cum cum% 138.28GB 10.94% 10.94% 138.28GB 10.94% logrus.(*Entry).WithFields 95.27GB 7.54% 18.48% 95.27GB 7.54% strings.Replace 67.05GB 5.31% 23.79% 185.09GB 14.65% v3.(*v3Adapter).parseEncrypt 57.01GB 4.51% 28.30% 57.01GB 4.51% bufio.NewWriterSize 56.63GB 4.48% 32.78% 56.63GB 4.48% bufio.NewReaderSize 56.11GB 4.44% 37.23% 56.11GB 4.44% net/url.unescape 39.75GB 3.15% 40.37% 39.75GB 3.15% regexp.(*bitState).reset 36.11GB 2.86% 43.23% 38.05GB 3.01% des3_and_base64.(*des3AndBase64).des3Decrypt 36.01GB 2.85% 46.08% 36.01GB 2.85% des3_and_base64.(des3AndBase64).base64Decode 35.08GB 2.78% 48.86% 35.08GB 2.78% math/big.nat.make标准库中最常用的函数,居然……,不可忍必须优化,先使用 list strings.Replace 看一下源码什么地方分配的内存。(pprof) list strings.ReplaceTotal: 1.23TBROUTINE ======================== strings.Replace in /usr/local/go/src/strings/strings.go 95.27GB 95.27GB (flat, cum) 7.54% of Total . . 858: } else if n < 0 || m < n { . . 859: n = m . . 860: } . . 861: . . 862: // Apply replacements to buffer. 47.46GB 47.46GB 863: t := make([]byte, len(s)+n(len(new)-len(old))) . . 864: w := 0 . . 865: start := 0 . . 866: for i := 0; i < n; i++ { . . 867: j := start . . 868: if len(old) == 0 { . . 869: if i > 0 { . . 870: _, wid := utf8.DecodeRuneInString(s[start:]) . . 871: j += wid . . 872: } . . 873: } else { . . 874: j += Index(s[start:], old) . . 875: } . . 876: w += copy(t[w:], s[start:j]) . . 877: w += copy(t[w:], new) . . 878: start = j + len(old) . . 879: } . . 880: w += copy(t[w:], s[start:]) 47.81GB 47.81GB 881: return string(t[0:w]) . . 882:}从源码发现首先创建了一个 buffer 来起到缓冲的效果,一次分配足够的内存,这个在之前 【Go】slice的一些使用技巧 里面有讲到,另外一个是 string(t[0:w]) 类型转换带来的内存拷贝,buffer 能够理解,但是类型转换这个不能忍,就像凭空多出来的一个数拷贝。既然类型转换这里有点浪费空间,有没有办法可以零成本转换呢,那就使用 go-extend 这个包里面的 exbytes.ToString 方法把 []byte 转换成 string,这个函数可以零分配转换 []byte 到 string。 t 是一个临时变量,可以安全的被引用不用担心,一个小技巧节省一倍的内存分配,但是这样真的就够了吗?我记得 bytes 标准库里面也有一个 bytes.Replace 方法,如果直接使用这种方法呢就不用重写一个 strings.Replace了,使用 go-extend 里面的两个魔术方法可以一行代码搞定上面的优化效果 s = exbytes.ToString(bytes.Replace(exstrings.UnsafeToBytes(s), []byte{’ ‘}, []byte{’’}, -1)), 虽然是一行代码搞定的,但是有点长,exstrings.UnsafeToBytes 方法可以极小的代价把 string 转成 bytes, 但是 s 不能是标量或常量字符串,必须是运行时产生的字符串否者可能导致程序奔溃。这样确实减少了一倍的内存分配,即使只有 47.46GB 的分配也足以排到前十了,不满意这个结果,分析代码看看能不能更进一步减少内存分配吧。分析代码使用火焰图看看究竟什么函数在调用 strings.Replace 呢:这里主要是两个方法在使用,当然我记得还有几个地方有使用,看来不在火焰图中应该影响比较低 ,看一下代码吧(简化的代码不一定完全合理):// 第一部分func (v2 *v2Adapter) parse(s string) (*AdRequest, error) { s = strings.Replace(s, " “, “”, -1) requestJSON, err := v2.paramCrypto.Decrypt([]byte(s)) if err != nil { return nil, err } request := v2.getDefaultAdRequest() if err := request.UnmarshalJSON(requestJSON); err != nil { return nil, err } return request, nil}// 第二部分func (v3 *v3Adapter) parseEncrypt(s []byte) ([]byte, error) { ss := strings.Replace(string(s), " “, “”, -1) requestJSON, err := v3.paramCrypto.Decrypt([]byte(ss)) if err != nil { return nil, error } return requestJSON, nil}// 通过搜索找到的第三部分type LogItems []stringfunc LogItemsToBytes(items []string, sep, newline string) []byte { for i := range items { items[i] = strings.Replace(items[i], sep, " “, -1) } str := strings.Replace(strings.Join(items, sep), newline, " “, -1) return []byte(str + newline)}通过分析我们发现前两个主要是为了删除一个字符串,第三个是为了把一个字符串替换为另一个字符串,并且源数据的生命周期很短暂,在执行替换之后就不再使用了,能不能原地替换字符串呢,原地替换的就会变成零分配了,尝试一下吧。优化先写一个函数简单实现原地替换,输入的 len(old) < len(new) 就直接调用 bytes.Replace 来实现就好了 。func Replace(s, old, new []byte, n int) []byte { if n == 0 { return s } if len(old) < len(new) { return bytes.Replace(s, old, new, n) } if n < 0 { n = len(s) } var wid, i, j int for i, j = 0, 0; i < len(s) && j < n; j++ { wid = bytes.Index(s[i:], old) if wid < 0 { break } i += wid i += copy(s[i:], new) s = append(s[:i], s[i+len(old)-len(new):]…) } return s}写个性能测试看一下效果:$ go test -bench=”.” -run=nil -benchmemgoos: darwingoarch: amd64pkg: github.com/thinkeridea/go-extend/exbytes/benchmarkBenchmarkReplace-8 500000 3139 ns/op 416 B/op 1 allocs/opBenchmarkBytesReplace-8 1000000 2032 ns/op 736 B/op 2 allocs/op使用这个新的函数和 bytes.Replace 对比,内存分配是少了,但是性能却下降了那么多,崩溃…. 啥情况呢,对比 bytes.Replace 的源码发现我这个代码里面 s = append(s[:i], s[i+len(old)-len(new):]…) 每次都会移动剩余的数据导致性能差异很大,可以使用 go test -bench=”.” -run=nil -benchmem -cpuprofile cpu.out -memprofile mem.out 的方式来生成 pprof 数据,然后分析具体有问题的地方。找到问题就好了,移动 wid 之前的数据,这样每次移动就很少了,和 bytes.Replace 的原理类似。func Replace(s, old, new []byte, n int) []byte { if n == 0 { return s } if len(old) < len(new) { return bytes.Replace(s, old, new, n) } if n < 0 { n = len(s) } var wid, i, j, w int for i, j = 0, 0; i < len(s) && j < n; j++ { wid = bytes.Index(s[i:], old) if wid < 0 { break } w += copy(s[w:], s[i:i+wid]) w += copy(s[w:], new) i += wid + len(old) } w += copy(s[w:], s[i:]) return s[0:w]}在运行一下性能测试吧:$ go test -bench="." -run=nil -benchmemgoos: darwingoarch: amd64pkg: github.com/thinkeridea/go-extend/exbytes/benchmarkBenchmarkReplace-8 1000000 2149 ns/op 416 B/op 1 allocs/opBenchmarkBytesReplace-8 1000000 2231 ns/op 736 B/op 2 allocs/op运行性能差不多,而且更好了,内存分配也减少,不是说是零分配吗,为啥有一次分配呢?var replaces stringvar replaceb []bytefunc init() { replaces = strings.Repeat(“A BC”, 100) replaceb = bytes.Repeat([]byte(“A BC”), 100)}func BenchmarkReplace(b *testing.B) { for i := 0; i < b.N; i++ { exbytes.Replace([]byte(replaces), []byte(" “), []byte(”"), -1) }}func BenchmarkBytesReplace(b *testing.B) { for i := 0; i < b.N; i++ { bytes.Replace([]byte(replaces), []byte(" “), []byte(”"), -1) }}可以看到使用了 []byte(replaces) 做了一次类型转换,因为优化的这个函数是原地替换,执行过一次之后后面就发现不用替换了,所以为了公平公正两个方法每次都转换一个类型产生一个新的内存地址,所以实际优化后是没有内存分配了。之前说写一个优化 strings.Replace 函数,减少一次内存分配,这里也写一个这样函数,然后增加两个性能测试函数,对比一下效率 性能测试代码:$ go test -bench="." -run=nil -benchmemgoos: darwingoarch: amd64pkg: github.com/thinkeridea/go-extend/exbytes/benchmarkBenchmarkReplace-8 1000000 2149 ns/op 416 B/op 1 allocs/opBenchmarkBytesReplace-8 1000000 2231 ns/op 736 B/op 2 allocs/opBenchmarkStringsReplace-8 1000000 2260 ns/op 1056 B/op 3 allocs/opBenchmarkUnsafeStringsReplace-8 1000000 2522 ns/op 736 B/op 2 allocs/opPASSok github.com/thinkeridea/go-extend/exbytes/benchmark 10.260s运行效率上都相当,优化之后的 UnsafeStringsReplace 函数减少了一次内存分配只有一次,和 bytes.Replace 相当。修改代码有了优化版的 Replace 函数就替换到项目中吧:// 第一部分func (v2 *v2Adapter) parse(s string) (*AdRequest, error) { b := exbytes.Replace(exstrings.UnsafeToBytes(s), []byte(" “), []byte(”"), -1) requestJSON, err := v2.paramCrypto.Decrypt(b) if err != nil { return nil, err } request := v2.getDefaultAdRequest() if err := request.UnmarshalJSON(requestJSON); err != nil { return nil, err } return request, nil}// 第二部分func (v3 *v3Adapter) parseEncrypt(s []byte) ([]byte, error) { s = exbytes.Replace(s, []byte(" “), []byte(”"), -1) requestJSON, err := v3.paramCrypto.Decrypt(s) if err != nil { return nil, err } return requestJSON, nil}// 第三部分type LogItems []stringfunc LogItemsToBytes(items []string, sep, newline string) []byte { for i := range items { items[i] = exbytes.ToString(exbytes.Replace(exstrings.UnsafeToBytes(items[i]), []byte(sep), []byte(" “), -1)) } b := exbytes.Replace(exstrings.UnsafeToBytes(strings.Join(items, sep)), []byte(newline), []byte(” “), -1) return append(b, newline…)}上线后性能分析$ go tool pprof allocs2File: xxType: alloc_spaceTime: Feb 2, 2019 at 5:33pm (CST)Entering interactive mode (type “help” for commands, “o” for options)(pprof) top exbytes.ReplaceFocus expression matched no samplesActive filters: focus=exbytes.ReplaceShowing nodes accounting for 0, 0% of 864.21GB total flat flat% sum% cum cum%(pprof)居然在 allocs 上居然找不到了,确实是零分配。优化前 profile :$ go tool pprof profileFile: xxType: cpuTime: Feb 1, 2019 at 9:54pm (CST)Duration: 30.08s, Total samples = 12.23s (40.65%)Entering interactive mode (type “help” for commands, “o” for options)(pprof) top strings.ReplaceActive filters: focus=strings.ReplaceShowing nodes accounting for 0.08s, 0.65% of 12.23s totalShowing top 10 nodes out of 27 flat flat% sum% cum cum% 0.03s 0.25% 0.25% 0.08s 0.65% strings.Replace 0.02s 0.16% 0.41% 0.02s 0.16% countbody 0.01s 0.082% 0.49% 0.01s 0.082% indexbytebody 0.01s 0.082% 0.57% 0.01s 0.082% memeqbody 0.01s 0.082% 0.65% 0.01s 0.082% runtime.scanobject优化后 profile :$ go tool pprof profile2File: xxType: cpuTime: Feb 2, 2019 at 5:33pm (CST)Duration: 30.16s, Total samples = 14.68s (48.68%)Entering interactive mode (type “help” for commands, “o” for options)(pprof) top exbytes.ReplaceActive filters: focus=exbytes.ReplaceShowing nodes accounting for 0.06s, 0.41% of 14.68s totalShowing top 10 nodes out of 18 flat flat% sum% cum cum% 0.03s 0.2% 0.2% 0.03s 0.2% indexbytebody 0.02s 0.14% 0.34% 0.05s 0.34% bytes.Index 0.01s 0.068% 0.41% 0.06s 0.41% github.com/thinkeridea/go-extend/exbytes.Replace通过 profile 来分配发现性能也有一定的提升,本次 strings.Replace 和 bytes.Replace 优化圆满结束。本博文中使用函数均在 go-extend 中,优化后的函数在 exbytes.Replace 中。转载:本文作者: 戚银(thinkeridea)本文链接: https://blog.thinkeridea.com/201902/go/replcae_you_hua.html版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处! ...

February 3, 2019 · 6 min · jiezi