From 1352e36b18e8bf8c2330d1d4ac73c4c9ffec3de0 Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 29 May 2015 11:26:11 +0800 Subject: [PATCH] edit iterator && string --- docs/destructuring.md | 21 +++++++- docs/function.md | 18 +++---- docs/iterator.md | 54 ++++++++++++++----- docs/let.md | 15 +++++- docs/string.md | 121 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 181 insertions(+), 48 deletions(-) diff --git a/docs/destructuring.md b/docs/destructuring.md index e178a35..eeb262c 100644 --- a/docs/destructuring.md +++ b/docs/destructuring.md @@ -59,7 +59,7 @@ var [bar, foo] = [1]; ``` -以上几种情况都属于解构不成功,foo的值都会等于undefined。 +以上几种情况都属于解构不成功,foo的值都会等于undefined。这是因为原始类型的值,会自动转为对象,比如数值1转为`new Number(1)`,从而导致foo取到undefined。 另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。 @@ -122,6 +122,22 @@ a // "a" 事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。 +```javascript +function* fibs() { + var a = 0; + var b = 1; + while (true) { + yield a; + [a, b] = [b, a + b]; + } +} + +var [first, second, third, fourth, fifth, sixth] = fibs(); +sixth // 5 +``` + +上面代码中,fibs是一个Generator函数,原生具有Iterator接口。解构赋值会依次从这个接口获取值。 + ## 对象的解构赋值 解构不仅可以用于数组,还可以用于对象。 @@ -190,6 +206,9 @@ x // 3 var {x, y = 5} = {x: 1}; console.log(x, y) // 1, 5 +var { message: msg = "Something went wrong" } = {}; +console.log(msg); // "Something went wrong" + ``` 对象解构可以与函数参数的默认值一起使用。 diff --git a/docs/function.md b/docs/function.md index 4666b61..552462b 100644 --- a/docs/function.md +++ b/docs/function.md @@ -69,19 +69,19 @@ var p = new Point(); ```javascript -fetch(url, { body='', method='GET', headers={} }){ +fetch(url, { body = '', method = 'GET', headers = {} }){ console.log(method); } ``` -上面代码中,传入函数fetch的第二个参数是一个对象,调用的时候可以为它的三个属性设置默认值。这个例子也说明,不仅函数定义时,可以设置参数默认值,而且函数调用时,也可以设置参数默认值。 +上面代码中,传入函数fetch的第二个参数是一个对象,调用的时候可以为它的三个属性设置默认值。 甚至还可以设置双重默认值。 ```javascript -fetch(url, { method='GET' } = {}){ +fetch(url, { method = 'GET' } = {}){ console.log(method); } @@ -95,10 +95,10 @@ fetch(url, { method='GET' } = {}){ // 以下两种写法都是错的 -function f(x=5, y) { +function f(x = 5, y) { } -function f(x, y=5, z) { +function f(x, y = 5, z) { } ``` @@ -107,7 +107,7 @@ function f(x, y=5, z) { ```javascript -function foo(x=5, y=6){ +function foo(x = 5, y = 6){ console.log(x,y); } @@ -123,8 +123,8 @@ foo(undefined, null) ```javascript (function(a){}).length // 1 -(function(a=5){}).length // 0 -(function(a, b, c=5){}).length // 2 +(function(a = 5){}).length // 0 +(function(a, b, c = 5){}).length // 2 ``` @@ -249,7 +249,7 @@ push(a, 1, 2, 3) ``` -注意,rest参数之后不能再有其他参数,否则会报错。 +注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 ```javascript diff --git a/docs/iterator.md b/docs/iterator.md index f1787c6..a71c4bc 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -8,7 +8,7 @@ JavaScript原有的数据结构,主要是数组(Array)和对象(Object 遍历器(Iterator)就是这样一种机制。它属于一种接口规格,任何数据结构只要部署这个接口,就可以完成遍历操作,即依次处理该结构的所有成员。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得数据结构的成员能够按某种次序排列。在ES6中,遍历操作特指for...of循环,即Iterator接口主要供for...of消费。 -遍历器的遍历过程是这样的:它提供了一个指针,默认指向当前数据结构的起始位置。第一次调用遍历器的next方法,可以将指针指向到第一个成员,第二次调用就指向第二个成员,直至指向数据结构的结束位置。每一次调用,都会返回当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 +遍历器的遍历过程是这样的:它提供了一个指针,默认指向当前数据结构的起始位置。也就是说,遍历器返回一个内部指针。第一次调用遍历器的next方法,可以将指针指向到第一个成员,第二次调用next方法,就指向第二个成员,直至指向数据结构的结束位置。每一次调用,都会返回当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 下面是一个模拟next方法返回值的例子。 @@ -33,9 +33,15 @@ it.next() // { value: undefined, done: true } ``` -上面代码定义了一个makeIterator函数,它的参数是一个数组。调用该函数,就会返回一个对象。这个对象具有一个next方法,每次调用next方法,它的内部指针就会指向数组的下一个成员,并返回一个该成员信息的对象。请特别注意,next方法的返回值的构造:一个具有value和done两个属性的对象。通过这个返回值,我们就可以知道当前成员的值是什么,以及遍历是否结束。在这个例子中,makeIterator函数用来生成遍历器,它返回的那个具有next方法的对象就是遍历器,调用遍历器的next方法,就可以遍历事先给定的数组。 +上面代码定义了一个makeIterator函数,它的作用就是返回数组的遍历器。对数组`['a', 'b']`执行这个函数,就会返回该数组的遍历器it。 -因为遍历器的作用,只是把接口规格加到数据结构之上。所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。 +遍历器it的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。第一次调用,指向a;第二次调用,指向b。 + +next方法返回一个对象,表示当前位置的信息。这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法。 + +总之,遍历器是一个对象,具有next方法。调用next方法,就可以遍历事先给定的数据结构。 + +由于遍历器只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。 ```javascript @@ -60,9 +66,9 @@ it.next().value // '2' 上面的例子中,idMaker函数返回的对象就是遍历器,但是并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。 -总之,所谓Iterator接口,就是指调用这个接口,会返回一个遍历器对象。该对象具备next方法,每次调用该方法,会返回一个具有value和done两个属性的新对象,指向部署了Iterator接口的数据结构的一个成员。 +在ES6中,有些数据结构原生提供遍历器(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构部署了System.iterator属性(详见下文)。凡是部署了System.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器。 -如果使用TypeScript的写法,遍历器接口、遍历器和遍历器返回值的规格可以描述如下。 +如果使用TypeScript的写法,遍历器接口、遍历器和next方法返回值的规格可以描述如下。 ```javascript @@ -83,9 +89,9 @@ interface IterationResult { ### 默认的Iterator接口 -Iterator接口的开发目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(见后文的介绍)。当使用for...of循环,遍历某种数据结构时,该循环会自动去寻找Iterator接口。 +Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。 -ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。也就是说,调用Symbol.iterator方法,就会得到当前数据结构的默认遍历器。Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。 +ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。也就是说,调用Symbol.iterator方法,就会得到当前数据结构的默认遍历器。Symbol.iterator本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一节)。 在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。 @@ -111,17 +117,38 @@ iter.next() // { value: undefined, done: true } ```javascript -class MySpecialTree { - // ... - [Symbol.iterator]() { - // ... - return theIterator; +class RangeIterator { + constructor(start, stop) { + this.value = start; + this.stop = stop; } + + [Symbol.iterator]() { return this; } + + next() { + var value = this.value; + if (value < this.stop) { + this.value++; + return {done: false, value: value}; + } else { + return {done: true, value: undefined}; + } + } +} + +function range(start, stop) { + return new RangeIterator(start, stop); +} + +for (var value of range(0, 3)) { + console.log(value); } ``` -上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器。下面是一个例子。 +上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器。 + +下面是通过遍历器实现指针结构的例子。 ```javascript @@ -159,6 +186,7 @@ Obj.prototype[Symbol.iterator] = function(){ var one = new Obj(1); var two = new Obj(2); var three = new Obj(3); + one.next = two; two.next = three; diff --git a/docs/let.md b/docs/let.md index b1083d3..6d00096 100644 --- a/docs/let.md +++ b/docs/let.md @@ -349,14 +349,25 @@ a = ["Dave"]; // 报错 如果真的想将对象冻结,应该使用Object.freeze方法。 ```javascript - const foo = Object.freeze({}); foo.prop = 123; // 不起作用 - ``` 上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用。 +除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。 + +```javascript +var constantize = (obj) => { + Object.freeze(obj); + Object.keys(obj).forEach( (key, value) => { + if ( typeof obj[key] === 'object' ) { + constantize( obj[key] ); + } + }); +}; +``` + ## 全局对象的属性 全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5规定,所有全局变量都是全局对象的属性。 diff --git a/docs/string.md b/docs/string.md index f54836c..7b8193d 100644 --- a/docs/string.md +++ b/docs/string.md @@ -364,9 +364,9 @@ r.sticky // true ## 模板字符串 -在ES6,输出模板通常是这样写的。 +以前的JavaScript语言,输出模板通常是这样写的。 -```js +```javascript $("#result").append( "There are " + basket.count + " " + "items in your basket, " + @@ -377,7 +377,7 @@ $("#result").append( 上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。 -```js +```javascript $("#result").append(` There are ${basket.count} items in your basket, ${basket.onSale} @@ -385,7 +385,7 @@ $("#result").append(` `); ``` -模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。 +模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。 ```javascript @@ -405,15 +405,40 @@ var name = "Bob", time = "today"; ``` -上面代码中的字符串,都是用反引号表示。如果在模板字符串中嵌入变量,需要将变量名写在`${}`之中。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。 +上面代码中的字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。 ```javascript - var greeting = `\`Yo\` World!`; - ``` -大括号内部可以进行运算,以及引用对象属性。 +如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。 + +```javascript +$("#warning").html(` +

Watch out!

+

Unauthorized hockeying can result in penalties + of up to ${maxPenalty} minutes.

+`); +``` + +模板字符串中嵌入变量,需要将变量名写在`${}`之中。 + +```javascript +function authorize(user, action) { + if (!user.hasPrivilege(action)) { + throw new Error( + // 传统写法为 + // 'User ' + // + user.name + // + ' is not authorized to do ' + // + action + // + '.' + `User ${user.name} is not authorized to do ${action}.`); + } +} +``` + +大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。 ```javascript @@ -445,34 +470,25 @@ console.log(`foo ${fn()} bar`); ``` +如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,默认会调用对象的toString方法。 + 如果模板字符串中的变量没有声明,将报错。 ```javascript - var msg = `Hello, ${place}`; // throws error - ``` -模板字符串使得字符串与变量的结合,变得容易。下面是一个例子。 +## 标签模板 -```javascript - -if (x > MAX) { - throw new Error(`Most ${MAX} allowed: ${x}!`); - // 传统写法为'Most '+MAX+' allowed: '+x+'!' -} - -``` - -模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。 +模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。 ```javascript var a = 5; var b = 10; -tag`Hello ${ a + b } world ${ a * b}`; +tag`Hello ${ a + b } world ${ a * b }`; ``` @@ -546,7 +562,66 @@ msg 上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。 -处理函数的第一个参数,还有一个raw属性。它也是一个数组,成员与处理函数的第一个参数完全一致,唯一的区别是字符串被转义前的原始格式,这是为了模板函数处理的方便而提供的。 +“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。 + +```javascript +var message = + SaferHTML`

${sender} has sent you a message.

`; + +function SaferHTML(templateData) { + var s = templateData[0]; + for (var i = 1; i < arguments.length; i++) { + var arg = String(arguments[i]); + + // Escape special characters in the substitution. + s += arg.replace(/&/g, "&") + .replace(//g, ">"); + + // Don't escape special characters in the template. + s += templateData[i]; + } + return s; +} +``` + +上面代码中,经过SaferHTML函数处理,HTML字符串的特殊字符都会被转义。 + +标签模板的另一个应用,就是多语言转换(国际化处理)。 + +```javascript +i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.` +// Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto. +``` + +模板字符串并不能取代Mustache之类的模板函数,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。 + +```javascript +// 下面的hashTemplate函数 +// 是一个自定义的模板处理函数 +var libraryHtml = hashTemplate` + +`; +``` + +除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。 + +```javascript +java` +class HelloWorldApp { + public static void main(String[] args) { + System.out.println(“Hello World!”); // Display the string. + } +} +` +HelloWorldApp.main(); +``` + +模板处理函数的第一个参数,还有一个raw属性。它也是一个数组,成员与处理函数的第一个参数完全一致,唯一的区别是字符串被转义前的原始格式,这是为了模板函数处理的方便而提供的。 ```javascript