前言

ES6 作为 JavaScript 的里程碑版本,彻底革新了前端开发的语法与效率。它不仅带来了 let、Promise 等实用特性,更让代码编写更简洁、逻辑更清晰。本文从基础概念出发,用通俗语言和简单实例拆解 ES6 核心知识点,帮你快速掌握这门前端必备技能,为框架学习和项目开发筑牢根基。

一、ES6是什么?为什么要学ES6?

1、什么是ES6?

ES6其实是ECMAScript 6的简称。ECMAScript 是JavaScript的语法标准,由 Ecma 国际组织制定,用来规范 JavaScript 的语言规则。
从 1997 年第一版发布到现在,ECMAScript 已经更新了多个版本。其中 ES6 在 2015 年发布,是改动最大、最具里程碑意义的版本。从 ES6 开始,每年都会发布一个新版本,版本号比年份最后一位大 1(比如 2016 年发布的 ES7,2019 年发布的 ES10)。
简单说,ES6 就是 JavaScript 的 “升级版本”,在原有基础上增加了很多实用的新语法和新功能。

2、为什么要学习ES6?

  • ES6 的变动内容最多,加入了大量新特性,学会它能让编程更简单、更高效;
  • 现在前端开发中,ES6 已经成为行业标准,不管是框架开发还是日常工作,都离不开它;
  • 掌握 ES6 是前端就业的必备技能,大部分企业招聘都会要求熟练使用 ES6 特性。

二、ES6的核心新特性

1、let关键字

let 专门用来声明变量,和以前的 var 相比,它的规则更清晰,用起来更安全,有三个核心特点:

  • 不允许重复声明:同一个变量不能用 let 重复定义;
  • 有块级作用域:变量只在所在的大括号{}内有效,出了大括号就不能用了;
  • 不存在变量提升:不能在声明变量之前使用它。
// 1. 不允许重复声明
let name = "张三";
// let name = "李四"; // 报错:变量已声明

// 2. 块级作用域
if (true) {
  let age = 18;
  console.log(age); // 输出:18(块内可以访问)
}
// console.log(age); // 报错:age未定义(块外不能访问)

// 3. 不存在变量提升
// console.log(gender); // 报错:不能在声明前使用
let gender = "男";

let 与 var 的区别

  1. 作用域不同
    var:函数作用域。变量只在声明它的函数内部有效,若在函数外部声明,则为全局作用域。
    let:块级作用域。变量在声明它的代码块(如 {} 、 if 、 for 等)内部有效,超出该块则不可访问。
  2. 变量提升
    var:会被提升到函数顶部(声明被提升,赋值不提升)。
    let:不会被提升,在声明前访问变量会报错。
  3. 重复声明
    var:允许在同一作用域内重复声明同一变量(后声明的会覆盖前声明的)。
    let:不允许在同一作用域内重复声明同一变量(会直接报错)。
  4. 全局对象属性
    var:在全局作用域声明的变量会成为全局对象(如浏览器中的 window )的属性。
    let:在全局作用域声明的变量不会成为全局对象的属性。
  5. 循环中的表现
    var:在循环中声明的变量会共享同一个变量实例(循环结束后仍可访问,值为最后一次迭代的值)。
    let:在循环中声明的变量会为每次迭代创建新的变量绑定(循环结束后不可访问,且每次迭代的值独立)。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>区别var与let</title>
</head>
<body>

    <script>
        console.log(a); // undefined
        var a = 1;

        //console.log(b);
        let b = 2; // 报错,不可以变量提升

        {
            var c = 1;
            console.log(c); // 1
        }
        console.log(c); // 1

        {
            let d = 1;
            console.log(d); // 1
        }
        //console.log(d); // 报错,块级作用域

        let e = 1;
        // let e = 2; // 报错,不可重复声明变量
        console.log(e);

    </script>
</body>
</html>

2、const关键字

const 用来声明 “常量”,也就是值不能随意修改的量,有五个核心特点:

  • 声明时必须赋初始值:不能只写const a;,必须写成const a = 10;
  • 标识符一般大写:这是约定俗成的规范,方便区分常量和变量;
  • 不允许重复声明:和 let 一样,不能重复定义同一个常量;
  • 值不允许修改:常量赋值后,不能再改它的值(如果是对象,对象里的属性可以改);
  • 有块级作用域:和 let 一样,只在所在的大括号内有效。
// 1. 声明必须赋初始值
const PI = 3.14;
// const NAME; // 报错:缺少初始值

// 2. 标识符大写(规范)
const SCHOOL = "第一中学";

// 3. 不允许重复声明
// const PI = 3.1415; // 报错:常量已声明

// 4. 值不允许修改
// PI = 3.1415; // 报错:不能修改常量的值

// 5. 块级作用域
if (true) {
  const CITY = "北京";
  console.log(CITY); // 输出:北京(块内可以访问)
}
// console.log(CITY); // 报错:CITY未定义(块外不能访问)

// 特殊情况:对象类型的常量,属性可以修改
const PERSON = { name: "张三" };
PERSON.name = "李四"; // 允许修改对象属性
console.log(PERSON.name); // 输出:李四

应用场景:声明不需要修改的值用 const(比如固定的配置、对象),需要修改的值用 let。

3、变量的解构赋值

解构赋值是什么呢?
解构赋值就是按照一定模式,从数组或对象中提取值,直接赋给变量。这样写不用一次次通过索引或属性名获取数据,更简洁。
数组结构:

// 以前提取数组元素
const fruits = ["苹果", "香蕉", "橙子"];
let f1 = fruits[0];
let f2 = fruits[1];
console.log(f1, f2); // 输出:苹果 香蕉

// 用解构赋值提取
const [a1, a2, a3] = fruits;
console.log(a1, a2, a3); // 输出:苹果 香蕉 橙子

对象解构:

// 以前提取对象属性
const person = {
  name: "林志颖",
  age: 40,
  tags: ["车手", "歌手", "演员"]
};
let pName = person.name;
let pTags = person.tags;
console.log(pName, pTags); // 输出:林志颖 ["车手","歌手","演员"]

// 用解构赋值提取
const { name, tags } = person;
console.log(name); // 输出:林志颖
console.log(tags); // 输出:["车手","歌手","演员"]

应用场景:频繁使用数组元素或对象属性时,用解构赋值能省很多代码。

4、模板字符串

模板字符串用反引号`标识,比普通字符串更强大,解决了字符串拼接的麻烦,有两个核心特点:

  • 可以直接换行:字符串里不用加\n,直接换行写就行;
  • 可以用${变量名}形式插入变量:不用再用+拼接变量和字符串。
// 1. 可以直接换行
const html = `
  <ul>
    <li>张三</li>
    <li>李四</li>
    <li>王五</li>
  </ul>
`;
console.log(html); // 输出带换行的HTML结构

// 2. 插入变量
const star = "田七";
const result = `${star} Hello`;
console.log(result); // 输出:田七 Hello

// 3. 插入表达式
const a = 10;
const b = 20;
const sumStr = `${a} + ${b} = ${a + b}`;
console.log(sumStr); // 输出:10 + 20 = 30

应用场景:需要拼接字符串和变量时,优先用模板字符串。

5、箭头函数

箭头函数用=>来定义函数,语法更简洁,还解决了 this 指向的问题,有五个核心特点:

  • 形参只有一个时,小括号可以省略;
  • 函数体只有一条语句时,花括号可以省略,并且会自动返回这条语句的结果;
  • this 指向声明时所在作用域的 this(不是调用时的对象);
  • 不能作为构造函数:不能用 new 关键字创建实例;
  • 不能使用 arguments 对象:要获取参数,用剩余参数…代替。
// 1. 通用写法(多个参数,多条语句)
const add = (a, b) => {
  return a + b;
};
console.log(add(10, 20)); // 输出:30

// 2. 省略小括号(只有一个参数)
const multiply = num => {
  return num * 10;
};
console.log(multiply(3)); // 输出:30

// 3. 省略花括号(只有一条语句,自动返回)
const subtract = (a, b) => a - b;
console.log(subtract(50, 15)); // 输出:35

// 4. this指向(声明时的作用域)
const person = {
  name: "张三",
  sayHi: function() {
    // 普通函数的this指向调用者(person)
    const fn = () => {
      console.log(this.name); // 箭头函数的this继承自sayHi的this,输出:张三
    };
    fn();
  }
};
person.sayHi();

应用场景:简单函数、回调函数(比如数组方法里的函数)用箭头函数,写法更简洁。

6、回调地狱

在处理多个异步操作时,如果每个操作都嵌套在前面操作的回调函数里,就会形成 “回调地狱”。代码会一层套一层,看起来像金字塔,可读性和维护性都很差。

// 需求:等待1秒打印Python,再等2秒打印Web前端,再等3秒打印Dify
setTimeout(() => {
  console.log("Python");
  setTimeout(() => {
    console.log("Web前端");
    setTimeout(() => {
      console.log("Dify");
    }, 3000); // 第三个异步操作
  }, 2000); // 第二个异步操作
}, 1000); // 第一个异步操作

现在存在的一个问题就是:如果有更多异步操作,代码会一直嵌套下去,很难看懂和修改。
那么解决这个问题的方案是什么呢?
答案就是下面要讲的 Promise。

7、同步请求与异步请求

在编程中,请求操作(比如从服务器拿数据、读取文件)分为同步和异步两种,两者的执行逻辑完全不同:
(1)同步请求
特点:按顺序执行,前一个任务完成后,才能执行下一个任务;会阻塞后续代码,前面的任务没做完,后面的代码就不能跑。

// 同步执行:先执行完alert,才会执行console.log
alert("第一个任务");
console.log("第二个任务"); // 只有关掉alert,才会打印

(2)异步请求
特点:不用等前一个任务完成,就能执行下一个任务;不会阻塞代码,前一个任务在执行时,后面的代码可以正常跑;结果通过回调函数、Promise、async/await 处理。

// 异步执行:setTimeout是异步操作,不会阻塞后续代码
setTimeout(() => {
  console.log("异步任务完成");
}, 1000);
console.log("同步任务先执行"); // 先打印这句话,1秒后再打印上面的内容

8、Promise

Promise 是 ES6 引入的异步编程新方案,专门用来封装异步操作,还能清晰地获取成功或失败的结果,从根本上解决回调地狱的问题。
Promise 有三个状态:pending(等待中)、fulfilled(成功)、rejected(失败),核心用法是通过then方法处理成功结果,catch方法处理失败结果。

// 用Promise封装异步操作(等待指定时间后打印内容)
function waitAndPrint(content, time) {
  // 返回Promise对象
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(content);
      resolve(); // 告诉Promise任务完成,可以执行下一个操作
    }, time);
  });
}

// 链式调用,解决回调地狱
waitAndPrint("Python", 1000)
  .then(() => waitAndPrint("Web前端", 2000)) // 第一个任务完成后,执行第二个
  .then(() => waitAndPrint("Dify", 3000)); // 第二个任务完成后,执行第三个

处理成功和失败

new Promise((resolve,reject)=>{
        resolve("成功")
        // reject("失败")
        }).then(res=>{
            console.log(res)
        }).catch(err=>{
            console.log(err)
        })

9、模块化

模块化就是把一个大的程序文件,拆分成多个小文件,每个小文件负责一个功能,然后通过特定语法把这些小文件组合起来使用。

  1. 模块化的好处
  • 防止命名冲突:不同文件里的变量、函数名可以重复,不会互相影响;
  • 代码复用:一个模块可以在多个地方引入使用,不用重复写代码;
  • 高维护性:代码拆分后,结构更清晰,修改起来更方便。
  1. ES6模块化语法
    核心有两个命令:export(导出模块的功能)和import(导入其他模块的功能)。
    导入模块:创建一个math.js文件,导出两个函数
// math.js
// 导出加法函数
export function add(a, b) {
  return a + b;
}

// 导出乘法函数
export function multiply(a, b) {
  return a * b;
}

导出模块:在另一个文件中导入并使用

// main.js
// 导入math.js中的功能
import { add, multiply } from "./math.js";

// 使用导入的函数
console.log(add(10, 20)); // 输出:30
console.log(multiply(3, 4)); // 输出:12

注意:使用模块化时,需要在 HTML 文件中引入脚本时加上type=“module”,比如:

<script type="module" src="main.js"></script>

三、async和await

async 和 await 是 ES8(2017 年)引入的特性,它们是 Promise 的语法糖,能让异步代码的写法更简洁,看起来和同步代码几乎一样。

  1. async 函数的特点
  • async 关键字用在函数前面,标记这个函数是异步函数;
  • async 函数的返回值是 Promise 对象;
  • Promise 的结果由 async 函数的返回值决定:返回普通值,Promise 就是成功状态;抛出错误,Promise 就是失败状态。
  1. await 表达式的特点
  • await 必须写在 async 函数里面,不能单独使用;
  • await 后面一般跟 Promise 对象;
  • await 会等待 Promise 完成,然后返回 Promise 成功的值;
  • 如果 await 的 Promise 失败了,会抛出异常,需要用try…catch捕获处理。
// 1. 封装异步操作(和前面Promise实例一样)
function waitAndPrint(content, time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(content);
      resolve();
    }, time);
  });
}

// 2. 使用async/await编写异步代码
async function run() {
  await waitAndPrint("Python", 1000); // 等待1秒,打印Python
  await waitAndPrint("Web前端", 2000); // 再等2秒,打印Web前端
  await waitAndPrint("Dify", 3000); // 再等3秒,打印Dify
}

// 调用函数
run();

失败的处理:

// 封装可能失败的异步操作
function fetchData() {
  return new Promise((resolve, reject) => {
    const success = false; // 模拟请求失败
    if (success) {
      resolve("请求到的数据");
    } else {
      reject("网络错误");
    }
  });
}

// 使用try...catch捕获失败
async function getData() {
  try {
    const data = await fetchData();
    console.log("成功:", data);
  } catch (error) {
    console.log("失败:", error); // 输出:失败:网络错误
  }
}

getData();

应用场景:处理异步操作时,用 async/await 比 Promise 的链式调用更简洁,代码逻辑更清晰。

结语

ES6 的新特性看似繁多,但核心都是为了让编程更高效、代码更易维护。掌握 let/const 的作用域、Promise 的异步逻辑、async/await 的简洁写法,能让你在前端开发中事半功倍。希望本文能帮你打通 ES6 的学习脉络,在实际项目中灵活运用这些特性,不断提升代码质量与开发效率。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐