轻松理解ES6箭头函数的应用场景和优势(第一篇)

1,545 阅读7分钟

块级作用域变量声明(let、const)

作用域是指在程序中定义变量的区域,该变量只能在特定区域内使用。在JavaScript中,变量的作用域有两种类型:全局作用域和局部作用域。在ES6中引入了两个新的声明变量的方式:letconst,它们可以在块级作用域中声明变量,使得变量的作用域更加严格和明确,避免了变量提升等问题,与之前的var声明变量的方式有很大的区别。

在本文中,我们将通过一个示例来说明使用letvar在作用域方面的区别。我们定义两个数组funcs1funcs2,分别用varlet声明循环变量ik,并向数组中添加闭包函数,闭包函数用来输出ik的值。然后使用for循环分别遍历两个数组,并调用数组中的函数,输出结果进行比较。

下面是示例代码:

// 使用var声明变量的示例
console.log('使用var声明变量的示例:');
var funcs1 = [];
for (var i = 0; i < 5; i++) {
  funcs1.push(function() {
    console.log('i:', i);
  });
}

for (var j = 0; j < funcs1.length; j++) {
  funcs1[j]();
}

// 使用let声明变量的示例
console.log('使用let声明变量的示例:');
var funcs2 = [];
for (let k = 0; k < 5; k++) {
  funcs2.push(function() {
    console.log('k:', k);
  });
}

for (let l = 0; l < funcs2.length; l++) {
  funcs2[l]();
}

输出结果如下:

// 使用var声明变量的示例:
i: 5
i: 5
i: 5
i: 5
i: 5
// 使用let声明变量的示例:
k: 0
k: 1
k: 2
k: 3
k: 4

从输出结果可以看出,在使用var声明变量的示例中,因为var声明的变量是函数作用域,而不是块级作用域,导致最终输出的结果都是5。而在使用let声明变量的示例中,每次循环都会创建一个新的变量k,保证了闭包内部访问到的变量是不同的,输出的结果符合预期。因此,在使用for循环等语句时,如果需要在循环中使用闭包或定时器等,使用let声明变量可以避免变量共享和变量提升导致的问题。

以下是一个简易选项卡的js逻辑部分的代码,分别用var 和 let 来展示效果

使用 var

function createTab() {
  var tabs = document.querySelectorAll('.tab');
  var contents = document.querySelectorAll('.tab-content');
  var activeIndex = 0;

  for (var i = 0; i < tabs.length; i++) {
    var tab = tabs[i];
    tab.addEventListener('click', function() {
      // 切换标签的选中状态
      tabs[activeIndex].classList.remove('active');
      tab.classList.add('active');

      // 切换内容的显示状态
      contents[activeIndex].classList.remove('active');
      contents[i].classList.add('active');

      activeIndex = i;
    });
  }
}

createTab();

在这个示例中,我们使用了 var 关键字来声明变量 itab。由于 var 声明的变量会被提升到函数作用域顶部,因此 itab 可以在整个函数作用域内访问。但是,由于 var 声明的变量没有块级作用域的限制,因此在循环中,itab 的值会被不断地覆盖。这就导致了一个问题:当事件处理函数被触发时,它们访问到的 itab 可能已经不是最新的值了,而是最后一次循环中的值。

使用 let

function createTab() {
  var tabs = document.querySelectorAll('.tab');
  var contents = document.querySelectorAll('.tab-content');
  var activeIndex = 0;

  for (let i = 0; i < tabs.length; i++) {
    let tab = tabs[i];
    tab.addEventListener('click', function() {
      // 切换标签的选中状态
      tabs[activeIndex].classList.remove('active');
      tab.classList.add('active');

      // 切换内容的显示状态
      contents[activeIndex].classList.remove('active');
      contents[i].classList.add('active');

      activeIndex = i;
    });
  }
}

createTab();

在这个示例中,我们使用了 let 关键字来声明变量 itab。由于 let 声明的变量具有块级作用域的限制,因此在循环内部声明的 itab 只能在该循环内部访问。这样做的好处是,在每次循环时,itab 都会被重新声明,因此它们的值不会互相影响。当事件处理函数被触发时,它们访问到的 itab 就是最新的值了,而不是最后一次循环中的值。

箭头函数(Arrow function)

它可以更简洁地定义函数。箭头函数的语法如下:

(param1, param2, …, paramN) => { statements }

箭头函数相比于普通函数有以下特点:

  1. 箭头函数没有自己的this,它的this是继承自外层作用域的this。这意味着在箭头函数内部访问的this与外部作用域的this相同,不会发生变化,这样可以避免this绑定错误的问题。
  2. 箭头函数没有arguments对象,所以要获取传入的参数需要使用Rest参数或者展开运算符。
  3. 箭头函数不能作为构造函数使用,因为它没有自己的this,无法使用new操作符创建对象。

箭头函数适合在以下场景中使用:

  1. 作为回调函数,因为它的语法更简洁,可以减少代码量。
  2. 作为简单的函数表达式,例如在map、filter等数组方法中使用。
  3. 作为在对象字面量中定义方法的函数,因为它的语法更简洁。
  4. 作为不需要自己的this的函数,例如处理事件监听器。

为什么要用箭头函数?箭头函数有以下优点:

  1. 代码更简洁,语法更清晰,可以减少代码量。
  2. 不需要使用bind方法来绑定this,避免了this绑定错误的问题。
  3. 可以更好地处理异步代码,例如在Promise中使用箭头函数可以避免回调地狱。

总之,箭头函数是一种更加简洁、清晰、灵活的函数定义方式,可以使代码更加易读易写,提高开发效率。

下面我给你举几个例子来说明箭头函数的使用场景、与普通函数的区别以及为什么要用它。

1. 箭头函数作为回调函数

箭头函数在回调函数中的使用非常普遍,特别是在异步编程中。在这种情况下,箭头函数可以简化代码并提高可读性。

比如,下面是一个使用普通函数作为回调函数的例子:

setTimeout(function() {
  console.log('Hello, world!');
}, 1000);

这里我们使用了setTimeout()函数,将一个函数作为回调函数传递给它。这个函数在1秒钟后会被调用,并输出一条消息。

现在我们用箭头函数来重写它:

setTimeout(() => {
  console.log('Hello, world!');
}, 1000);

这样看起来更简洁、更易读。由于箭头函数没有自己的this,所以我们不需要通过bind()方法来绑定上下文。

2. 箭头函数作为函数表达式

箭头函数还可以用来定义函数表达式。比如,下面的代码定义了一个接受两个参数并返回它们之和的箭头函数表达式:

const add = (a, b) => {
  return a + b;
};

console.log(add(2, 3)); // 输出 5

这段代码定义了一个名为add的常量,它的值是一个箭头函数。当我们调用这个函数时,它会将传入的两个参数相加并返回结果。

注意,这里的箭头函数有一个花括号,因为它有多条语句,需要使用return语句来返回结果。如果箭头函数只有一条语句,并且这条语句可以直接返回结果,那么我们可以将花括号省略:

const multiply = (a, b) => a * b;

console.log(multiply(2, 3)); // 输出 6

这段代码定义了一个名为multiply的常量,它的值是一个箭头函数。当我们调用这个函数时,它会将传入的两个参数相乘并返回结果。注意,这里的箭头函数没有花括号,因为它只有一条语句,并且可以直接返回结果。

3. 箭头函数的作用域

箭头函数和普通函数在作用域上也有一些差异。

普通函数有它自己的 this 值,而箭头函数没有。箭头函数的 this 值继承自外部的作用域,即定义函数时所处的对象。

例如:

const obj = {
  name: 'Alice',
  sayName: function () {
    console.log(this.name); // 输出 Alice
    const arrowFunc = () => {
      console.log(this.name); // 输出 Alice
    };
    arrowFunc();
  }
};
obj.sayName();

在这个例子中,obj.sayName() 调用了一个箭头函数 arrowFunc,它的 this 值继承自 obj.sayName 的作用域,因此输出结果为 Alice。

另一方面,如果我们将 arrowFunc 改成普通函数:

const obj = {
  name: 'Alice',
  sayName: function () {
    console.log(this.name); // 输出 Alice
    function normalFunc() {
      console.log(this.name); // 输出 undefined
    }
    normalFunc();
  }
};
obj.sayName();

这里 normalFuncthis 值不再是 obj,而是全局对象 window(在浏览器环境下)。因此输出结果为 undefined。

总的来说,箭头函数具有以下特点:

  • 没有自己的 this 值,继承外部作用域的 this 值。
  • 没有 arguments 对象,可以使用剩余参数语法 ... 代替。
  • 不能作为构造函数使用,不能使用 new 操作符。
  • 不能使用 yield 关键字。

箭头函数还可以用于简化代码和提高可读性。

例如,当我们需要在一个对象数组中筛选出满足特定条件的元素时,可以使用箭头函数结合数组的 filter() 方法来实现:

const persons = [
  { name: 'Alice', age: 22 },
  { name: 'Bob', age: 32 },
  { name: 'Charlie', age: 26 },
  { name: 'David', age: 18 }
];

const adults = persons.filter(person => person.age >= 18);

console.log(adults);
// Output: 
// [
//   { name: 'Alice', age: 22 },
//   { name: 'Bob', age: 32 },
//   { name: 'Charlie', age: 26 },
//   { name: 'David', age: 18 }
// ]

在这个例子中,我们使用箭头函数作为 filter() 方法的回调函数,该函数接收一个参数 person,用于表示当前正在被过滤的对象元素,然后返回一个布尔值来判断该元素是否应该被保留在新数组中。

另外,箭头函数还可以用于简化对象字面量的方法定义。比如,我们可以将下面的代码:

const person = {
  name: 'Alice',
  sayHello: function() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

person.sayHello(); // Output: Hello, my name is Alice.

改写成:

const person = {
  name: 'Alice',
  sayHello() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

person.sayHello(); // Output: Hello, my name is Alice.

在这个例子中,我们将方法的定义改写成了简化形式,并省略了 function 关键字。这样做可以使代码更简洁、可读性更高。

另外,箭头函数还可以让我们更方便地处理某些特定的场景。例如,在使用 Promise 时,我们可以使用箭头函数来避免写冗长的回调函数链,从而提高代码的可读性。比如,下面的代码:

fetch('https://api.github.com/users/github')
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    console.log(data);
  })
  .catch(function(error) {
    console.error(error);
  });

可以改写成:

fetch('https://api.github.com/users/github')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

在这个例子中,我们使用箭头函数来简化回调函数的定义,从而使代码更加清晰简洁。

箭头函数可以简化函数的定义,并且具有更简洁的语法,特别是在处理回调函数和处理this上下文时特别有用。但是,在需要使用this关键字的情况下,箭头函数可能不适用,因为它们绑定到定义函数时的上下文,而不是调用时的上下文。因此,在这种情况下,应该使用传统的函数定义方式。