-
rest参数
ES6引入rest参数(形式为…变量名),用于获取函数的多余参数,这样就可以省略arguments对象了,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function add (...values) {
let sum - 0;
for (let val of values) {
sum += val
}
return sum
}
add(1, 3, 5, 7)
// 16
上面代码,add可以传入任意个数参数进行求和
rest参数和arguments对象的对比
// arguments
function sortNumbers () {
return Array.prototype.slice.call(arguments).sort();
}
// rest
function sortNumbers (...numbers) {
numbers.sort()
}
上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁
arguments对象不是数组,只是一个类数组,所以使用数组方法必须先用Array.prototype.slice.call先将其转为数组。
而rest对象就不需要了,它是一个真正的数组
利用rest改写push方法
let a = [];
function push (array, ...items) {
items.forEach((item) => {
array.push(item);
})
return array;
}
push(a, 1, 3, 5, 7)
注意, rest参数之后不能再有其他参数(rest必须是最后一个参数)
function foo (a, ...b, c) {
}
函数的length属性不包含rest
(function (a) {}).length // 1
(function (...rest) {}).length // 0
(function (a, ...rest) {}).length // 1
(function (a, ...rest, b) {}).length // error
-
严格模式
从ES5开始,允许函数内部使用严格模式
function test () {
'use strict'
// 整个函数体内部运用严格模式的规定
}
ES2016规定,如果函数使用默认值,扩展运算符,解构赋值内部就不可以再显示的运用严格模式,否则报错。
// 报错
function doSomething(a, b = a) {
'use strict';
// code
}
// 报错
const doSomething = function ({a, b}) {
'use strict';
// code
};
// 报错
const doSomething = (...a) => {
'use strict';
// code
};
const obj = {
// 报错
doSomething({a, b}) {
'use strict';
// code
}
};
这样的规定是因为,严格模式下,都要使用于函数参数和函数体。
但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样有一个不合理的地方。
只有从函数体之中,才能知道参数是否应该被严格模式下执行,但是参数优先于函数体执行
function test (value = 070) {
'use strict'
return value
}
上面代码,参数value的默认值是八进制的070,但是严格模式下不能用前缀0表示八进制,所以应该报错,但是实际上,js内部会先执行成功value = 070,然后进入函数体,发现严格模式,这时会报错。
虽然可以先解析函数体代码,再执行参数,但这样无疑是增加了复杂性,因此标准引入,函数使用扩展,默认值,解构赋值就不能的使用严格模式
但是有两种方法可以变通
使用全局严格模式
'use strict'
function test (a, b = a) {
}
使用无参数立即执行函数
(function () {
'use strict'
return function (value = 42) {
return value;
}
}) ();
- name属性
函数的name属性,返回该函数的函数名
function foo () {}
foo.name
// foo
ES5中,如果将一个匿名函数赋值给一个变量,name会返回空字符串,
但ES6中,会返回改变量的名
let foo = function () {}
// es5
foo.name
// ''
// es6
foo.name
// foo
如果讲一个具名函数赋值给一个变量,则es5和es6的name户型都返回这个具名函数的原本名字
let f = function foo () {}
// es5
f.name
// foo
// es6
f.name
// foo
Function构造函数返回的函数实例,name名为anonymous
(new Function).name
// anonymous
bind返回的函数,name属性值会加上bound前缀
function foo () {}
foo.bind({}).name
// bound foo
(function () {}).bind({}).name
// bound
-
箭头函数
ES6允许使用 (=>) 定义函数
let test = () => {}
// 等同于
function test () {}
let test = function () {}
如果参数部分不需要,或者需要多个参数时,使用() 来代替
let f = () => 3;
// 等同于
let f = function () { return 3 }
let f = (x, y) => x + y;
// 等同于
let f = function (x, y) { return x + y }
如果箭头函数的语句大于一条,需要使用{}包起来,并且需要现实的写return
let f = (x, y) => {
console.log(x, y);
return x + y;
}
f(5, 21)
由于大括号被解释为代码块,如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则报错
// error
let p = () => {car: 'BMW', wife: 'fanfan'}
// correct
let p = () => ({car: 'BMW', wife: 'fanfan'})
下面是一种特殊情况
let p = () => {a : 1}
p()
上面代码,原意是让他返回一个{ a : 1 }对象,但是js会理解成代码块,所以执行的其实是a :1。
这时,a可以被解释为语句的标签,因此实际执行的语句是1;
然后函数没有返回值,就这样结束了!
如果函数不需要返回值,且只有一行语句。可以采用这种写法,不需要写大括号
let p = () => void console.log('hello => fn');
箭头函数可以与解构赋值一起使用
let p = ({first, last}) => first + '' + last;
// 等于
let p = function (person) {
return person.first + '' + person.last
}
箭头函数表达起来通俗易懂,代码量大大减少,并且习惯后爱不释手
let foo = n => n * n
let bar = n => n * 2
箭头函数只要用来简化会提到函数
过去的回调
(function () {
// code
return function () {
// code
return function () {
// code
return function () {
// code
// ...
}
}
}
} ())
使用了箭头函数之后
(function () {
// code
return () => {
//code
return () => {
// code
return () => {
// ...
}
}
}
} ())
// 正常函数
list.map(function (index) {
return index * 2
})
// 箭头函数
list.map(index => index * 2)
let result = arr.sort(function (a, b) {
return a - b;
})
let result = arr.sort((a, b) => a - b)
使用rest参数与箭头函数结合
let numbers = (...rest) => rest;
numbers(1, 2, 3, 4, 5);
// [1, 2, 3, 4, 5]
let numbers1 = (first, ...last) => [first, last];
number1(1, 2, 3, 4, 5)
// [1, [2, 3, 4, 5]]
使用注意点
- 箭头函数内部没有this,它的this来源于定义时被给予的,只要定义的环境没有this,那么箭头函数的this就是undefined
- 箭头函数不能用作构造函数,不能new
- 箭头函数不可以使用arguments对象,可以使用rest替代
- 箭头函数不可以使用yield,也就是不能使用Generator函数
第一点尤为重要,this是可变的,但是在箭头函数中,定义之后他就是固定的。
下面一个例子阐述箭头函数与普通函数的区别
function foo () {
setTimeout(() => {
console.log(this.id)
})
}
var id = 30;
foo.call({id: 21})
// 21
上面代码执行结果是21, 因为箭头函数的this依赖于定义时的环境(对象),所以它找的就是定义时的this,当foo被call了之后会改变this指向。
如果函数是正常执行,会打印window.id
也就是30.
注意:这里不能用let 因为声明在后,使用在前。
在一个就是如果foo()直接执行时找不到this.id的
因为let的变量不归你window所有
箭头函数可以让里面的this,在绑定定义时指定自己的作用域,而不是在执行时
function timer () {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => {
this.s1 ++
}, 1000)
// 普通函数
setInterval(function () {
this.s2 ++
})
}
let timer = new Timer();
console.log(setTimeout(() => {timer.s1}, 1000))
// 3
console.log(setTimeout(() => {timer.s2}, 1000))
// 0
上面的timer函数中有两个方法,一个箭头函数一个普通函数,这两个函数在执行的时候箭头函数的this指向了timer,普通函数的this指向了window。
所以在打印s1 的时候是3
打印s2的时候是0
箭头函数使用的是timer中s1
普通函数使用的是window里的s2,虽然window没有这个s2但是不报错。
箭头函数的优点是用作回调时,总是可以绑定固定的this。
let handler = {
id: '666666',
foo () {
document.addEventListener('click', event => {
this.bar(event.type)
}, false)
}
bar (type) {
console.log('bound' + type + this.id)
}
}
这样写的好处在于,这个箭头函数的this永远指向handler,因为普通函数这是调用this.bar会报错。
this指向document,但是经过了箭头函数的改动,指向就会变为handler
trhis指向的固定化,实际并不是因为箭头函数内部有绑定this的机制,而是箭头函数没有自己的this,它完全依赖于外部this。
所以它不可以使用构造函数
箭头函数转换为es5
function foo () {
setTimeout(() => {
console.log(this.id)
}, 1000)
}
function foo () {
let _this = this;
setTimeout( function () {
console.log(this.id)
}, 1000)
}
由此可以看出箭头函数的this依赖于外部this
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
上面函数的this只有一个,就是foo函数,因为内部全部都是箭头函数,他们都依赖与foo的this
除了this,箭头函数还不可以使用arguments,super,new.target
function foo () {
setTimeout(() => {
console.log(arguments)
}, 1000)
}
foo(1, 3, 5, 7)
// [1, 3, 5, 7]
上面代码中,箭头函数内部的变量arguments,其实是函数foo的arguments
箭头函数没有this的原因,也无法使用call(), apply(), bind()
(function () {
return [
(() => this.id.bind({id: 32}) ())
]
} ()).call({id: 21})
// 21
由于箭头函数无法绑定bind,所以内部this指向外部的this
this在对象中,使用要非常小心,但是箭头函数改变了这一特性,此时箭头函数的this永远指向外层对象
不适合场景
箭头函数使得this从静态变为动态,下面两个场合不适合使用箭头函数
第一个是定义对象的方法,且方法中使用了this
let obj = {
car: 'BMW',
foo: () => {
this.car = 'SRL'
}
}
如果foo是个普通函数,调用obj.foo时this指向obj, 那么car就可以轻易拿到。
但是使用箭头函数,那么他的this依赖于外部,但是对象恰恰没有this这种机制,导致箭头函数的this指向window,最后会导致报错。
第二种是需要动态的this时
let button = document.querySelector('button');
button.onclick = () => {
this.classList.add('button');
}
本意是想当点击button按钮的时候添加一个类名,但使用了箭头函数之后,他的this不再是调用者的this了,而是全局window,导致最后结果不如意,所以这种情况用使用普通函数
箭头函数内部,还可以再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。
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]
函数调用,依赖另一个函数的功能,使用箭头函数实现
let p1 = a => a + 1;
let p2 = a => a * 2;
p2(p1(3));
// 8
箭头函数还有一个功能,就是可以很方便地改写 λ 演算。
// λ演算的写法
fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))
// ES6的写法
var fix = f => (x => f(v => x(x)(v)))
(x => f(v => x(x)(v)));
函数参数的尾逗号
ES2017允许函数的最后一个参数有尾逗号
此前函数的定义和调用时,都不允许最后一个添加逗号
function bar (
car,
wife
)
{
}
bar('BMW', 'fanfan')
如果你在参数后面添加逗号就会报错,但ES6完善了这一点
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function foo (
wife,
car,
) {}
foo('fanfan', 'BMW')
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
Function.prototype.toString()
ES2019对函数实例的toString()方法做出了修改
toString()方法返回函数代码本身,以前会省略注释,空格
function /* foo comment */ foo () {}
foo.toString()
// function foo () {}
上面代码中,函数foo原始代码包含注释,函数名foo和圆括号 之前有空格,但是原来的toString()把他们省略了
但是es6又把他们重新召唤回来了
function /* foo comment */ foo () {}
foo.toString()
// 'function /* foo comment */ foo () {}'
Catch参数的省略
js语言的try…catch结构,以前必须在catch后紧跟参数,但有时会用不到这个参数。
try {
} catch (error) {
}
ES2019做出修改允许catch语句省略参数
try {
} catch {
}