您可以通过左右滑屏查看更多笔记内容。。

Es6字符串的新增方法

12 Sep 2019 . category: tech .
更多笔记

字符串的新增方法

1.String.fromCodePoint()

  • 用于从 Unicode 码点返回对应字符,可以识别码点大于0xFFFF的字符;

    console.log(String.fromCodePoint(0x20BB7));   // "𠮷"
    

2. String.raw()

  • ES6 还为原生的 String 对象;往往用于模板字符串的处理方法。

    String.raw`Hi\n${2+3}!`;
    // 返回 "Hi\\n5!"
      
    String.raw`Hi\u000A!`;
    // 返回 "Hi\\u000A!"
    
  • String.raw()`方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。

    String.raw({ raw: 'test' }, 0, 1, 2);     // 't0e1s2t'
    // 等同于
    String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
    

3.实例方法:codePointAt()

  • 作用:正确处理 4 个字节储存的字符,返回一个字符的码点。

  • 对于那些两个字节储存的常规字符,它的返回结果与charCodeAt()方法相同。

    var s = "𠮷";
    s.length // 2
    s.charAt(0) // ''
    s.charAt(1) // ''
    s.charCodeAt(0) // 55362
    s.charCodeAt(1) // 57271
    -------------------------------
    let s = '𠮷a';
    s.codePointAt(0) // 134071
    s.codePointAt(1) // 57271
    s.codePointAt(2) // 97
    
  • codePointAt()方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString()方法转换一下。

    let s = '𠮷a';
    s.codePointAt(0).toString(16) // "20bb7"
    s.codePointAt(2).toString(16) // "61"
    
  • 字符a在字符串s的正确位置序号应该是 1,但是必须向codePointAt()方法传入 2。解决这个问题的一个办法是使用for...of循环,因为它会正确识别 32 位的 UTF-16 字符;

    let s = '𠮷a';
    for (let ch of s) {
      console.log(ch.codePointAt(0).toString(16));
    }
    // 20bb7
    // 61
    
  • 使用扩展运算符(...)进行展开运算。

    let arr = [...'𠮷a']; // arr.length === 2
    arr.forEach(
      ch => console.log(ch.codePointAt(0).toString(16))
    );
    // 20bb7
    // 61
    
  • 测试一个字符是由两个字节还是由四个字节组成的?

    function is32Bit(c) {
      return c.codePointAt(0) > 0xFFFF;
    }
      
    is32Bit("𠮷") // true
    is32Bit("a") // false
    

4.实例方法:normalize()

  • 作用:将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。normalize方法目前不能识别三个或三个以上字符的合成

  • 案例:``O(\u004F)和ˇ(\u030C)合成Ǒ`(\u004F\u030C)

    '\u01D1'.normalize() === '\u004F\u030C'.normalize()
    // true
    
  • 参数:指定normalize的方式:
  • NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。
    • NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
    • NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
    • NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
    • NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。

5.实例方法:includes(), startsWith(), endsWith() –原先的indexOf方法

  • 确定一个字符串是否包含在另一个字符串中方法?

    • includes():返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • 参数:支持两个,第一个参数:要查找的字符串;第二个参数:开始搜索的位置。

    let s = 'Hello world!';
    console.log(s.startsWith('world', 6)) // true
    console.log(s.endsWith('Hello', 5)) // true
    console.log(s.includes('Hello', 0))  // true
    

    endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束

6.实例方法:repeat()

  • 作用:repeat方法返回一个新字符串,表示将原字符串重复n次。
  • 如果参数是小数,会被取整;
  • 如果参数是负数或者Infinity,会报错;
  • 如果参数是 0 到-1 之间的小数,则等同于 0.

  • 参数NaN等同于 0;

  • 如果repeat的参数是字符串,则会先转换成数字。

    'hello'.repeat(2) 		// "hellohello"
    'na'.repeat(Infinity)   // RangeError
    'na'.repeat(-1)         // RangeError
    'na'.repeat(-0.9) 		// ""
    'na'.repeat('3') 		// "nanana"
    

7.实例方法:padStart(),padEnd()

  • 作用:实现字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

  • 参数:第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

  • 如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。

  • 如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。

  • 如果省略第二个参数,默认使用空格补全长度。

    'x'.padStart(5, 'ab') 		// 'ababx'
    'x'.padEnd(5, 'ab') 		// 'xabab'
    'xxx'.padStart(2, 'ab')		// 'xxx'
    'xxx'.padEnd(2, 'ab') 		// 'xxx'
    'abc'.padStart(10, '0123456789')     // '0123456abc'
    'x'.padStart(4) // '   x'
    'x'.padEnd(4) // 'x   '
    
  • 用途:

    • 为数值补全指定位数。下面代码生成 10 位的数值字符串。

      '1'.padStart(10, '0') // "0000000001"
      '12'.padStart(10, '0') // "0000000012"
      '123456'.padStart(10, '0') // "0000123456"
      
    • 提示字符串格式。

      '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
      '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
      

8.实例方法:trimStart(),trimEnd()

  • 作用:trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

    const s = '  abc  ';
    s.trim() 			// "abc"
    s.trimStart() 		// "abc  "
    s.trimEnd() 		// "  abc"
    
  • 这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。

9.实例方法:matchAll()

  • 作用:返回一个正则表达式在当前字符串的所有匹配;


正则的扩展

1.RegExp 构造函数

ES5 中的构造函数:

  1. 第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。

    var regex = new RegExp('xyz', 'i');
    // 等价于
    var regex = /xyz/i;
    
  2. 第二种情况是,参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。

    var regex = new RegExp(/xyz/i);
    // 等价于
    var regex = /xyz/i;
    

ES6中不允许使用第二个参数添加修饰符,直接报错;

  • 如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

    new RegExp(/abc/ig, 'i').fla
    gs     // "i"原有正则对象的修饰符是ig,它会被第二个参数i覆盖。
    

2.字符串的正则方法 match()replace()search()split()

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

3.u 修饰符

作用:含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

  • 点字符(.)在正则表达式中,含义是除了换行符以外的任意单个字符。

    对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符。

    var s = '𠮷';
    /^.$/.test(s) 		// false
    /^.$/u.test(s) 		// true
    /^.$/u.test("1") 	// true
    
  • Unicode 字符表示法:用大括号表示 Unicode 字符

    /\u{61}/.test('a') // false
    /\u{61}/u.test('a') // true
    /\u{20BB7}/u.test('𠮷') // true
    
  • 量词:使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。

    /a{2}/.test('aa') // true
    /a{2}/u.test('aa') // true
    /𠮷{2}/.test('𠮷𠮷') // false
    /𠮷{2}/u.test('𠮷𠮷') // true
    
  • 预定义模式:u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的 Unicode 字符。

    /^\S$/.test('𠮷') // false
    /^\S$/u.test('𠮷') // true
    

    \S是预定义模式,匹配所有非空白字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的 Unicode 字符。

    案例:返回字符串的长度的函数:

    var s1 = '𠮷';
    console.log(/^.$/.test(s1) );    // false
    console.log(/^.$/u.test("1"));   // true
    function codePointLength(text) {
        var result = text.match(/[\s\S]/gu);
        return result ? result.length : 0;
    }
    let s = '𠮷𠮷';
    console.log(s.length);            //4
    console.log(codePointLength(s));  //2
    
  • i修饰符 \u004B\u212A都是大写的K

    /[a-z]/i.test('\u212A') // false
    /[a-z]/iu.test('\u212A') // true
    

4.RegExp.prototype.unicode 属性:表示是否设置了u修饰符。

const r1 = /hello/;
const r2 = /hello/u;

r1.unicode // false
r2.unicode // true

5.y 修饰符 粘连”(sticky)修饰符。

全局匹配,后一次匹配从上一次匹配成功的下一个位置开始,确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

  • exec():用于检索字符串中的正则表达式的匹配。

    1. 返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
    2. 当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。
    3. 重要事项:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0。
  • var s = 'aaa_aa_a';
    var r1 = /a+/g;
    var r2 = /a+/y;
      
    console.log(r1.exec(s)) 	// ["aaa"]
    console.log(r2.exec(s)) 	// ["aaa"]
    console.log(r1.exec(s)) 	// ["aa"]
    console.log(r1.exec(s)) 	// ["a"]
    console.log(r1.exec(s)) 	// ["null"]
    console.log(r2.exec(s))		// null
    

6. RegExp.prototype.sticky 属性

表示是否设置了y修饰符

var r = /hello\d/y;
r.sticky // true

7.RegExp.prototype.flags 属性

返回正则表达式的修饰符;

// ES5 的 source 属性
// 返回正则表达式的正文
/abc/ig.source
// "abc"
// ES6 的 flags 属性
// 返回正则表达式的修饰符
/abc/ig.flags
// 'gi'

8.s 修饰符:dotAll 模式

行终止符,就是该字符表示一行的终结,以下四个字符属于“行终止符”。

  • U+000A 换行符(\n
  • U+000D 回车符(\r
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)

dotAll模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。

const re = /foo.bar/s;
// 另一种写法
// const re = new RegExp('foo.bar', 's');
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'

/s修饰符和多行修饰符/m不冲突,两者一起使用的情况下,.匹配所有字符,而^$匹配每一行的行首和行尾。

9.后行断言

“后行断言”正好与“先行断言”相反,x只有在y后面才匹配,必须写成/(?<=y)x/。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/。“后行否定断言”则与“先行否定断言”相反,x只有不在y后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!\$)\d+/

/\d+(?=%)/.exec('100% of US presidents have been male')  	// ["100"]先行断言
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill')  // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90')                // ["90"]

10.Unicode 属性类

\p{...}\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true       \p{Script=Greek}指定匹配一个希腊文字母,所以匹配π成功。

Unicode 属性类要指定属性名和属性值。

\P{…}\p{…}的反向匹配,即匹配不满足条件的字符。

1.匹配所有十进制字符
const regex = /^\p{Decimal_Number}+$/u;
regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true
2.匹配罗马数字。
// 匹配所有数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
3.// 匹配所有空格
\p{White_Space}

// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu

// 匹配所有的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

11.具名组匹配:则表达式使用圆括号进行组匹配。

允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

如果具名组没有匹配,那么对应的groups对象属性会是undefined

  • 解构赋值和替换

    let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
    one  // foo
    two  // bar
    
  • 引用

    const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
    RE_TWICE.test('abc!abc') // true
    RE_TWICE.test('abc!ab') // false
    

12.String.prototype.matchAll


数值的扩展

1.二进制和八进制表示法:

二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。

0b111110111 === 503 // true
0o767 === 503 // true

0b0o前缀的字符串数值转为十进制,要使用Number方法。

Number('0b111')  // 7
Number('0o10')  // 8

2.Number.isFinite(), Number.isNaN()

  • Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity(无限)。

    参数类型不是数值,Number.isFinite一律返回false

  • Number.isNaN()用来检查一个值是否为NaN

    参数类型不是NaNNumber.isNaN一律返回false

  • isFinite(25) // true    传统方法先调用Number()将非数值的值转为数值,再进行判断,
    isFinite("25") // true
    Number.isFinite(25) // true
    Number.isFinite("25") // false
      
    isNaN(NaN) // true
    isNaN("NaN") // true
    Number.isNaN(NaN) // true
    Number.isNaN("NaN") // false
    Number.isNaN(1) // false
    

3.Number.parseInt(), Number.parseFloat()

// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

4.Number.isInteger()

  • 作用:用来判断一个数值是否为整数。

  • JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

  • JavaScript 中数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位);

    1. 如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。

      Number.isInteger(3.0000000000000002) // true
      
    2. 如果一个数值的绝对值小于Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨的最小值,会被自动转为 0。这时,Number.isInteger也会误判.

      Number.isInteger(5E-324) // false
      Number.isInteger(5E-325) // true
      

5.Number.EPSILON

Math.pow(x,y)    //x 的 y 次幂。
  • 一个极小的常量Number.EPSILON:它表示 1 与大于 1 的最小浮点数之间的差。

  • Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

  • Number.EPSILON的实质是一个可以接受的最小误差范围。

    例如:误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。

    //误差检查函数。
    function withinErrorMargin (left, right) {
      return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
    }
      
    0.1 + 0.2 === 0.3 // false
    withinErrorMargin(0.1 + 0.2, 0.3) // true
      
    1.1 + 1.3 === 2.4 // false
    withinErrorMargin(1.1 + 1.3, 2.4) // true
      
    

6.安全整数和 Number.isSafeInteger()

  • JavaScript 能够准确表示的整数范围在-2^532^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

  • Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

  • Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

  • 需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。

    function trusty (left, right, result) {
      if (
        Number.isSafeInteger(left) &&
        Number.isSafeInteger(right) &&
        Number.isSafeInteger(result)
      ) {
        return result;
      }
      throw new RangeError('Operation cannot be trusted!');
    }
      
    trusty(9007199254740993, 990, 9007199254740993 - 990)
    // RangeError: Operation cannot be trusted!
      
    trusty(1, 2, 3)
    // 3
    

7.Math对象的扩展

  1. Math.trunc():用于去除一个数的小数部分,返回整数部分;

  2. Math.sign:用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

    返回五种值。

    • 参数为正数,返回+1
    • 参数为负数,返回-1
    • 参数为 0,返回0
    • 参数为-0,返回-0;
    • 其他值,返回NaN
  3. Math.cbrt:用于计算一个数的立方根。

  4. Math.clz32():将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。

  5. Math.imul:返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。

  6. Math.fround:将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致)。

  7. Math.hypot方法返回所有参数的平方和的平方根。如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。

  8. 对数方法:

    1. Math.expm1(x)返回 e^x - 1,即Math.exp(x) - 1
    2. Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN
    3. Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。
    4. Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。
  9. ES6 新增了 6 个双曲函数方法。

    • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
    • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
    • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
    • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
    • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
    • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)
  10. 指数运算符(**

    • 多个指数运算符连用时,是从最右边开始计算的。

      // 相当于 2 ** (3 ** 2)
      2 ** 3 ** 2
      // 512
      
    • 指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

      let a = 1.5;
      a **= 2;
      // 等同于 a = a * a;
            
      let b = 4;
      b **= 3;
      // 等同于 b = b * b * b;
      


函数的扩展

1.函数参数的默认值

  1. 基本用法

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
  • 阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;
  • 有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
  • 参数变量是默认声明的,所以不能用letconst再次声明。
  • 使用参数默认值时,函数不能有同名参数。
  • 参数默认值是惰性求值的,默认值不是传值的,而是每次都重新计算默认值表达式的值;
  1. 与解构赋值默认值结合使用

    function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
      console.log(method);
    }
    fetch('http://example.com')    //省略了第二个参数;直接得到默认值;
    // "GET"
    
    // 写法一:写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;
    function m1({x = 0, y = 0} = {}) {
      return [x, y];
    }
       
    // 写法二:函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
    function m2({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    ------------------------------------------------
    // 函数没有参数的情况
    m1() // [0, 0]
    m2() // [0, 0]
       
    // x 和 y 都有值的情况
    m1({x: 3, y: 8}) // [3, 8]
    m2({x: 3, y: 8}) // [3, 8]
       
    // x 有值,y 无值的情况
    m1({x: 3}) // [3, 0]
    m2({x: 3}) // [3, undefined]
       
    // x 和 y 都无值的情况
    m1({}) // [0, 0];
    m2({}) // [undefined, undefined]
       
    m1({z: 3}) // [0, 0]
    m2({z: 3}) // [undefined, undefined]
    
  2. 参数默认值的位置:通常情况下,定义了默认值的参数,应该是函数的尾参数。

    如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

    // 例二
    function f(x, y = 5, z) {
      return [x, y, z];
    }
    f() // [undefined, 5, undefined]
    f(1) // [1, 5, undefined]
    f(1, ,2) // 报错
    f(1, undefined, 2) // [1, 5, 2]
    
  3. 函数的length属性,将返回没有指定默认值的参数个数。

    • 也就是说,指定了默认值后,length属性将失真。

      (function (a) {}).length // 1
      (function (a = 5) {}).length // 0
      (function (a, b, c = 5) {}).length // 2
      
    • 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

      (function (a = 0, b, c) {}).length // 0
      (function (a, b = 1, c) {}).length // 1
      
  4. 作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。

    var x = 1;
    function foo(x){
      console.log("1x="+x);
      var x = 3;
      console.log("2x="+x);
    }
    foo();
    //调用函数f00时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是1x输出undefined;
    //   1x=undefined     
    //   2x=3
    
    var x = 1;
    function foo(x, y = function() { x = 2; console.log("1x="+x)}) {
      var x = 3;
      y();
      console.log("2x="+x);
    }
    foo();
    console.log("3x="+x) ;
    // 1x=2
    // 2x=3
    // 3x=1
    
  5. 应用:利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

    function throwIfMissing() {
      throw new Error('Missing parameter');
    }
    function foo(mustBeProvided = throwIfMissing()) {
      return mustBeProvided;
    }
    foo()
    // Error: Missing parameter
    

    如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。

  6. rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

    // arguments变量的写法
    function sortNumbers() {
      return Array.prototype.slice.call(arguments).sort();
    }
    //arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。
       
    // rest参数的写法
    const sortNumbers = (...numbers) => numbers.sort();
    
    • rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

    • 函数的length属性,不包括 rest 参数。

      (function(...a) {}).length  // 0
      (function(a, ...b) {}).length  // 1
      
  7. 严格模式:规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

  8. 函数的name属性,返回该函数的函数名。

    • 将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

      var f = function () {};
      // ES5
      f.name // ""
      // ES6
      f.name // "f"
      
    • 将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。

      const bar = function baz() {};
           
      // ES5
      bar.name // "baz"
           
      // ES6
      bar.name // "baz"
      
    • (new Function).name // "anonymous"   Function构造函数
      
    • bind返回的函数,name属性值会加上bound`前缀。

      foo.bind({}).name // "bound foo"
           
      (function(){}).bind({}).name // "bound "
      
  9. “箭头”(=>)函数:

    // rest 参数与箭头函数结合的例子。
    const numbers = (...nums) => nums;
        
    numbers(1, 2, 3, 4, 5)
    // [1,2,3,4,5]
        
    const headAndTail = (head, ...tail) => [head, tail];
        
    headAndTail(1, 2, 3, 4, 5)
    // [1,[2,3,4,5]]
    

    使用注意:

    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

    (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

    上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

    var id = 21;
    function foo() {
      var id = 21;
      setTimeout(() => {
        var id = 21;
        console.log('id:', this.id);
      }, 100);
    }
    foo.call({ id: 42 });
    //id:42
    
    function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      // 箭头函数
      setInterval(() => this.s1++, 1000);
      // 普通函数
      setInterval(function () {
        this.s2++;
      }, 1000);
    }
        
    var timer = new Timer();
    setTimeout(() => console.log('s1: ', timer.s1), 3100);
    setTimeout(() => console.log('s2: ', timer.s2), 3100);
    // s1: 3
    // s2: 0
    前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。
    
    var handler = {
      id: '123456',
        
      init: function() {
        document.addEventListener('click',
          event => this.doSomething(event.type), false);
      },
        
      doSomething: function(type) {
        console.log('Handling ' + type  + ' for ' + this.id);
      }
    };
    这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。
    

    由于箭头函数使得this从“动态”变成“静态”,下面两个场合不应该使用箭头函数:

    • 第一个场合是定义对象的方法,且该方法内部包括this;
    • 第二个场合是需要动态this的时候,也不应使用箭头函数。
    嵌套的箭头函数
    function insert(value) {
      return {into: function (array) {
        return {after: function (afterValue) {
          array.splice(array.indexOf(afterValue) + 1, 0, value);
          return array;
        }};
      }};
    }
        
    insert(2).into([1, 3]).after(1); //[1, 2, 3]
    

    ==>

    let insert = (value) => ({into: (array) => ({after: (afterValue) => {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }})});
        
    insert(2).into([1, 3]).after(1); //[1, 2, 3]
    
  10. 尾调用优化

    • 尾调用(Tail Call):是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

    • 尾递归:函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

      function Fibonacci (n) {
        if ( n <= 1 ) {return 1};
        return Fibonacci(n - 1) + Fibonacci(n - 2);
      }
      Fibonacci(10) // 89
      Fibonacci(100) // 超时
      Fibonacci(500) // 超时
      
      function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
        if( n <= 1 ) {return ac2};
        return Fibonacci2 (n - 1, ac2, ac1 + ac2);
      }
      Fibonacci2(100) // 573147844013817200000
      Fibonacci2(1000) // 7.0330367711422765e+208
      Fibonacci2(10000) // Infinity
      

8.Function.prototype.toString()

toString()方法返回函数代码本身,不会省略注释和空格。

function /* foo comment */ foo () {}

foo.toString()
// "function /* foo comment */ foo () {}"

9.catch 命令的参数省略

//以前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。
try {
  // ...
} catch (err) {
  // 处理错误
}

==>

//允许catch语句省略参数。
try {
  // ...
} catch {
  // ...
}