ES6中函数新增了哪些扩展
问题解析
ES6 对函数进行了重大改进,引入了默认参数、rest 参数、箭头函数等特性。这些问题考察对函数新特性的理解,以及箭头函数与传统函数在 this 绑定上的关键区别。
核心概念
1. 默认参数(Default Parameters)
- 参数可设置默认值
- 默认参数可以是表达式
- 与解构赋值结合使用
- 尾参数才能设置默认值
2. name 属性
- ES5:匿名函数
name为空字符串 - ES6:返回实际函数名
3. 参数作用域
- 设置默认值的参数会形成单独作用域
4. 严格模式限制
- 默认参数/解构/扩展运算符时不能在函数体内显式设置严格模式
5. 箭头函数(Arrow Functions)
- 语法简化
this指向定义时- 不能作为构造函数
- 没有
arguments对象 - 不能使用
yield
详细解答
1. 参数默认值
基本用法
// ES5
function greet(name) {
name = name || 'Guest';
return 'Hello, ' + name;
}
// ES6
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
greet(); // 'Hello, Guest'
greet('Alice'); // 'Hello, Alice'
greet(undefined); // 'Hello, Guest'(显式传入 undefined 使用默认值)
greet(null); // 'Hello, null'(null 不会触发默认值)
默认值为表达式
// 默认值可以是任意表达式
function getDefault() {
return 'default value';
}
function foo(param = getDefault()) {
return param;
}
// 惰性求值:每次调用时重新计算
function add(x = Math.random(), y = Math.random()) {
return x + y;
}
// 默认值可以引用其他参数
function greet(name, greeting = `Hello, ${name}`) {
return greeting;
}
greet('Alice'); // 'Hello, Alice'
// 后面的参数可以引用前面的参数
function foo(x = 1, y = x) {
return [x, y];
}
foo(); // [1, 1]
// 但前面的参数不能引用后面的参数
function bar(x = y, y = 1) {
return [x, y];
}
bar(); // ReferenceError: y is not defined
与解构赋值结合
// 解构 + 默认值
function fetch(url, { method = 'GET', body = '' } = {}) {
console.log(method, body);
}
fetch('http://example.com');
// GET ''
fetch('http://example.com', { method: 'POST' });
// POST ''
// 对比:没有 = {} 时
function fetch2(url, { method = 'GET' }) {
console.log(method);
}
fetch2('http://example.com'); // TypeError: Cannot destructure property 'method'
length 属性变化
// length 属性统计第一个有默认值的参数之前的参数个数
function a(x, y) {}
function b(x, y = 1) {}
function c(x = 1, y) {}
function d(x, y = 1, z) {}
a.length; // 2
b.length; // 1
c.length; // 0
d.length; // 1
2. name 属性
// 具名函数
function foo() {}
foo.name; // 'foo'
// 匿名函数赋值给变量(ES6)
var bar = function() {};
bar.name; // 'bar'
// 具名函数表达式
var baz = function qux() {};
baz.name; // 'qux'
// 构造函数
(new Function()).name; // 'anonymous'
// bind 产生的函数
function original() {}
original.bind({}).name; // 'bound original'
// Symbol 作为属性名
const obj = {
[Symbol('desc')]() {}
};
Object.values(obj)[0].name; // '[desc]'
3. 参数作用域
设置默认值的参数会形成单独的作用域,不设置默认值则不会。
let x = 1;
function foo(x, y = x) {
// 参数作用域:y 的默认值 x 指向参数 x,不是全局 x
console.log(y);
}
foo(2); // 2
// 更复杂的例子
let a = 1;
function bar(a, b = function() { a = 2; }) {
// 参数作用域中的 a 是参数 a
var a = 3; // 函数体内的 a,与参数 a 不同
b(); // 修改的是参数作用域中的 a
console.log(a); // 3(函数体内的 a)
}
bar();
console.log(a); // 1(全局 a 不受影响)
4. 严格模式限制
// 报错:默认参数时不能在函数体内使用严格模式
function doSomething(a, b = a) {
'use strict';
// SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
}
// 报错:解构参数
function doSomething({ a, b }) {
'use strict';
// SyntaxError
}
// 报错:rest 参数
function doSomething(...args) {
'use strict';
// SyntaxError
}
// 解决方式1:全局严格模式
'use strict';
function doSomething(a, b = a) {
// OK
}
// 解决方式2:函数包裹
function doSomething(a, b = a) {
function inner() {
'use strict';
// OK
}
inner();
}
5. 箭头函数
基本语法
// 基本形式
const add = (x, y) => x + y;
// 多行语句需要大括号和 return
const sum = (x, y) => {
const result = x + y;
return result;
};
// 单个参数可省略括号
const double = x => x * 2;
// 无参数需要空括号
const getRandom = () => Math.random();
// 返回对象需要括号包裹
const makePerson = (name, age) => ({ name, age });
// 与解构结合
const greet = ({ name, age }) => `Hello, ${name} (${age})`;
this 绑定
箭头函数没有自己的 this,它继承外层作用域的 this。
// 传统函数的问题
const obj = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
greetFriends: function() {
this.friends.forEach(function(friend) {
console.log(this.name + ' says hi to ' + friend);
// this.name 是 undefined,因为 this 指向全局对象
});
}
};
// ES5 解决方案
const obj2 = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
greetFriends: function() {
var self = this;
this.friends.forEach(function(friend) {
console.log(self.name + ' says hi to ' + friend);
});
}
};
// ES6 箭头函数
const obj3 = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
greetFriends() {
this.friends.forEach(friend => {
console.log(this.name + ' says hi to ' + friend);
// this 继承自 greetFriends,指向 obj3
});
}
};
// 另一个例子
function Timer() {
this.seconds = 0;
// 传统函数需要 bind
setInterval(function() {
this.seconds++;
}.bind(this), 1000);
// 箭头函数自动绑定
setInterval(() => {
this.seconds++;
}, 1000);
}
不能作为构造函数
const Person = (name) => {
this.name = name;
};
const p = new Person('Alice');
// TypeError: Person is not a constructor
没有 arguments 对象
const foo = () => {
console.log(arguments);
// ReferenceError: arguments is not defined
};
// 使用 rest 参数替代
const bar = (...args) => {
console.log(args); // 真正的数组
};
// 对比传统函数
function baz() {
console.log(arguments); // 类数组对象
}
不能使用 yield
// 箭头函数不能作为 generator
const gen = *() => { // SyntaxError
yield 1;
};
// 正确写法
function* gen() {
yield 1;
}
没有 prototype 属性
const foo = () => {};
console.log(foo.prototype); // undefined
不能用作对象方法(某些情况)
const obj = {
value: 42,
// 不推荐:箭头函数作为方法
getValue: () => this.value
// this 不指向 obj,而是指向定义时的上下文
};
// 推荐:使用简写方法
const obj2 = {
value: 42,
getValue() {
return this.value; // this 指向 obj2
}
};
深入理解
1. 默认参数的惰性求值
let counter = 0;
function increment() {
return ++counter;
}
function foo(a = increment()) {
console.log(a);
}
foo(); // 1
foo(); // 2
foo(); // 3
// 每次调用 foo() 都会重新计算默认值
2. 箭头函数 this 绑定的实现原理
箭头函数的 this 在定义时确定,不能通过 call、apply、bind 改变:
const obj = { value: 42 };
const arrow = () => console.log(this.value);
const normal = function() { console.log(this.value); };
arrow.call(obj); // undefined(this 仍指向定义时的上下文)
normal.call(obj); // 42
// 箭头函数在全局作用域定义
// 所以 this 指向全局对象(严格模式下是 undefined)
3. 函数参数作用域的 TDZ
function foo(a = b, b) {
// 参数 a 的默认值 b 处于 TDZ
return [a, b];
}
foo(undefined, 1);
// ReferenceError: Cannot access 'b' before initialization
// 正确写法
function bar(a = b, b = 2) {
return [a, b];
}
bar(); // [2, 2]
4. 箭头函数与数组方法
const numbers = [1, 2, 3, 4, 5];
// 箭头函数使代码更简洁
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 链式操作
const result = numbers
.filter(n => n > 2)
.map(n => n * n)
.reduce((acc, n) => acc + n, 0);
5. 立即执行箭头函数
// IIAFE (Immediately Invoked Arrow Function Expression)
const result = (() => {
const a = 1;
const b = 2;
return a + b;
})();
// 带参数的 IIAFE
const greeting = ((name) => `Hello, ${name}`)('Alice');
// 与 async 结合
(async () => {
const data = await fetch('/api/data');
console.log(data);
})();
最佳实践
1. 默认参数的使用原则
// 推荐:使用默认参数替代 || 运算符
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
// 不推荐:|| 会误判 falsy 值
function greetBad(name) {
name = name || 'Guest'; // '' 也会被替换为 'Guest'
return `Hello, ${name}`;
}
// 处理可能为 0 或 '' 的情况
function multiply(a, b = 1) {
return a * b;
}
multiply(5, 0); // 0(正确)
function multiplyBad(a, b) {
b = b || 1; // 错误:0 会被替换为 1
return a * b;
}
2. 箭头函数的使用场景
// 适合使用箭头函数的场景:
// 1. 回调函数
const doubled = [1, 2, 3].map(x => x * 2);
// 2. 需要保持 this 上下文
class Counter {
constructor() {
this.count = 0;
// 使用箭头函数保持 this
setInterval(() => this.count++, 1000);
}
}
// 3. 简单的单行函数
const isEven = n => n % 2 === 0;
// 4. 函数式编程
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
// 不适合使用箭头函数的场景:
// 1. 对象方法(需要 this 指向对象)
const obj = {
value: 42,
getValue() { // 使用简写方法
return this.value;
}
};
// 2. 原型方法
Person.prototype.getName = function() {
return this.name;
};
// 3. 需要 arguments 的函数
function sum() {
return Array.from(arguments).reduce((a, b) => a + b, 0);
}
// 4. 需要作为构造函数
function Person(name) {
this.name = name;
}
3. 参数解构与默认值的结合
// 配置对象参数
function createUser({
name = 'Anonymous',
age = 0,
email = '',
role = 'user'
} = {}) {
return { name, age, email, role };
}
// 使用
createUser(); // { name: 'Anonymous', age: 0, email: '', role: 'user' }
createUser({ name: 'Alice', age: 25 }); // { name: 'Alice', age: 25, email: '', role: 'user' }
4. 避免默认参数的副作用
// 不推荐:默认参数有副作用
let id = 0;
function createItem(name = `Item ${++id}`) {
return { id, name };
}
// 推荐:无副作用的默认参数
function createItemGood(name) {
return {
id: generateId(),
name: name || 'Untitled'
};
}
面试要点
-
默认参数的注意事项:
null不会触发默认值,只有undefined会- 默认参数是惰性求值的
- 后面的参数可以引用前面的参数
-
箭头函数与普通函数的区别:
this绑定:箭头函数继承外层this,不能改变- 没有
arguments对象,使用 rest 参数替代 - 不能作为构造函数,没有
prototype - 不能使用
yield,不能作为 generator
-
参数作用域理解:
- 默认参数会形成单独的作用域
- 理解 TDZ 在参数中的作用
-
严格模式限制的原因:
- 默认参数、解构、rest 参数会改变参数的处理方式
- 与严格模式的某些特性冲突
-
箭头函数的最佳实践:
- 适合:回调函数、需要保持
this的场景、简单的函数 - 不适合:对象方法、原型方法、需要
arguments、构造函数
- 适合:回调函数、需要保持
-
length 属性的变化:
- 默认参数会影响
length属性的值 - 只统计第一个默认参数之前的参数个数
- 默认参数会影响
-
name 属性的改进:
- ES6 中匿名函数赋值给变量后,
name返回变量名 - 有助于调试和错误追踪
- ES6 中匿名函数赋值给变量后,