1.字符串的扩展
- codePointAt() 能正确处理四个字节存储的字符,返回一个字符的码点
- fromCodePoint() 可以正确的识别32位的utf-16字符
- for... of 字符串的遍历接口
- at() 正确识别uncode大于0xFFFF的字符,返回正确的字符
- includes() 是否包含参数字符串,返回布尔值
- startsWith() 参数字符串是否在字符串头部,返回布尔值
- endsWith() 参数字符串是否在字符串尾部,返回布尔值
- repeat() 返回一个新的字符串,表示将原字符重复n次
- padStart() 头部补全字符串,接受两个参数,第一个参数表示字符串的最小长度,第二个参数表示用来补全的字符串
- padEnd() 尾部补全字符串,接受两个参数,第一个参数表示字符串的最小长度,第二个参数表示用来补全的字符串
- 模板字符串,用反引号标识来表示
- 标签模板:模板字符串紧跟在一个函数后面,该函数用来处理模板字符串,这被称为标签模板
tag函数的第一个参数是一个数组,为模板字符串中那些没有被变量替换的部分,其他的参数为各个变量被替换之后的值。
2. 正则的扩展
3.数值的扩展
-
1.Number
- 二进制和八进制的表示: 分别用前缀0b(或0B)和0o(或0O)表示。
- Number.isFinite(), Number.isNaN(): Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。 Number.isNaN()用来检查一个值是否为NaN。 它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
- Number.parseInt(),Number.parseFloat(): ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
- Number.isInteger(): 判断一个数值是否为整数。
- Number.EPSILON(): 表示1与大于1的最小浮点数之间的差。 等于 2 的 -52 次方。
- Number.isSafeInteger(): JavaScript 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值. ES6 引入了Number.MAX_SAFE_INTEGER = 2^53-1 和Number.MIN_SAFE_INTEGER = -(2^53-1) 这两个常量,用来表示这个范围的上下限。
-
2.Math
- Math.trunc(): 去除一个数的小数部分,返回整数. 对于非数值,Math.trunc内部使用Number方法将其先转为数值。 对于空值和无法截取整数的值,返回NaN。
- Math.sign(): 判断一个数是正数,负数,还是零。返回值有5种:(1)参数为正数,返回+1;(2)参数为负数,返回-1;(3)参数为 0,返回0;(4)参数为-0,返回-0;(5)其他值,返回NaN。
- Math.cbrt(): 计算一个数的立方根。
- Math.clz32(): 返回一个数的 32 位无符号整数形式有多少个前导 0。
- Math.imul(): 返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
- Math.fround(): 返回一个数的32位单精度浮点数形式。Math.fround方法的主要作用,是将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致)。 对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。
4. 函数的扩展
-
1.函数参数的默认值
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
- 好处:
(1) 阅读代码的人,能够立刻意识到哪个参数是可以省略的。
(2)便于代码的优化,即使直接拿掉这个参数,也不会影响代码的执行。 - 基本用法:直接写在参数定义的后面
- 特点:
(1)参数变量默认是声明的,所以不能用let,const再次声明
(2)使用参数默认值时,函数不能有同名参数
(3)函数参数默认值不是传值的,而是每次都计算默认值表达式的值,所以函数参数默认是惰性传值的。 -
2. rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
rest参数后面不能有别的参数(rest参数只能是最后一个参数),函数的length不包括rest参数,
es6规定,函数参数使用了默认值,解构赋值,扩展运算符,则函数体内部不能声明为严格模式。
这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。
解决办法:
(1).设置全局性的严格模式
(2).把函数包在一个无参数的立即执行函数里
-
- name 属性
返回函数的名字。
- 在es5中,如果把匿名函数赋值给一个变量,name属性返回的是空值,es6中则返回函数的实际名字。
- 如果将一个具名函数赋值给变量,es5和es6都返回函数的具体名称。
- Function构造函数返回的函数实例,name属性的值为anonymous。
- bind返回的函数,name属性值会加上bound前缀。
- name 属性
-
- 箭头函数
- ES6 允许使用“箭头”(=>)定义函数。
- 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
箭头函数的使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
除了this,箭头函数中也不存在arguments,super, new.target.
由于箭头函数不存在this,所以也不能调用apply(),call(), bind()来改变this的指向
(5)不适合使用箭头函数的场景: 定义函数的方法,且该方法中包含this
需要动态绑定this的时候,也不应该使用箭头函数
如果函数体复杂,有许多行,或者有很多的读写操作,不是单纯的计算值,也不应该使用箭头函数,应该使用普通函数,提高代码的可读性。
-
6. 双冒号运算符
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
-
7. 尾调用优化
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。
这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5)
上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
还有一个比较著名的例子,就是计算 Fibonacci 数列,也能充分说明尾递归优化的重要性。
非尾递归的 Fibonacci 数列实现如下。
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
递归函数的改写
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量total,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1?
两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1);
}
factorial(5) // 120
函数式编程有一个概念,叫做柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
factorial(5) // 120
上面代码通过柯里化,将尾递归函数tailFactorial变为只接受一个参数的factorial。
第二种方法就简单多了,就是采用 ES6 的函数默认值。
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
上面代码中,参数total有默认值1,所以调用时不用提供这个值。
-
8. 函数参数的尾逗号
es6允许函数的最后一个参数有尾逗号(trailing comma)。