字符串类型扩展

unicode

javascript 本可以表示 unicode, ES5 可以直接表示 utf-16 的字符集, 但如果需要表示 utf-32 的字符集就多有不便, 需要使用2个 utf-16 的编码。ES6就这个问题进行了改进, 且与原表示符是等价的(理论上是这样的, 但 js 不能保证都识别正确):

//ES5
"\uD842\uDFB7"  //"𠮷"

//ES6
"\u{20BB7}"   //"𠮷"

"\uD842\uDFB7" === "\u{20BB7}";   //true
"\u01d1" === "\u004f\u030C";       //理论上是一致的, 但 js 返回了 false

//使用 normalize 统一编码来解决这个问题
"\u01d1".normalize() === "\u004f\u030C".normalize();     //true

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

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

//以下结果都是 true
"\x7A" === "z";
"\u{7A}" === "z";
"\u007A" === "z";
"\172" === "z";
"\z" === "z";

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

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

var a="𠮷a";

console.log(a.length);       //3
console.log(a.charAt(0));    //"\uD842"
console.log(a.split(''));    //["\uD842", "\uDFB7", a]

//这些显然不是我们想要的, 我们需要替换使用
console.log(a.codePointLength());       //下面我们可以自己简单写一个 codePointLength() 方法
console.log(a.at(0));    //"𠮷", ES7 方法, babel中有
console.log(a.toArray());    //下面我们可以自己简单写一个 toArray() 方法

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

var a="𠮷a";
for(let alpha of a){
  console.log(alpha);    //依次输出: 𠮷, a
}

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

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

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

//计算字符串长度, 方法1
String.prototype.codePointLength = function(){
  var result = this.match(/[\s\S]/gu);
  return result ? result.length : 0;
};
//计算字符串长度, 方法2
String.prototype.codePointLength2 = function(){
  return [...this].length;
};

//计算字符大小
String.prototype.size = function(){
  var size = 0;
  for(let alpha of this){
    if(alpha.codePointAt(0) > 0xFFFF){
      size+=4;
    } else {
      size+=2;
    }
  }
  return size;
};

var a="𠮷a";
console.log(a.codePointLength());    //2
console.log(a.codePointLength2());    //2
console.log(a.size());   //6

//字符串拆分为数组,方法1
String.prototype.toArray = function(nil){
  if(nil === undefined){
    return Array.from(this);
  }
  if(nil.constructor === RegExp || nil.constructor === String){
    var reg = new RegExp(nil, "u");
    return this.split(reg);
  }
}
//字符串拆分为数组,方法2
String.prototype.toArray2 = function(nil){
  if(nil === undefined){
    return [...this];
  }
  if(nil.constructor === RegExp || nil.constructor === String){
    var reg = new RegExp(nil, "u");
    return this.split(reg);
  }
}

var a="𠮷ds𠮷asaf𠮷saf";
console.log(a.toArray());   //["𠮷", "d", "s", "𠮷", "a", "s", "a", "f", "𠮷", "s", "a", "f"]
console.log(a.toArray('a'));   //["𠮷ds𠮷", "s", "f𠮷s", "f"]
console.log(a.toArray('𠮷'));   //["", "ds", "asaf", "saf"]
console.log(a.toArray2());   //["𠮷", "d", "s", "𠮷", "a", "s", "a", "f", "𠮷", "s", "a", "f"]
console.log(a.toArray2('a'));   //["𠮷ds𠮷", "s", "f𠮷s", "f"]
console.log(a.toArray2('𠮷'));   //["", "ds", "asaf", "saf"]

模板字符串

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

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

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

//我们这样写动态字符串
var multiStr = `I am ${name}`;    //假定 name 已定义
//这样写多行字符串
var multiStr = `
first line
second line
third line
`;

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

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

var x=2, y=3;
console.log(`x+y=${x+y}`);    //x+y=5

function plus(a, b){
  return a+b;
}
console.log(`x+y=${plus(x, y)}`);    //x+y=5

引用模板字符串本身:

//方法1
let str = 'return ' + '`Hello ${name}`';
let fun = new Function('name', str);
fun('Jack');

//方法2
let str2 = '(name) => `Hello ${name}`';
let fun2 = eval(str2);
fun2('Jack');

标签模板

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

function funName(strings, ...values){
   //...
}

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

function tag(strings, ...values){
  console.log(strings[0]);
  console.log(strings[1]);
  console.log(strings[2]);
  console.log(values[0]);
  console.log(values[1]);
  return 'OK';
}
//当然, 也可以这样写(关于展开运算符, 这里不做深入讨论, 具体在 ES6 的函数部分展开)
function tag(strings, value0, value1){
  console.log(strings[0]);
  console.log(strings[1]);
  console.log(strings[2]);
  console.log(value0);
  console.log(value1);
  return 'OK';
}

下面是完整的调用:

var a = 5;
var b = 10;

function tag(strings, ...values){
  console.log(strings[0]);      //"Hello "
  console.log(strings[1]);      //" world "
  console.log(strings[2]);      //"!"
  console.log(values[0]);       //15
  console.log(values[1]);       //50
  return 'OK';
}

console.log(tag`Hello ${a+b} world ${a*b}!`);     //"OK"

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

var a = 5;
var b = 10;

function tag(strings, ...values){
  var result = [], i = 0;
  while(i < strings.length){
    result.push(strings[i]);
    if(values[i]){
      result.push(values[i]);
    }
    i++;
  }
  return result.join('');
}

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() 方法, 用于解析模板字符串, 但返回值的字符串是原生字符, 其内部基本如下:

String.raw = function(strings, ...values){
  var output = "";
  for(var index = 0; index < values.length; index++){
    output += strings.raw[index] + value[index];
  }
  output += string.raw[index];
  retrun output;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容