From 634ab7c5a2e1b9feb62bab0fbbff2522039b893a Mon Sep 17 00:00:00 2001 From: Ruan Yifeng Date: Fri, 31 Jul 2015 20:46:08 +0800 Subject: [PATCH] edit docs/class --- docs/class.md | 6 +-- docs/function.md | 20 ++++++++- docs/string.md | 109 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 8 deletions(-) diff --git a/docs/class.md b/docs/class.md index 740f067..3f14344 100644 --- a/docs/class.md +++ b/docs/class.md @@ -436,9 +436,9 @@ B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true ``` -上面代码中,子类A的`__proto__`属性指向父类B,子类A的prototype属性的__proto__属性指向父类B的prototype属性。 +上面代码中,子类B的`__proto__`属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。 -这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__属性`)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 +这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__`属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 ```javascript B.prototype = new A(); @@ -477,7 +477,7 @@ class A extends null { } A.__proto__ === Function.prototype // true -A.prototype.__proto__ === null // true +A.prototype.__proto__ === undefined // true ``` 这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。 diff --git a/docs/function.md b/docs/function.md index bece99b..d798dcb 100644 --- a/docs/function.md +++ b/docs/function.md @@ -694,7 +694,7 @@ function f(x){ } ``` -上面代码中,情况一是调用函数g之后,还有别的操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 +上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。 ```javascript function f(x){ @@ -746,6 +746,20 @@ g(3); 这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。 +注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。 + +```javascript +function addOne(a){ + var one = 1; + function inner(b){ + return b + one; + } + return inner(a); +} +``` + +上面的函数不会进行尾调用优化,因为内层函数inner用到了,外层函数addOne的内部变量one。 + ### 尾递归 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 @@ -774,7 +788,9 @@ function factorial(n, total) { factorial(5, 1) // 120 ``` -由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。 +由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScript的实现,都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。 + +目前,只有开启严格模式,尾调用优化才会生效。 ### 递归函数的改写 diff --git a/docs/string.md b/docs/string.md index 9f48741..c83faf6 100644 --- a/docs/string.md +++ b/docs/string.md @@ -127,6 +127,8 @@ ES6对这一点做出了改进,只要将码点放入大括号,就能正确 ES6对正则表达式添加了u修饰符,用来正确处理大于\uFFFF的Unicode字符。 +一旦加上u修饰符号,就会修改下面这些正则表达式的行为。 + **(1)点字符** 点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。 @@ -300,7 +302,9 @@ repeat()返回一个新字符串,表示将原字符串重复n次。 ## 正则表达式的y修饰符 -除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。它的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 +除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。 + +y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。 ```javascript var s = "aaa_aa_a"; @@ -328,6 +332,40 @@ r.exec(s) // ["aa_"] 上面代码每次匹配,都是从剩余字符串的头部开始。 +使用lastIndex属性,可以更好地说明y修饰符。 + +```javascript +const REGEX = /a/g; + +REGEX.lastIndex = 2; // 指定从第三个位置y开始搜索 +const match = REGEX.exec('xaya'); + +match.index +// 3 +REGEX.lastIndex +// 4 +REGEX.exec('xaxa') +// null +``` + +上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。 + +y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。 + +```javascript +const REGEX = /a/y; + +// 第三个位置y不匹配 +REGEX.lastIndex = 2; +console.log(REGEX.exec('xaya')); // null + +// 第四个位置出现匹配 +REGEX.lastIndex = 3; +const match = REGEX.exec('xaxa'); +match.index // 3 +REGEX.lastIndex // 4 +``` + 进一步说,y修饰符号隐含了头部匹配的标志ˆ。 ```javascript @@ -337,6 +375,32 @@ r.exec(s) // ["aa_"] 上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志ˆ在全局匹配中都有效。 +在split方法中使用y修饰符,原字符串必须以分隔符开头。 + +```javascript +// 没有找到匹配 +'x##'.split(/#/y) +// [ 'x##' ] + +// 找到两个匹配 +'##x'.split(/#/y) +// [ '', '', 'x' ] +``` + +后续的分隔符只有紧跟前面的分隔符,才会被识别。 + +```javascript +'#x#'.split(/#/y) +// [ '', 'x#' ] + +'##'.split(/#/y) +// [ '', '', '' ] +``` + +如果同时使用g修饰符和y修饰符,则y修饰符覆盖g修饰符。 + +## 正则表达式的sticky属性 + 与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。 ```javascript @@ -344,7 +408,46 @@ var r = /hello\d/y; r.sticky // true ``` -## Regexp.escape() +## 正则表达式的flags属性 + +ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符。 + +```javascript +// ES5的source属性 +// 返回正则表达式的正文 +/abc/ig.source +// "abc" + +// ES6的flags属性 +// 返回正则表达式的修饰符 +/abc/ig.flags +// 'gi' +``` + +## RegExp构造函数 + +在ES5中,RegExp构造函数只能接受字符串作为参数。 + +```javascript +var regex = new RegExp("xyz", "i"); +// 等价于 +var regex = /xyz/i; +``` + +ES6允许RegExp构造函数接受正则表达式作为参数,这时会返回一个原有正则表达式的拷贝。 + +```javascript +var regex = new RegExp(/xyz/i); +``` + +如果使用RegExp构造函数的第二个参数指定修饰符,则会修改原有的正则表达式。 + +```javascript +new RegExp(/abc/ig, 'i').flags +// "i" +``` + +## RegExp.escape() 字符串必须转义,才能作为正则模式。 @@ -360,7 +463,7 @@ escapeRegExp(str) 上面代码中,str是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。 -已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为[`Regexp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。 +已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化,作为RegExp对象的静态方法[`RegExp.escape()`](https://github.com/benjamingr/RexExp.escape),放入ES7。 ```javascript RegExp.escape("The Quick Brown Fox");