# ES6数据类型新增的属性和方法
# 变量的解构赋值
- 数组的解构赋值
let [a,b,c] = [1,2,3] // a=1,b=2,c=3
let [head,...tail] = [1,2,3,4] // [2,3,4]
let [x,y,...z] = ['a'] // x='a',y=undefined,z=[]
// 默认值
let [foo=true] = [] // foo=true
let [x,y='b'] = ['a'] // x='a',y='b'
let [x,y='b'] = ['a',undefined] // x='a',y='b'
let [x=1] = [undefined] // x = 1
let [x=1] = [null] // x= null Es6中使用严格相等运算符来判断一个位置是否有值,所以如果一个数组成员!==undefined,默认值是不会生效的
2
3
4
5
6
7
8
9
10
- 对象的解构赋值
var {foo:baz} = {foo:'aaa',bar:'bbb'}
baz // 'aaa'
let node = {
loc: {
start: {
line: 1,
column: 5
}
}
}
var {loc,loc:{start},loc:{start:{line}}} = node; // line 1
// 对象解构也可以指定默认值
var {x=3} = {} // x=3
var {x,y=5} = {x:1} // x=1,y=5
var {x:y=3} = {} //y=3
var {x:y=3} = {x:5} // y=5
// 如果将一个已经声明的变量用于解构赋值,必须非常小心
let x;
{x} = {x:1} // SyntaxError: syntax error
// 正确
let x;
({x} = {x:1}) // ES6规则是,只要有可能导致解构歧义就不得使用圆括号
// 解构同时重命名
const school = {
classes: {
stu: {
name:'Blob',
age: 24
}
}
}
const {classes: {stu: {name: newName } } } = school
newName // Blob
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
TIP
- 对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。
let {foo:baz} = {foo:'aaa',bar: 'bbb'}
baz // 'aaa'
foo // error:foo is not defined
2
3
上面的代码中,
foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
let obj = {
p: [
'Hello',
{y: 'World'}
]
}
let {p: [x,{y}]} = obj
x// 'Hello'
y // World
2
3
4
5
6
7
8
9
这时
p
是模式不是变量,不会被赋值。如果要p
被赋值,可以这样:
let {p,p:[x,{y}]} = obj // 因为相对应let {p: p,p: [x,{y}]} = obj,后面那个p才是变量
- 默认值生效的条件是,对象的属性值严格等于
undefined
var {x=3} = {x:undefined}; // x=3
var {x=3} = {x:null} // x=null
2
- 数组本质是对象,因此可以对数组进行对象属性的解构
let arr = [1,2,3]
let {0: first,[arr.length-1] : last} = arr;
first//1
last//3
2
3
4
5
- 字符串解构
const [a,b,c,d,e] = 'hello'
a // h
b // e
...
2
3
4
- 数值/布尔值
// 解构赋值时,如果等号右边是数值/布尔值,先转对象
let {toString: a} = 123 ; // = true
s === Number.prototype.toString // true
2
3
只要等号右边部署对象/数组就会先将其转为对象。由于
undefined/null
无法转为对象,所以解构赋值时回报错TypeError
- 函数参数解构
function add([x,y]) {
return x + y
}
add([1,2]) // 3
[[1,2],[3,4]].map(([a,b]) => a +b)
// [3,7]
// undefined会触发函数参数的默认值
[1,undefined,3].map((x ='yes') => x) // [1,'yes',3]
//函数参数使用默认值 变量x,y指定默认值
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]
// 函数move参数指定默认值
function move({x,y} = {x:0,y:0}) {
reutrn [x,y]
}
move({x:3,y:8}) // [3,8]
move({x:3}) // [3,undefined]
move({}) // [undefined,undefined]
move() // [0,0]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TIP
// 超级猩猩笔试题
//根据函数默认值和解构
function func() {
return {
a,b
}
}
console.log(func()) // 输出{a:1,b:2}
console.log(func({a:3})) // 输出{a:3,b:456}
console.log(func({})) // 输出{a:123,b:456}
// 所以func函数参数如下
function func({a:a=123,b:b=456} = {a:1,b:2}) {
return {
a,
b
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 圆括号问题
不能使用
- 变量声明语句
let [(a)] = [1]
1- 函数参数
- 赋值语句的模式
({p:a}) = {p:42} [({p:a}),{x:c}] = [{},{}]
1
2
3可以使用
- 赋值语句的非模式部分
[(b)] = [3]
1
# 字符串的扩展
- 每个字符固定两个字节
- 字符串可以使用
for...of
遍历 includes()
返回布尔值,表示是否找到参数字符串;startsWith/endsWith()
表示参数字符串是否在源字符串头/尾部。这三个方法都支持第二个参数,表示开始搜索的位置。
使用第二个参数n时,
endsWith
的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对第n个位置到字符串结束位置之间的字符。
repeat()
表示重复原字符串n次并返回新字符串。如果参数为小数会取整。参数为负数/Infinity
就会报错。参数为-1~0
之间就等同于0
。参数NaN
等同于0
;如果参数为字符串就先转为数值。padStart/padEnd()
,有两个参数,第一个参数指定字符串最小长度,第二个指定补充的字符串。
'x'.padStart(5,'ab') // ababx
- 如果原字符串长度比第一个参数大或相等,则返回原字符串。
- 补全后长度超过指定长度,则截掉超出的补全字符串
'abc'.padStart(10, '0123456789') // '0123456abc'
- 省略第二个参数,则用空格补全。
- 模板字符串
- 大括号内可以放任意的js表达式,可以进行运算,以及引用对象属性
- 如果大括号内的值不是字符串,按照一般规则转为字符串。比如为对象则默认调用对象的
toString
方法。 - 如果大括号内为字符串,则原样输出。
# 数值的扩展
- 八进制用
0b/0B/0o/0o
表示。如果要使用0b/0x
前缀的字符串数值转为十进制数值,要使用Number
方法。 Number.isFinite()
判断一个数值是否有限,有限返回true
。NaN
返回false
,传入其他类型都返回false
Number.isNaN()
检查一个值是否为NaN,非NaN
一律返回false
Number.isNaN(9/NaN) // true
Number.isNaN('true'/0) // true
Number.isNaN('true'/'true') // true
2
3
- ES6将
parseInt/parseFloat()
方法从全局转移到了Number
对象上,为了减少全局对象的方法,使得语言逐步模块化。方法使用没有变化。 Number.isInteger()
判断一个值是否为整数,传入非数值类型都返回false
。类似3
和3.0
这样将视为同一个值Number.EPSILON
。引入一个很小的数,目的在于为浮点数计算设置一个误差范围。浮点数计算结果小于该数就可以认为得到正确的结果。Number.isSafeInteger()
用来判断一个数是否在安全整数范围(-2^53~2^53
)因为超过这个范围就无法精确表示。
TIP
js中所有数字都保存成
64
位浮点数,所以整数的精确度只能到53
个二进制位。大于这个范围的整数就无法精确表示。所以引入新的数据类型Integer
(整数)来解决这个问题。只能用来表示整数,没有位数限制,任何位数的整数都可以精确表示。为了和Number
类型区别必须使用后缀n
表示。 二进制,八进制,十六进制表示法后缀也要加上。
1n + 2n = 3n
typeof 3n // 'bigint'
2
js提供
Integer
对象用来生成Integer
类型数值。转换规则如Number()
一致。
Integer(123) // 123n
Integer('123') // 123n
Integer(true) // 1n
Integer(false) // 0n
new Integer() // TypeError
2
3
4
5
6
在数学运算符号中,
Integer
类型的+ - * **
二元运算符和Number
类型行为一样,除法则舍去小数部分返回整数
9n / 5n // 1n
1n + 1 // 报错,不能混合运算
2
3
几乎所有Number
运算符都可以用在该类型中,除了不带符号的右移位运算符>>>
和一元的求正运算符+
,因为会报错。前者是因为该类型没有最高位,后者是在asm.js
总是报错或者返回Number
类型。
相等运算符会改变数据类型,也是不允许混合使用。精确相等运算符===
则不会类型改变可以混合使用
0n == 0 // TypeError
0n == false // TypeError
0n === 0 // false
2
3
4
# Math对象的扩展
Math.trunc()
去除一个小数的小数部分,返回整数部分。对于非数值的参数,用Number()
转换为数值。对于空值和无法截取整数的值返回NaN
Math.trunc('12.abc') // NaN
Math.sign()
返回一个数是正数/负数/0。对于非数值的则转为数值,不能转为数值的则返回NaN
。正数返回+1,负数为-1。-0则返回-0
// 没有这个方法,可以这样
Math.sign = Math.sign || function(x) {
x = +x; //类型转换
if(x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1
}
2
3
4
5
6
7
8
Math.cbrt()
返回一个数的立方根Math.hypot()
返回所有参数的平方和的平方根- 指数运算符
**
。对于特别大的运算结果,和Math.pow()
计算结果有略微差异。
# 对象的扩展
Object.assign()
该操作和对象扩展运算符执行结果是一样的。区别在于,
Object.assign
将会修改它的第一个参数对象,这个修改可以触发其第一个参数对象的setter
。而扩展运算符会创建一个对象副本,而不会修改任何值,在对不可变性有要求的情况下是一种更好的选择。
# 函数扩展
- 默认参数为默认声明的,函数体内不能用
let/const
再次声明。默认参数应该是函数的尾参数,如果非尾部的参数设置默认值,实际上这个参数是无法忽略的,除非显式输入undefined
。输入null
没有这个效果。使用参数默认值函数不能有同名参数
function foo(x=5,y=6) {
console.log(x,y)
}
foo(undefined,null) // 5 null
2
3
4
function foo(x,x,y=1) {
...
}
// SyntaxError: Duplicate parameter name not allowed in this context
2
3
4
- 函数的
length
返回形参的个数。如果有默认值的参数,则减去。形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数。。与之对比的是,arguments.length
是函数被调用时实际传参的个数。
(function (a) {}).length // 1
(function (a=5) {}).length // 0
(function(...args) {}).length // 0 剩余参数rest也不会计入length属性
2
3
4
如果设置了默认值的参数不是尾参数,那么length
属性也不再计入后面的参数。
(function (a=0,b,c){}).length // 0
(function(a,b=1,c){}).length // 1
2
rest
参数之后不能再有其他参数,否则会报错。- ES2016做了规定,只要函数参数使用了默认值,解构赋值或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
- 函数的
name
属性返回该函数的函数名。
- 如果将一个匿名函数赋值给一个变量,ES5的
name
属性会返回空字符串,而ES6返回实际的函数名
var f = function(){}
f.name // '' es5
f.name // 'f' es6
2
3
- 如果将一个具名函数赋值给一个变量,
name
属性返回具名函数的名字,而不是变量名。 Function
构造函数返回的函数实例,name
属性返回anonymous
bind
返回的函数,name
属性会加上bound
前缀
# 箭头函数
TIP
- 箭头函数使用的注意事项:
- 函数体内的
this
对象就是定义时所在的对象,而不是使用所在的对象。 - 不可以当作构造函数。也就是说,不可以使用
new
命令,否则会抛出错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。可以使用rest
剩余参数。 - 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数。
- 函数体内的
this
指向的固定化并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为没有this
,所以不能用作构造函数。
箭头函数转为ES5
// ES6
function foo() {
setTimeout(() => {
console.log('id:',this.id)
},100)
}
//es5
function foo() {
var _this = this
setTimeout(function() {
console.log('id:',_this.id)
},100)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
除了
this
以下三个变量也是箭头函数中不存在的,分别指向外层函数的对应变量:arguments,super,new.target
。
function foo() {
setTimeout(() => {
console.log('args:'+arguments)
},100)
}
foo(2,4,6,8)
//args: [2,4,6,8]
2
3
4
5
6
7
箭头函数内部的变量
arguments
其实就是函数foo
的arguments
变量。
TIP
在使用动态回调时,对于类似下面这种对回调函数的
this
有特殊场景需求的用法,箭头函数的this
便无法满足其要求。
const btn = document.getElementById('btn')
btn.addEventListener('click',() => {
console.log(this === window)
})
2
3
4
# 绑定this
函数绑定运算符是并排的双冒号
(::)
,双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this
对象)绑定到右边的函数上。
foo::bar
// 等同于
bar.bind(foo)
2
3
- 如果双冒号左边为空,右边为一个对象的方法,则等同于将该方法绑定到该对象上。
- 由于双冒号运算符返回的还是原来的对象,因此可以采用链式写法。
# 尾调用
函数最后一步是调用另一个函数
function f(x) {
return g(x)
}
// 这些就不是了
function f(x) {
let y = g(x)
return y;
}
function f(x) {
return g(x) + 1
}
// 都是调用了函数之后还有接下来的操作,所以不是尾调用。
2
3
4
5
6
7
8
9
10
11
12
13
14
TIP
函数调用会在内存形成一个调用记录,又称调用帧,保存调用位置和内部变量等信息。所有调用帧会形成一个调用栈。尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置,内部变量等信息就不会再用到了,直接在内层函数的调用帧取代外层函数的即可。
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行尾调用优化。
尾调用自身就是尾递归。对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
function factorial(n) {
if(n===1) return ;
return n*factorial(n - 1)
}
factorial(5) // 120
2
3
4
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)
2
3
4
5
只要使用尾递归,就不会发生栈溢出,相对节省内存。
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
2
3
4
5
6