io.js入门(三)—— 所支持的ES6(下)
(接上篇)
标准ES6特性
6. 新的String方法/New String methods
7. 符号/Symbols
新的String方法/New String methods
codePointAt() 和 String.fromCodePoint()
JS内部是以UTF-16的形式(每个字符长度都固定为16位元、2个字节)来存储字符编码的。就汉字来说,大部分的汉字均以2字节的形式来存储即可,却也有部分汉字需要有4个字节的长度来存储(其codePoint/码点大于0xFFFF),比如这个生僻字“𠮷”(注意不是“吉”),其Unicode码点是0x20BB7(十进制为134071),UTF-16码点为0xD842 0xDFB7(十进制为55362 57271)。
我们如果对这种字符使用常规的 charAt(0) 和 String.fromCharCode() ,将无法获得正确的码点/字符:
var s = "𠮷", a = s.charCodeAt(0), //55362,即前两个字节的码点 b = String.fromCharCode(0x20BB7); //乱码字符
为解决此问题,ES6推出了 codePointAt() 和 String.fromCodePoint() 这两个字符串方法,前者可以顺利获取字符的Unicode码点,后者可由已知码点获得对应字符:
var s = "𠮷", a = s.codePointAt(0), //134071 b = String.fromCodePoint(0x20BB7); //"𠮷"
该方法的测试比较麻烦,因为常规是命令行界面是GBK编码的,即使使用“chcp 65001”口令切到UTF-8形式也无法正常显示上文提到的生僻字符。不过我们可以将代码转到浏览器(别用IE)上显示:
var s = "𠮷", a = s.codePointAt(0), aa = s.charCodeAt(0), b = s.codePointAt(1), bb = s.charCodeAt(1), c = String.fromCodePoint(a), cc = String.fromCharCode(aa), d = String.fromCodePoint(b), dd = String.fromCharCode(bb); console.log("a: "+a+",aa: "+aa+",b: "+b+",bb: "+bb+",c: "+c+",cc: "+cc+",d: "+d+",dd: "+dd); var http = require('http'), fs = require('fs'); http.createServer(function(req,res) { res.writeHeader(200,{"Content-Type": "text/html"}); res.write('<!doctype html><html><head><meta charset="utf-8"><title>测试</title></head><body><div>'); res.write("a: "+a+",aa: "+aa+",b: "+b+",bb: "+bb+",c: "+c+",cc: "+cc+",d: "+d+",dd: "+dd); res.end("</div></body></html>"); }).listen(1337);
接着我们访问地址 http://127.0.0.1:1337/ 可看到如下内容:
startsWith(),endsWith() 和 includes()
常规我们会使用 s.indexOf() 来检测一个字符串里是否含有某段字符串。ES6提供了3个新的字符串检索方法,其中 startsWith() 和 endsWith() 分别用于从头部/尾部开始匹配字符串片段,includes()则没有位置限定,它们都返回boolean值。这三个方法都很好理解,直接上例子吧:
var str = 'To be, or not to be, that is the question.'; console.log(str.startsWith('To be')); //true console.log(str.endsWith('question.')); //true console.log(str.includes('not to be')); //true
这三个方法均支持第二个参数,表示开始搜索的位置:
var str = 'To be, or not to be, that is the question.'; console.log(str.startsWith('To be',1)); //false console.log(str.endsWith('To',2)); //true console.log(str.includes('not to be',1)); //true
注意endsWith()加上第二个参数后,表示匹配从开始到参数索引位置结束的字符串。
repeat()
repeat()返回一个新字符串,表示将原字符串重复n次:
var str = "x".repeat(3); console.log(str); //xxx
normalize()
该方法可将所给字符串转化为Unicode标准化字符:
var n1 = '\uD842\uDFB7'.normalize(); //𠮷
这里有个有趣的例子——为了表示语调和重音符号,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别:
console.log('\u01D1'==='\u004F\u030C'); //false console.log('\u01D1'.length); // 1 console.log('\u004F\u030C'.length); // 2
上面代码表示,JavaScript将合成字符视为两个字符,导致两种表示方法不相等。使用normalize()方法可以解决该问题:
var n1 = '\u01D1'.normalize() ;
var n2 = '\u004F\u030C'.normalize();
alert(n1===n2); //true 注意这里仅仅是在chrome为true,但在iojs里是false,因为iojs默认没有自带Intl,需要安装的童鞋可以查看这里
normalize方法可以接受四个参数:
NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”(当然这里只是举例子,normalize只适用于欧洲文字,解决法语的重音符号之类的问题)。
NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize(NFC).length // 1 '\u004F\u030C'.normalize(NFD).length // 2
符号/Symbols
codePointAt() 和 String.fromCodePoint()
ES6引入了一种新的原始数据类型Symbol,表示独一无二的ID。它通过Symbol函数生成:
var symbol1 = Symbol(); console.log(typeof symbol); // "symbol"
Symbol函数可以接受一个字符串作为参数,表示Symbol实例的名称。需要注意的是Symbol实例均是独一无二、不相等的:
var symbolA = Symbol('Test'); var symbolB = Symbol('Test'); console.log(symbolA===symbolB); //false
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。如果你的确想生成一个Symbol包装对象,应使用Object() 方法:
var sym = Symbol("foo"); typeof sym; // "symbol" var symObj = Object(sym); typeof symObj; // "object"
常规我们把Symbol实例作为某个对象的属性名字,因为它是独一无二的,可以有效防止对象属性被污染:
var syma = Symbol('test'); var symb = Symbol('test'); var aag = {}; aag[syma] = 123; aag[symb] = 456; console.log(aag[syma]); //123
另一个需要了解的是Symbol实例作为对象属性是不可被枚举的,但我们可以通过 Object.getOwnPropertySymbols() 来获取它们:
var syma = Symbol('a'); var symb = Symbol('b'); var obj = {}; obj[syma] = 123; obj[symb] = 456; obj["c"] = 789; for(var i in obj) console.log(i+":"+obj[i]); //只输出"c:789" var objectSymbols = Object.getOwnPropertySymbols(obj); console.log(objectSymbols.length); // 2 console.log(objectSymbols[0]); // Symbol(a) for(var i in objectSymbols) console.log( i +":"+ objectSymbols[i].toString()); //注意把Symbol实例转为字符串得使用.toString()方法 //0:Symbol(a) //1:Symbol(b)
除了常规创建独一无二的Symbol实例,我们也可以通过 Symbol.for() 来创建可共享的Symbol实例。通过Symbol.for()创建的Symbol实例,我们可以使用 Symbol.keyFor() 获取它的键名:
var syma = Symbol('a'); var symaa = Symbol('a'); var symb = Symbol.for('b'); var symbb = Symbol.for('b'); console.log(syma===symaa); //false console.log(symb===symbb); //true console.log(Symbol.keyFor(symb)); //b
Symbol除了可作为对象的某个独一无二的、不可枚举的属性,还有一个用处是可用于描述:
var obj = {}; var descript = Symbol.for('这是一个有趣的对象'); obj[descript] = Symbol.keyFor(descript); console.log(obj[descript]); //'这是一个有趣的对象'
字符串模板/Template strings
模板字符串(template string)是增强版的字符串,用反引号(`)标识(注意不是单引号)。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量和公式等东西:
console.log(`string text line 1 //多行字符串 string text line 2`); //string text line 1 //string text line 2 // 字符串中嵌入变量 var name = "Bob", time = "today"; console.log(`Hello ${name}, how are you ${time}?`); //Hello Bob, how are you today? //花括号内可进行计算 var param_a = 1, param_b = 2; var obj = {c:3}; console.log(`the result is ${param_a + param_b + obj.c}`); //the result is 6
模板字符串使得字符串与变量的结合,变得容易。下面是一个例子:
if (x > MAX) { throw new Error(`Most ${MAX} allowed: ${x}!`); // 传统写法为'Most '+MAX+' allowed: '+x+'!' }
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串:
var a = 5; var b = 10; function tag(s, v1, v2) { console.log(s[0]); console.log(s[1]); console.log(v1); console.log(v2); } tag`Hello ${ a + b } world ${ a * b}`; // "Hello " // " world " // 15 // 50
与常规的函数不同,Templat函数所接收到的参数比较另类,像上方的函数tag依次接受三个参数。第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。第一个参数之后的参数,都是模板字符串各个变量被替换后的值
也就是说,tag函数实际的参数如下:
tag(['Hello ', ' world '], 15, 50)
下面是一个更复杂的例子,展示了如何将各个参数按照原来的位置拼合回去:
var total = 30; var msg = passthru`The total is ${total} (${total*1.05} with tax)`; function passthru(literals) { var result = ""; var i = 0; while (i < literals.length) { result += literals[i++]; if (i < arguments.length) { result += arguments[i]; } } return result; } msg // "The total is 30 (31.5 with tax)"
另外模板字符串还有一个 String.raw() 方法,往往用来充当模板字符串的处理函数,返回字符串被转义前的原始格式:
console.log(String.raw`Hi\n${2+3}!`); // "Hi\n5!" console.log(String.raw`Hi\u000A!`); // 'Hi\u000A!'
注:本文大部分内容参考自阮一峰老师的ES6入门,但本文实例已事先对所有代码进行了校正(包括在io.js上的兼容性、代码错误的堪正,以及部分未提及特性方法的补漏)。
就此我们便介绍了io.js上所支持的全部ES6特性,共勉~