开始字符串扩展之前,我们先看看在ES6之前的字符串有什么问题:

  1. //ES6
  2. "\u{20BB7}" //"?"
  3. "\uD842\uDFB7" === "\u{20BB7}"; //true
  4. "\u01d1" === "\u004f\u030C"; //理论上是一致的, 但 js 返回了 false
  5. //使用 normalize 统一编码来解决这个问题
  6. "\u01d1".normalize() === "\u004f\u030C".normalize(); //true

遗憾的是, normalize() 方法只能表示2个 utf-16 字符的合成, 要是3个或以上就只能自己用正则表达式解决了。

至此, 一个字符有了6中表示方法:

  1. //以下结果都是 true
  2. "\x7A" === "z";
  3. "\u{7A}" === "z";
  4. "\u007A" === "z";
  5. "\172" === "z";
  6. "\z" === "z";

javascript内部用 utf-16 格式存储字符, 这样仓促的引入 utf-32 的码点会引起许多错误, 所以 ES6 在 String.prototype 上添加了 CodePointAt() 方法替换 charCodeAt(), 用静态方法 String.fromCodePoint() 替换了 String.fromCharCode(), 以解决因编码差异导致的计算错误。

但是, 我们依然要注意到一下错误:

  1. var a="?a";
  2. console.log(a.length); //3
  3. console.log(a.charAt(0)); //"\uD842"
  4. console.log(a.split('')); //["\uD842", "\uDFB7", a]
  5. //这些显然不是我们想要的, 我们需要替换使用
  6. console.log(a.codePointLength()); //下面我们可以自己简单写一个 codePointLength() 方法
  7. console.log(a.at(0)); //"?", ES7 方法, babel中有
  8. console.log(a.toArray()); //下面我们可以自己简单写一个 toArray() 方法

为此, 遍历字符串中的每个字符, 请使用 for…of

  1. var a="?a";
  2. for(let alpha of a){
  3. console.log(alpha); //依次输出: ?, a
  4. }

除此之外, ES6 还提供了一些其他的简单方法, 这里简单带过:

  1. //动态方法
  2. s.includes(str, n); //s从下标 n 的字符起是否包含 str 字符串, 返回 Boolean, n 默认为 0
  3. s.startsWith(str, n); //s从下标 n 的字符起是否以 str 开头, 返回 Boolean, n 默认为 0
  4. s.endWith(str, n); //s的前 n 个字符是否以 str 结尾, 返回 Boolean, n 默认为字符串长度
  5. s.repeat(n); //返回将 s 重复 n 次的新字符串, 如果 n 是小数, 会向下取整;如果 n 是 infinity 或小于等于-1 会报错;如果 n 大于-1且小于等于零, 返回空字符串;如果 n 为 NaN, 返回空字符串;其余传输参数遵循隐式类型转换。
  6. s.padStart(minLen, str); //对于小于 minLen 长度的字符串, 在 s 前用 str 重复补充为 len 长度, 返回该新字符串, 否则返回原字符串。str默认为空格
  7. s.padEnd(minLen, str); //对于小于 minLen 长度的字符串, 在 s 后用 str 重复补充为 len 长度, 返回该新字符串, 否则返回原字符串。str默认为空格

自定义函数计算字符串长度和大小, 以及转化为数组:

  1. //计算字符串长度, 方法1
  2. String.prototype.codePointLength = function(){
  3. var result = this.match(/[\s\S]/gu);
  4. return result ? result.length : 0;
  5. };
  6. //计算字符串长度, 方法2
  7. String.prototype.codePointLength2 = function(){
  8. return [...this].length;
  9. };
  10. //计算字符大小
  11. String.prototype.size = function(){
  12. var size = 0;
  13. for(let alpha of this){
  14. if(alpha.codePointAt(0) > 0xFFFF){
  15. size+=4;
  16. } else {
  17. size+=2;
  18. }
  19. }
  20. return size;
  21. };
  22. var a="?a";
  23. console.log(a.codePointLength()); //2
  24. console.log(a.codePointLength2()); //2
  25. console.log(a.size()); //6
  26. //字符串拆分为数组,方法1
  27. String.prototype.toArray = function(nil){
  28. if(nil === undefined){
  29. return Array.from(this);
  30. }
  31. if(nil.constructor === RegExp || nil.constructor === String){
  32. var reg = new RegExp(nil, "u");
  33. return this.split(reg);
  34. }
  35. }
  36. //字符串拆分为数组,方法2
  37. String.prototype.toArray2 = function(nil){
  38. if(nil === undefined){
  39. return [...this];
  40. }
  41. if(nil.constructor === RegExp || nil.constructor === String){
  42. var reg = new RegExp(nil, "u");
  43. return this.split(reg);
  44. }
  45. }
  46. var a="?ds?asaf?saf";
  47. console.log(a.toArray()); //["?", "d", "s", "?", "a", "s", "a", "f", "?", "s", "a", "f"]
  48. console.log(a.toArray('a')); //["?ds?", "s", "f?s", "f"]
  49. console.log(a.toArray('?')); //["", "ds", "asaf", "saf"]
  50. console.log(a.toArray2()); //["?", "d", "s", "?", "a", "s", "a", "f", "?", "s", "a", "f"]
  51. console.log(a.toArray2('a')); //["?ds?", "s", "f?s", "f"]
  52. console.log(a.toArray2('?')); //["", "ds", "asaf", "saf"]

模板字符串

ES5 中, 我们写一个多行字符很不方便:

  1. //我们这样写动态字符串
  2. var multiStr = "I am " + name; //假定 name 已定义
  3. //这样写多行字符串
  4. var multiStr = "first line\nsecond line\nthird line";
  5. //在或者这样
  6. var tempArr = ["first line", "second line", "third line"]; //多用于生成动态字符串。此外《编写高质量代码:改善JavaScript程序的188个建议》中指出, 这个方法写静态字符串一样比加号(+)性能更好
  7. var multiStr = tempArr.join("\n");

在 ES6 中利用反引号(...)和EL表达式(${...}), 我们可以这样:

  1. //我们这样写动态字符串
  2. var multiStr = `I am ${name}`; //假定 name 已定义
  3. //这样写多行字符串
  4. var multiStr = `
  5. first line
  6. second line
  7. third line
  8. `;

注意: 反引号中的所有空格和缩进都会被保留下来

EL表达式中可以放入任何表达式进行运算:

  1. var x=2, y=3;
  2. console.log(`x+y=${x+y}`); //x+y=5
  3. function plus(a, b){
  4. return a+b;
  5. }
  6. console.log(`x+y=${plus(x, y)}`); //x+y=5

引用模板字符串本身:

  1. //方法1
  2. let str = 'return ' + '`Hello ${name}`';
  3. let fun = new Function('name', str);
  4. fun('Jack');
  5. //方法2
  6. let str2 = '(name) => `Hello ${name}`';
  7. let fun2 = eval(str2);
  8. fun2('Jack');

标签模板

标签模板是用来处理字符串的函数, 但是调用方式和以往大不相同, 是直接将模板字符串跟在函数名后面, 该函数的结构如下:

  1. function funName(strings, ...values){
  2. //...
  3. }

其中, 以EL表达式作为分界, 前后和表达式之间的字符串部分, 会从左到右依次放入 strings 参数中;每一个 EL 表达式会从左到右依次放入 values 参数中:

  1. function tag(strings, ...values){
  2. console.log(strings[0]);
  3. console.log(strings[1]);
  4. console.log(strings[2]);
  5. console.log(values[0]);
  6. console.log(values[1]);
  7. return 'OK';
  8. }
  9. //当然, 也可以这样写(关于展开运算符, 这里不做深入讨论, 具体在 ES6 的函数部分展开)
  10. function tag(strings, value0, value1){
  11. console.log(strings[0]);
  12. console.log(strings[1]);
  13. console.log(strings[2]);
  14. console.log(value0);
  15. console.log(value1);
  16. return 'OK';
  17. }

下面是完整的调用:

  1. var a = 5;
  2. var b = 10;
  3. function tag(strings, ...values){
  4. console.log(strings[0]); //"Hello "
  5. console.log(strings[1]); //" world "
  6. console.log(strings[2]); //"!"
  7. console.log(values[0]); //15
  8. console.log(values[1]); //50
  9. return 'OK';
  10. }
  11. console.log(tag`Hello ${a+b} world ${a*b}!`); //"OK"

当然, 标签模板的正经用法如下:

  1. var a = 5;
  2. var b = 10;
  3. function tag(strings, ...values){
  4. var result = [], i = 0;
  5. while(i < strings.length){
  6. result.push(strings[i]);
  7. if(values[i]){
  8. result.push(values[i]);
  9. }
  10. i++;
  11. }
  12. return result.join('');
  13. }
  14. console.log(tag`Hello ${ a + b } world ${ a * b}!`); //Hello 15 world 50!

当然, 如果将上方 tag 函数中的 <, >, & 都替换掉, 并忽略 values 中的内容, 可以用来过滤用户输入数据, 防止恶意代码注入。

值得一提的是, tag的第一个参数 strings 还有一个 raw 属性, 也是一个数组, 内容分别对应 strings 数组的值的原生字符。比如 strings 中 strings[0]=“a\nb", 则 strings.raw[0]="a\\nb"。除此之外, ES6 还添加了 String.raw() 方法, 用于解析模板字符串, 但返回值的字符串是原生字符, 其内部基本如下:

  1. String.raw = function(strings, ...values){
  2. var output = "";
  3. for(var index = 0; index < values.length; index++){
  4. output += strings.raw[index] + value[index];
  5. }
  6. output += string.raw[index];
  7. retrun output;
  8. };