返回首页

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 在定义时确定,不能通过 callapplybind 改变:

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'
  };
}

面试要点

  1. 默认参数的注意事项

    • null 不会触发默认值,只有 undefined
    • 默认参数是惰性求值的
    • 后面的参数可以引用前面的参数
  2. 箭头函数与普通函数的区别

    • this 绑定:箭头函数继承外层 this,不能改变
    • 没有 arguments 对象,使用 rest 参数替代
    • 不能作为构造函数,没有 prototype
    • 不能使用 yield,不能作为 generator
  3. 参数作用域理解

    • 默认参数会形成单独的作用域
    • 理解 TDZ 在参数中的作用
  4. 严格模式限制的原因

    • 默认参数、解构、rest 参数会改变参数的处理方式
    • 与严格模式的某些特性冲突
  5. 箭头函数的最佳实践

    • 适合:回调函数、需要保持 this 的场景、简单的函数
    • 不适合:对象方法、原型方法、需要 arguments、构造函数
  6. length 属性的变化

    • 默认参数会影响 length 属性的值
    • 只统计第一个默认参数之前的参数个数
  7. name 属性的改进

    • ES6 中匿名函数赋值给变量后,name 返回变量名
    • 有助于调试和错误追踪