修改string/模板字符串

This commit is contained in:
Ruan Yifeng
2015-01-27 21:52:28 +08:00
parent ed562a470f
commit 78b4c2f282
6 changed files with 284 additions and 21 deletions
+18
View File
@@ -153,6 +153,24 @@ import { lastName as surname } from './profile';
```
ES6支持多重加载,即所加载的模块中又加载其他模块。
```javascript
import { Vehicle } from './Vehicle';
class Car extends Vehicle {
move () {
console.log(this.name + ' is spinning wheels...')
}
}
export { Car }
```
上面的模块先加载Vehicle模块,然后在其基础上添加了move方法,再作为一个新模块输出。
**(2)模块的整体输入,module命令**
export命令除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法area和circumference。
+15
View File
@@ -52,6 +52,21 @@ hw.next()
总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。
由于Generator函数本身就能生成遍历器,所以它的Symbol.iterator属性指向自身。
```javascript
function* gen(){
// some code
}
gen[Symbol.iterator]() === gen
// true
```
上面代码中,gen是一个Generator函数,它的Symbol.iterator属性就指向它自己。
由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
yield语句与return语句有点像,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。
+81 -17
View File
@@ -2,6 +2,8 @@
## Iterator(遍历器)
### 语法
遍历器(Iterator)是一种接口规格,任何对象只要部署这个接口,就可以完成遍历操作。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得对象的属性能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of循环使用。
遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。下面是一个模拟next方法返回值的例子。
@@ -51,9 +53,15 @@ it.next().value // '2'
```
上面的例子,只是为了说明next方法返回值的结构。Iterator接口返回的遍历器,原生具备next方法,不用自己部署。所以,真正需要部署的是Iterator接口,让其返回一个遍历器。在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署
上面的例子,说明next方法返回值的结构value和done两个属性
下面就是如何部署Iterator接口。一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。
### Iterator接口的部署
具有Iterator接口的对象,都能被for...of循环遍历(见后文的介绍)。所谓Iterator接口,就是指它会返回一个遍历器对象,该对象具备next方法,每次调用该方法,会依次返回一个具有上节提到的value和done两个属性的新对象,指向原对象的一个成员。
在ES6中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。其他对象需要手动部署Iterator接口,让其返回一个遍历器。
一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。
```javascript
@@ -69,7 +77,59 @@ class MySpecialTree {
上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。这里要注意,@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。
下面是为对象添加Iterator接口的例子。
下面是一个例子。
```javascript
function O(value){
this.value = value;
this.next = null;
}
O.prototype[Symbol.iterator] = function(){
var iterator = {
next: next
};
var current = this;
function next(){
if (current){
var value = current.value;
var done = current == null;
current = current.next;
return {
done: done,
value: value
}
} else {
return {
done: true
}
}
}
return iterator;
}
var one = new O(1);
var two = new O(2);
var three = new O(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i)
}
// 1
// 2
// 3
```
上面代码首先在构造函数的原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值的同时,自动将内部指针移到下一个实例。
下面是另一个为对象添加Iterator接口的例子。
```javascript
@@ -95,6 +155,22 @@ let obj = {
```
如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。
```javascript
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
```
上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。
### 原生具备iterator接口的数据结构
《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。
```javascript
@@ -159,6 +235,8 @@ str // "hi"
上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...)返回的值变成了bye,而字符串本身还是hi。
### Iterator接口与Generator函数
部署`Symbol.iterator`方法的最简单实现,还是使用下一节要提到的Generator函数。
```javascript
@@ -189,20 +267,6 @@ for (let x of obj) {
```
如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。
```javascript
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
```
上面代码中,变量obj的@@iterator方法返回的不是遍历器,因此报错。
## for...of循环
ES6中,一个对象只要部署了@@iterator方法,就被视为具有iterator接口,就可以用for...of循环遍历它的值。也就是说,for...of循环内部调用是原对象的`Symbol.iterator`方法。
+7
View File
@@ -19,6 +19,8 @@
- Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features)
- Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur文档列出的一些ES6例子
- Axel Rauschmayer, [ECMAScript 6: whats next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子
- Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io)
## 语法点
@@ -31,7 +33,12 @@
- Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值
- Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构
- Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍
## 字符串
- Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符
- Nicholas C. Zakas, [A critical review of ECMAScript 6 quasi-literals](http://www.nczonline.net/blog/2012/08/01/a-critical-review-of-ecmascript-6-quasi-literals/)
- Mozilla Developer Network, [Template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings)
## Object
+45 -4
View File
@@ -256,6 +256,16 @@ WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set
首先,WeakSet的成员只能是对象,而不能是其他类型的值。其次,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
```javascript
var ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
```
上面代码试图向WeakSet添加一个数值,结果报错。
WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构。
```javascript
@@ -303,6 +313,25 @@ ws.clear();
```
WeakSet没有size属性,没有办法遍历它的成员。
```javascript
ws.size
// undefined
ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function
ws.forEach
// undefined
```
上面代码试图获取size和forEach属性,结果都不能成功。
WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保存成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
## Map
**1)基本用法**
@@ -611,24 +640,36 @@ WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制
```javascript
var map = new WeakMap();
var wm = new WeakMap();
var element = document.querySelector(".element");
map.set(element, "Original");
wm.set(element, "Original");
var value = map.get(element);
var value = wm.get(element);
console.log(value); // "Original"
element.parentNode.removeChild(element);
element = null;
value = map.get(element);
value = wm.get(element);
console.log(value); // undefined
```
WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
```javascript
var wm = new WeakMap();
wm.size
// undefined
wm.forEach
// undefined
```
WeakMap的一个用处是部署私有属性。
```javascript
+118
View File
@@ -402,6 +402,15 @@ console.log(`${obj.x + obj.y}`)
```
如果模板字符串中的变量没有声明,将报错。
```javascript
var msg = `Hello, ${place}`;
// throws error
```
模板字符串使得字符串与变量的结合,变得容易。下面是一个例子。
```javascript
@@ -412,3 +421,112 @@ if (x > MAX) {
}
```
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。
```javascript
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b}`;
```
上面代码中,模板字符串前面有一个函数tag,整个表达式将返回tag处理模板字符串后的返回值。
函数tag依次接受三个参数。第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。第一个参数之后的参数,都是模板字符串各个变量被替换后的值。
- 第一个参数:['Hello ', ' world ']
- 第二个参数: 15
- 第三个参数:50
```javascript
var a = 5;
var b = 10;
function tag(s, v1, v2) {
console.log(s[0]); // "Hello "
console.log(s[1]); // " world "
console.log(v1); // 15
console.log(v2); // 50
return "OK";
}
tag`Hello ${ a + b } world ${ a * b}`;
// "OK"
```
下面是一个更复杂的例子。
```javascript
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)"
```
上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。
处理函数的第一个参数,还有一个raw属性。它也是一个数组,成员与处理函数的第一个参数完全一致,唯一的区别是字符串被转义前的原始格式,这是为了模板函数处理的方便而提供的。
```javascript
tag`First line\nSecond line`
```
上面代码中,tag函数的第一个参数是一个数组`["First line\nSecond line"]`,这个数组有一个raw属性,等于`["First line\\nSecond line"]`,两者唯一的区别就是斜杠被转义了。
function tag(strings) {
console.log(strings.raw[0]);
// "First line\\nSecond line"
}
## String.raw()
String.raw方法,往往用来充当模板字符串的处理函数,返回字符串被转义前的原始格式。
```javascript
String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"
String.raw`Hi\u000A!`;
// 'Hi\\u000A!'
```
String.raw方法也可以正常的函数形式使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。
```javascript
String.raw({ raw: 'test' }, 0, 1, 2);
// 't0e1s2t'
// 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
```