一 let和const
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
1 let与var的区别
- let不存在变量提升
- 块级作用域:ES5 只有全局作用域和函数作用域,没有块级作用域(ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域)
- let不允许在相同作用域内,重复声明同一个变量
2 const常量
const与let一样,唯一区别在于声明的常量不能被修改
3 标题函数声明
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。函数声明语句的行为类似于let,在块级作用域之外不可引用。
二 变量的解构赋值
1 数组解构
es6支持“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值,如果解构不成功,变量的值就等于undefined。
let [a, b, c] = [1, 2, 3] // a=1, b=2, c=3
let [a1, b1=2] = [1] // a1=1, b1=2 //指定默认值
let [d, [e], f] = [1, [2], 3] // 嵌套数组解构 d=1, e=2, f=3
let [g, ...h] = [1, 2, 3] // 数组拆分 g=1, h=[2, 3]
let [i,,j] = [1, 2, 3] // 不连续解构 i=1, j=3
let [k,l] = [1, 2, 3] // 不完全解构 k=1, l=2
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
2 对象解构
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let {a, b} = {a:'aa', b:'bb'} //a='aa' b='bb'
//设置默认值
let {x, y = 5} = {x: 1}; //x= 1 y=5
//允许别名,a的值将失效
let {a:a1,b} = {a:'aa', b:'bb'} //a1='aa' b='bb'
let obj = {a:'aa', b: {c:'c'}}
let {a, b:{c}} = obj // 嵌套解构 a='aa' c='c'
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心
//将一个已经声明的变量用于解构赋值
let x;
({x} = {x: 1});//将一个已经声明的变量用于解构赋值 x=1
{x} = {x: 1};// SyntaxError: syntax error
(2)解构赋值允许等号左边的模式之中,不放置任何变量名
({} = [true, false]);
({} = 'abc');
({} = []);
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;//first:1 last: 3
3 字符串的解构赋值
let [a, b, c] = 'hello' // a='h' b='e' c='l'
let {length : len} = 'hello'; //len: 5
4 数值和布尔值的解构赋值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象
undefined和null无法转为对象,所以对它们进行解构赋值,都会报错
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
5 函数参数的解构赋值
function say({name,age}){
console.log(name + '今年' + age)
}
say({name:'小明',age:18})
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
三、字符串的扩展
1 加强了对 Unicode 的支持
加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。(\u0000~\uFFFF之间)
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
- ‘\172’ === ‘z’ // true
- ‘\x7A’ === ‘z’ // true
- ‘\u007A’ === ‘z’ // true
- ‘\u{7A}’ === ‘z’ // true
2 JSON.stringify() 的改造
根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的JSON.stringify()方法有可能返回不符合 UTF-8 标准的字符串。
如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
3 模板字符串
用反引号(`)标识,字符串中嵌入变量用${}
4 新增方法
String.fromCodePoint()
- ES5 提供String.fromCharCode()方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符。
- ES6 提供String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足
String.fromCharCode(0x20BB7)
//String.fromCharCode()不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符
String.fromCodePoint(0x20BB7)// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'// true
//如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。
fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。
String.raw()
该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义。
String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!
// `foo${1 + 2}bar`等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
实例方法:codePointAt()
,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
let s = '𠮷a';//“𠮷a”视为三个字符
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
//测试一个字符由两个字节还是由四个字节组成的最简单方法。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("𠮷") // true
is32Bit("a") // false
实例方法:normalize()
将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize()// true
实例方法:includes(), startsWith(), endsWith()
let str = 'hello world'
//返回布尔值,表示是否找到了参数字符串
str.includes('r') //true
//返回布尔值,表示参数字符串是否在原字符串的头部
str.startsWith('hello') //true
//返回布尔值,表示参数字符串是否在原字符串的尾部
str.endsWith('d') //true
实例方法:repeat()
//repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'na'.repeat(0) // ""
如果repeat的参数是负数或者Infinity,会报错。参数NaN等同于 0,如果repeat的参数是字符串,则会先转换成数字。
实例方法:padStart(),padEnd()
如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
如果省略第二个参数,默认使用空格补全长度。
实例方法:trimStart(),trimEnd()
对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
let str = ' hello world '
//消除首尾的空格
str.trim() //'hello world'
//消除字符串头部的空格
str.trimStart() //'hello world '
//消除尾部的空格
str.trimEnd() //' hello world'
实例方法:matchAll()
matchAll()方法返回一个正则表达式在当前字符串的所有匹配
四 正则的扩展
1 正则的扩展
ES5 中,RegExp构造函数的参数有两种情况
//两种方式
var regex = new RegExp(/xyz/i);
var regex = new RegExp('xyz', 'i');
var regex = new RegExp(/xyz/, 'i'); //error
ES6 :如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
new RegExp(/abc/ig, 'i').flags // "i"
2 字符串的正则方法
字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。
3 u 修饰符
对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true
//ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为tru
一旦加上u修饰符号,就会修改下面这些正则表达式的行为。
- 对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符。
- ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词
- 使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。
- u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的 Unicode 字符。
- 没有u修饰符的情况下,正则中没有定义的转义(如逗号的转义,)无效,而在u模式会报错。
var s = '𠮷';
/^.$/.test(s) // false
/^.$/u.test(s) // true
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
RegExp.prototy
//unicode属性,表示是否设置了u修饰符。
const r2 = /hello/u;
r2.unicode // true
4 y修饰符
ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
//由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。
RegExp.prototype.sticky 属性
表示是否设置了y修饰符。
var r = /hello\d/y;
r.sticky // true
5 RegExp.prototype.flags 属性
ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符。
// ES5 的 source 属性
/abc/ig.source // "abc"
// ES6 的 flags 属性 返回正则表达式的修饰符
/abc/ig.flags // 'gi'
6 s 修饰符:dotAll 模式
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(该字符表示一行的终结)
- U+000A 换行符(\n)
- U+000D 回车符(\r)
- U+2028 行分隔符(line separator)
- U+2029 段分隔符(paragraph separator)
dotAll模式,即点(dot)代表一切字符。
re=/foo.bar/s
re.test('foo\nbar') // true
/foo.bar/.test('foo\nbar') // false
re.dotAll // true
正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。
7 后行断言
-
“先行断言”指的是,x只有在y前面才匹配,必须写成/x(?=y)/
-
“先行否定断言”指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
ES2018 引入后行断言,V8 引擎 4.9 版(Chrome 62)已经支持。
-
x只有在y后面才匹配,必须写成**/(?<=y)x/**
-
“先行否定断言”相反,x只有不在y后面才匹配,必须写成**/(?<!y)x/**
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]
//字符串替换
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'
“后行断言”的实现,需要先匹配/(?<=y)x/
的x
,然后再回到左边,匹配y
的部分
8 Unicode 属性类
ES2018 引入了一种新的类的写法\p{...}
和\P{...}
,允许正则表达式匹配符合 Unicode 某种属性的所有字符。\p{Number}
甚至能匹配罗马数字。
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
\P{…}
是\p{…}
的反向匹配,即匹配不满足条件的字符。
9 具名组匹配
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
如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>
的写法。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
10 String.prototype.matchAll()
ES2020 增加了String.prototype.matchAll()
方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。
const string = 'test1test2test3';
const regex = /t(e)(st(\d?))/g;
for (const match of string.matchAll(regex)) {
console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
五 数值扩展
1 二进制和八进制表示法
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b
(或0B
)和0o
(或0O
)表示。
2 扩展函数
Number.isFinite(),Number.isNaN()
-
Number.isFinite()
用来检查一个数值是否为有限的(finite),即不是
Infinity -
Number.isNaN()用来检查一个值是否为NaN。
它们与传统的全局方法
isFinite()
和isNaN()
的区别在于,传统方法先调用Number()
将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效。Number.isNaN(9/NaN) // true Number.isFinite(-Infinity); // false
Number.parseInt(),Number.parseFloat()
ES6 将全局方法parseInt()
和parseFloat()
,移植到Number
对象上面,行为完全保持不变
Number.isInteger() :判断一个数值是否为整数
由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger
可能会误判。
Number.EPSILON
ES6 在Number对象上面,新增一个极小的常量Number.EPSILON
。根据规格,它表示 1 与大于 1 的最小浮点数之间的差
- Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了
Number.EPSILON
可以用来设置“能够接受的误差范围”。
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
安全整数和 Number.isSafeInteger()
-
S6 引入了
Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示的整数范围范围的上下限 -
Number.isSafeInteger()
则是用来判断一个整数是否落在这个范围之内。
使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值
Number.isSafeInteger(9007199254740993 - 990)// true
9007199254740993 - 990
// 返回结果 9007199254740002
// 正确答案应该是 9007199254740003
3 Math对象扩展
Math.trunc()
Math.trunc
方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc
内部使用Number
方法将其先转为数值,对于空值和无法截取整数的值,返回NaN
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(null) // 0
Math.trunc(NaN); // NaN
Math.trunc('foo'); // NaN
Math.trunc(); // NaN
Math.trunc(undefined) // NaN
Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
Math.sign()
-
参数为正数,返回
+1
; -
参数为负数,返回
-1
; -
参数为 0,返回
0
; -
参数为-0,返回
-0
; -
其他值,返回
NaN
。 -
如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回
NaN
Math.cbrt()
Math.cbrt()
方法用于计算一个数的立方根。对于非数值,Math.cbrt()
方法内部也是先使用Number()
方法将其转为数值。
Math.clz32()
Math.clz32()
方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
-
对于小数,
Math.clz32
方法只考虑整数部分 -
对于空值或其他类型的值,
Math.clz32
方法会将它们先转为数值,然后再计算
Math.imul()
Math.imul
方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul
方法可以返回正确的低位数值。
如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)
与a * b
的结果是相同的,即该方法等同于(a * b)|0
的效果(超过 32 位的部分溢出)
Math.fround()
Math.fround
方法返回一个数的32位单精度浮点数形式
-
Math.fround
方法的主要作用,是将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致) -
NaN
和Infinity
,此方法返回原值。对于其它类型的非数值,Math.fround
方法会先将其转为数值,再返回单精度浮点数
Math.hypot()
Math.hypot
方法返回所有参数的平方和的平方根。
Math.hypot(3, 4); // 5
如果参数不是数值,Math.hypot
方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。
ES6新增对数方法
-
Math.expm1():返回 ex - 1,即
Math.exp(x) - 1
-
Math.log1p():返回
1 + x
的自然对数,即Math.log(1 + x)
。如果x
小于-1,返回NaN
。 -
Math.log10():返回以 10 为底的
x
的对数。如果x
小于 0,则返回 NaN -
Math.log2():返回以 2 为底的
x
的对数。如果x
小于 0,则返回 NaN。
ES6 新增双曲函数方法
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)
指数运算符
ES2016 新增了一个指数运算符(**
)
2 ** 2 // 4
b **= 3;// 等同于 b = b * b * b;
BigInt 数据类型
BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n
1234n // BigInt
1n + 2n // 3n
typeof运算符对于 BigInt 类!型的数据返回
bigint
BigInt 可以使用负号(-
),但是不能使用正号(+
),因为会与 asm.js 冲突。
六 函数的扩展
1 函数默认参数
如果传入了参数就使用传入的值,如果没有就使用默认值
function sum(a=0, b=0){
return a + b;
}
sum(); //0
sum(1); //1
sum(1,1) //2
2 箭头函数
ES6很有意思的一部分就是函数的快捷写法。也就是箭头函数。
箭头函数最直观的三个特点:
- 不需要 function 关键字来创建函数
- 省略 return 关键字
- 继承当前上下文的 this 关键字,this指向外部作用域
- 不可以new,也就是不能用作构造函数
- 不可以使用argument对象,可以使用rest参数代替
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
- 箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
- 箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
- 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
3 rest参数
ES6 引入 rest 参数(形式为...变量名
)用于获取函数的多余参数,这样就不需要使用arguments对象
function add(...value){
console.log(value)
//[1,2,3,4]
}
add(1,2,3,4)
arguments
对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call
先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
4 函数的 length 属性
指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
length
属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理, rest 参数也不会计入length
属性。
name 属性 函数的name
属性,返回该函数的函数名。
5 尾调用优化
尾调用(Tail Call)指某个函数的最后一步是调用另一个函数。
函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame)所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
-
“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
-
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
-
目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持
5.1 尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
3 函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
七 数组的扩展
es5新增的方法:forEach、map、filter、some、every、reduce
1 扩展运算符
扩展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5
[...document.querySelectorAll('div')]// [<div>, <div>, <div>]
//与正常的函数参数可以结合使用
function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
替代函数的 apply 方法
由于扩展运算符可以展开数组,所以不再需要apply
方法,将数组转为函数的参数了。
Math.max.apply(null, [14, 3, 77])// ES5 的写法
Math.max(...[14, 3, 77])// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);// ES5的 写法
arr1.push(...arr2);// ES6 的写法
扩展运算符还可以将字符串转为真正的数组。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
2 Array.from()将类似数组的对象转为数组
//类似数组的对象
let obj = {
0:'a',
1:'b',
2:'c',
length:3
}
//es5写法
var arr = [].slice.call(obj);
//es6写法
var arr1 = Array.from(obj);
// arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}
只要是部署了 Iterator 接口的数据结构,Array.from
都能将其转为数组。
扩展运算符背后调用的是遍历器接口(Symbol.iterator
),如果一个对象没有部署这个接口,就无法转换。Array.from
方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length
属性。因此,任何有length
属性的对象,都可以通过Array.from
方法转为数组,而此时扩展运算符就无法转换。
3 Array.of方法用于将一组值,转换为数组
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
4 数组实例的 copyWithin()
数组实例的copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
它接受三个参数。
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
5 数组实例的 find() 和 findIndex()
数组实例的find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。
[1, 4, -5, 10].find((n) => n < 0)// -5
数组实例的findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
。
这两个方法都可以接受第二个参数,用来绑定回调函数的this
对象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
6 数组实例的 fill()
fill
方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)// [7, 7, 7]
new Array(3).fill(7)// [7, 7, 7]
7 数组实例的 entries(),keys() 和 values()
ES6 提供三个新的方法——entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历
let arr = ['a','b','c']
//遍历数组的键名
for(let index of arr.keys()){
console.log(index)
}
//0 1 2
//遍历数组的键值
for(let value of arr.values()){
console.log(value)
}
//'a' 'b' 'c'
//遍历数组的键值对
for(let [index,value] of arr.entries()){
console.log(index,value)
}
//0 'a'
//1 'b'
//2 'c'
8 includes()表示是否包含某个值
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
//第二个参数表示搜索的开始位置,负数表示倒数位置
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
9 数组实例的 flat(),flatMap()
数组的成员有时还是数组,Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat() // [1, 2, 3, 4]
[1, 2, [3, [4, 5]]].flat()// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)// [1, 2, 3, 4, 5]
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity
关键字作为参数。
[1, [2, [3]]].flat(Infinity)// [1, 2, 3]
如果原数组有空位,flat()
方法会跳过空位。
flatMap()
方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()
),然后对返回值组成的数组执行flat()
方法。该方法返回一个新数组,不改变原数组
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])// [2, 4, 3, 6, 4, 8]
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
八 对象扩展
1 对象的简写
- 当属性名和属性值相同时,可以省略属性值
- 方法可以省略function
- 对象的属性名和方法名允许为变量或者表达式
let name = '小明'
let age ="age1"
let person = {
name,
[age]:18,
['hei'+'ght']:180,
sayName(){
console.log(this.name)
}
}
person.sayName();
console.log(person)
2 对象的扩展运算符 …
同数组扩展运算符,支持对象解构剩余参数,对象合并,复制对象
3 Object.is()
用来比较两个值是否严格相等,等同于 “===”
//唯一不同之处
+0 === -0 //true
NaN === NaN //false
Object.is(+0, -0) // false
Object.is(NaN,NaN) //true
4 Object.assign()
- 用于对象的合并,第一个参数是目标对象,后面的参数都是源对象
- 如果目标对象与源对象有同名属性,则后面会覆盖前面的属性
var obj1 = {a:1,b:2}
var obj2 = {b:3,c:4}
console.log(Object.assign(obj1,obj2))
//{a:1, b:3, c:4}
let obj = {a:1, b:2}
Object.assign(obj) === obj // true
//参数不是对象会转成对象再返回
typeof Object.assign(2) // "object"
//undefined和null无法转成对象,作为第一个参数会报错
Object.assign(undefined) // 报错
Object.assign(null) // 报错
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
5 Object.setPrototypeOf(),Object.getPrototypeOf()
- Object.setPrototypeOf()用来设置一个对象的prototype对象
- Object.getPrototypeOf()用于读取一个对象的原型对象
//设置obj对象上的__proto__原型对象为proto对象
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
6 Object.keys(),Object.values(),Object.entries()
使用for…of可以遍历对象的键名,键值,键值对
var obj = {
a:1,
b:2,
c:3
}
//传统遍历对象
for(key in obj){
console.log(key) //所有键值
console.log(obj[key]) //所有键值
}
//es6遍历
//所有键名
for( let index of Object.keys(obj) ){
console.log(index)
}
//所有键值
for( let value of Object.values(obj) ){
console.log(value)
}
//所有键值对
for( let [index,value] of Object.entries(obj) ){
console.log(index,value)
}
7 Object.fromEntries()
方法是Object.entries()的逆操作,用于将一个键值对数组转为对象
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
// 特别适合Map结构
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
8 super 关键字
this
关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super
,指向当前对象的原型对象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
9 解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
上面代码中,变量z
是解构赋值所在的对象。获取等号右边的所有尚未读取的键(a
和b
),将它们连同值一起拷贝过来。
解构赋值必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误