【ES6】函数的扩展
1.参数默认值
ES6之前变通的方法为,如果参数y赋值而其转换布尔值为false,则该赋值不起作用。为了避免这个问题还要先typeof判断参数y是否被赋值,即严格意义上的为undefined
function log(x, y) {
y = y || 'World';
}
ES6参数默认值的办法
function log(x, y = 'World') {}
注意:
- 参数变量是默认声明的,所以不能用
let或const再次声明。 - 使用参数默认值时,函数不能有同名参数
- 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
- 与解构赋值默认值结合使用
只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数调用时没提供参数,变量x和y就不会生成,从而报错。
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
通过提供函数参数的默认值,就可以避免这种情况,如果没有提供参数,函数foo的参数默认为一个空对象。
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
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];
}
上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
注意
- 定义了默认值的参数,应该是函数的尾参数。
- 指定了默认值以后,函数的
length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。 - 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
function f(y = x) {
let x = 2;
console.log(y);
}
f() // ReferenceError: x is not defined
2.rest参数
function add(...values) {}
arguments对象不是数组,而是一个类数组对象。以前为了使用数组的方法,必须经过如下转换
Array.prototype.slice.call(arguments)
注意
- rest 参数必须是尾参数
- 函数的length属性,不包括 rest 参数。
3.严格模式
'use strict'
ES2016规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
4.name属性
函数name属性返回该函数的函数名。
function foo() {}
foo.name // "foo"
如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
var f = function () {};
f.name // ES5返回""
f.name // es6返回"f"
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
const bar = function baz() {};
// ES5 ES6
bar.name // "baz"
Function构造函数返回的函数实例,name属性的值为anonymous。
(new Function).name // "anonymous"
bind的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
5.箭头函数
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
//如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => { return num1 + num2; }
//如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => ({ id: id, name: "Temp" });
//与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
注意
1.函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。而箭头函数的this指向是固定的,指向定义生效时所在的对象。
2.不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3.不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
4.不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
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
这种特性很有利于封装回调函数。
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,否则回调时运行会指向document对象。
实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
不适用场景
1.定义对象的方法,且该方法内部包括this。
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。
2.需要动态this的时候
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
6.尾调用优化
尾调用指某个函数的最后一步是调用另一个函数。
function f(x){
return g(x);
}
//不属于
function f(x){
let y = g(x);
return y;
}
function f(x){
return g(x) + 1;
}
函数调用会在内存形成调用记录,又称作调用帧(call frame),如在A调用B,那么A的调用帧上方又会有B的调用帧,以此类推所有调用帧形成调用栈(call stack)。
尾调用不需要保留外层函数的调用帧,直接用内层取代外层函数的调用帧。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
尾递归:如果函数尾调用自身,就称为尾递归。
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
// 递归
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120 // 尾递归 function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
美观解决方法:1.柯里化2.函数默认值
在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈
func.arguments:返回调用时函数的参数。func.caller:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。
Function.prototype.toString()
toString()方法返回函数代码本身,以前会省略注释和空格。
ES2019修改后的toString()方法,明确要求返回一模一样的原始代码。
catch 命令的参数省略
ES2019做出了改变,允许catch语句省略参数。
try {
// ...
} catch {
// ...
}

浙公网安备 33010602011771号