[01] ECMAScript6 基本语法
1. 概述
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
Q1:ECMAScript 和 JavaScript 的关系?
ECMAScript 和 JavaScript 的关系是,前者是后者的规范,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。
Q2:ES6 与 ECMAScript 2015?
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
ES6 的第一个版本,在 2015 年 6 月发布,正式名称是《ECMAScript 2015 标准》(简称 ES2015)。
2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小,基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
2. 基本语法
ES 标准中不包含 DOM 和 BOM 的定义,只涵盖基本数据类型、关键字、语句、运算符、内建对象、内建函数等通用语法。
本部分只学习前端开发中 ES6 的最少必要知识,方便后面项目开发中对代码的理解。
开发工具 VSCode
- 安装插件:Chinese、Live Server、Vetur、vue-helper
- 在本地新建文件夹
- [VSCode] 文件 → 打开文件夹
- [VSCode] 文件 → 将工作区另存为 → 保存在自己创建的文件夹下,随便取个名字(后缀是 .code-workspace)
- 以服务器方式打开网页预览:安装 Live Server 插件,然后文件右键 Open with Live Server
(1)let、const
1. let 声明变量
// 1. let 有作用域的概念
{
var a = 100;
// let b = 1101;
}
console.log(a); // 100
// console.log(b); // b is not defined
// 2. let 不可重复声明同名变量
var c = 200;
var c = 222;
console.log(c); // 222
let d = 300;
// let d = 333;
// console.log(d); // redeclaration of let d
// 3. var 会提升变量声明,let 不会
// console.log(e);
// var e = 400; // undefined
// let e = 400; // can't access lexical declaration 'e' before initialization
2. const 声明常量
const PI = 3.14;
console.log(PI);
// 1. const 声明的变量,一经定义不能改变
// PI = 3; // invalid assignment to const 'PI'
// 2. const 声明时必须赋值
// const PI2; // missing = in const declaration
(2)解构赋值
解构赋值是对赋值运算符的扩展。它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
// 1. 解构数组进行变量赋值
// ----- common
let a = 1, b = 2, c = 3;
console.log(a, b, c);
// ----- ES6
let [x, y, z] = [1, 2, 3];
console.log(x, y, z);
// 2. 解构对象赋值:变量名和字段名要一一对应
let user = {name: 'liuyuan', age: 22};
// ----- common
let name1 = user.name;
let age1 = user.age;
console.log(name1);
console.log(age1);
// ----- ES6
// 表示将 user 对象中的 name 属性的值赋给新变量 newName
let {name:newName, age} = user;
// 如果尝试打印未定义的 name 变量,则会抛出错误,因为 name 并没有被单独定义
console.log(name); // <empty string>
console.log(newName); // user.name->name->newName
console.log(age);
(3)链判断
如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。
比如,读取 message.body.user.firstName
这个属性,安全的写法是写成下面这样。
let message = null;
// 错误的写法
const firstName = message.body.user.firstName || 'default';
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
console.log(firstName)
这样的层层判断非常麻烦,因此引入了“链判断运算符”(optional chaining operator)?.
,简化上面的写法:
const firstName = message?.body?.user?.firstName || 'default';
(4)模板字符串
模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
let str1 = 'Hello';
let str2 = 'How are you';
console.log(str1 + '!' + str2 + "?");
// 1. 字符串插入变量和表达式
console.log(`${str1}!${str2}?`); // Hello!How are you?
// 2. 字符串中调用函数
function getName() {
return 'root';
}
console.log(`${str1}!${getName()}!`); // Hello!root!
// 3. 多行字符串
let headContent = `
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
`;
console.log(headContent);
(5)声明对象 / 方法简写
let name = 'tree';
let age = 13;
// 传统声明对象
let user = {name: name, age: age};
console.log(user);
// 声明对象简写
let user2 = {name, age};
console.log(user2);
// ------------------------------------------
// 定义函数
function f1 () {}
let f2 = function() {};
// 定义方法
let user = {
name: 'tree',
age: 13,
// sayHi: function() {
sayHi() {
console.log(this.name + "say Hi!");
}
};
user.sayHi();
(6)对象拓展运算符
拓展运算符 ...
用于取出参数对象所有可遍历属性然后拷贝到当前对象。
// 1. 对象复制
let user = {name: 'tree', age: 13};
let someone1 = user; // 指向同一个对象
someone1.name = 'tree1';
console.log(someone1); // Object { name: "tree1", age: 13 }
console.log(user); // Object { name: "tree1", age: 13 }
let someone2 = {...user}; // ~ COPY 一个对象赋值给 someone2
someone2.name = 'tree2';
console.log(someone2); // Object { name: "tree2", age: 13 }
console.log(user); // Object { name: "tree1", age: 13 }
// 2. 合并对象
let name = {name: 'tree3'};
let age = {age: 22};
let someone3 = {...name, ...age};
console.log(someone3);
let someone4 = {...someone2, ...someone3};
// 合并的对象中字段有交集,会覆盖重名变量
console.log(someone4); // Object { name: "tree3", age: 22 }
(7)函数的默认参数
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数;null 值被认为是有效的值传递。
function getUser(name, age = 18) {
console.log(name, age);
}
getUser('tree', 22); // tree 22
getUser('tree', ''); // tree <empty string>
getUser('tree', null); // tree null
getUser('tree'); // tree undefined [设置默认值后] tree 18
(8)不定参数
不定参数用来表示不确定参数个数,形如 ...变量名
,由 ...
加上一个具名参数标识符组成。具名参数只能放在参数列表的最后,并且有且只有一个不定参数。
function f1 (...values) {
console.log(values.length); // 5
console.log(values); // Array(5) [ 1, 2, 3, 4, 5 ]
}
f1(1, 2, 3, 4, 5);
(9)箭头函数
// 基本语法:参数 => 函数体
// - 当箭头函数体有多行语句,用 {} 包裹起来,表示代码块。
// - 当箭头函数没有参数或者有多个参数,要用 () 括起来,单个参数可省。
// - 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
let f1 = function(a) {
return a;
};
let f2 = (a)=>{return a;};
let f3 = a=>a*3;
let f4 = a=>console.log(a);
let f5 = ()=>console.log('神似 Lambda');
/*
* 一般用在回调函数上
* $.get('url', data => {
*
* })
*/
3. Promise
(1)Promise 简述
在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。
Promise 是现代 JavaScript 中异步编程的基础,是一个由异步函数返回的可以向我们指示当前操作所处的状态的对象。在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象可以让我们操作最终完成时对其进行处理(无论成功还是失败)
Promise 中文意思是“承诺”,承诺它过一段时间会给你一个结果。Promise 对象代表一个未完成、但预计将来会完成的操作,可以表示一个计算结果或网络请求的占位符。由于当前计算或网络请求尚未完成,所以结果暂时无法取得。
(2)Promise 有三种状态
- Pending(待定):初始状态,操作还未完成。
- Fulfilled(已兑现):操作成功完成,Promise 对象的结果已经可用。
- Rejected(已拒绝):操作失败,Promise 对象的结果表示失败原因。
(3)创建 Promise
你可以使用 new Promise
创建一个新的 Promise 对象。构造函数接受一个执行器函数(executor function),该函数的两个参数分别是 resolve 和 reject。这两个函数就是就是「回调函数」,由 JavaScript 引擎提供。
- resolve 函数:在异步操作成功时调用,将 Promise 的状态改为 Fulfilled,并将异步操作的结果,作为参数传递出去;
- reject 函数:在异步操作失败时调用,将其状态改为 Rejected,并将异步操作报出的错误,作为参数传递出去。
let promise = new Promise((resolve, reject) => {
// 异步操作
let success = true; // 只是一个示例,通常由异步操作结果决定
if (success) {
resolve('Operation succeeded');
} else {
reject('Operation failed');
}
});
Promise 对象的状态转换一旦发生,就不可再次更改,会一直保持这个状态,不会再发生变化。当状态发生变化后,then/catch 绑定的函数就会被调用。
(4)使用 Promise
Promise 对象提供了 then
和 catch
方法用于处理结果:
then(onFulfilled, onRejected)
:用于注册成功和失败的回调函数。catch(onRejected)
:用于注册失败的回调函数(它是then
方法的一个简写)。
promise
.then(result => {
console.log(result); // 成功时执行
})
.catch(error => {
console.error(error); // 失败时执行
});
链式调用:你可以将多个 then
语句链式调用,处理一系列异步操作。
promise
.then(result => {
// 处理第一个操作的成功
return anotherAsyncOperation();
})
.then(result => {
// 处理第二个操作的成功
})
.catch(error => {
// 处理任意操作的失败
});
(5)使用案例
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data fetched');
}, 1000);
});
}
function processData(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + ' and processed');
}, 1000);
});
}
fetchData()
.then(data => processData(data))
.then(result => {
console.log(result); // 'Data fetched and processed'
})
.catch(error => {
console.error(error);
});
4. async&await
在 JavaScript 中,async
和 await
的引入是为了简化异步编程。虽然 Promise
提供了更优雅的方式来处理异步操作,但异步代码仍然可能较为复杂,尤其是当涉及到多个异步操作时。async
和 await
提供了一种更直观的方式来处理异步操作(仍基于 Promise),类似于同步编程的方式,从而使代码更易于理解和维护。
(1)async 函数
- 定义:使用 async 关键字定义一个异步函数。async 函数总是返回一个 Promise 对象。
- 返回值:即使函数内部使用 return 返回一个值,该值也会被自动包装在 Promise 中。
async function fetchData() {
return 'Fetched data';
}
// 使用 async 函数
fetchData().then(data => console.log(data)); // 输出 'Fetched data'
(2)await 表达式
- 作用:用于等待一个 Promise 对象的结果。在 await 之前的代码会暂停执行,直到 Promise 解决(fulfilled)或拒绝(rejected)。
- 使用:await 只能在 async 函数内部使用。
- 效果:使异步代码看起来像同步代码,从而提高代码的可读性。
async function fetchData() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => resolve('Fetched data'), 1000);
});
console.log(result);
}
fetchData(); // 输出 'Fetched data'
(3)使用案例
-
定义 async 函数
async function myAsyncFunction() { // 异步操作。该函数将自动返回一个 Promise 对象 }
-
在 async 函数内部使用 await
async function myAsyncFunction() { // 使用 await 等待 Promise 对象处理,并获取其结果 let result = await someAsyncOperation(); console.log(result); }
-
处理异常
async function myAsyncFunction() { try { let result = await someAsyncOperation(); console.log(result); } catch (error) { console.error('Error:', error); } }
-
可以在 async 函数中串联多个 await,也可以使用 Promise.all 等待多个 Promise 同时完成。
async function processData() { try { let data1 = await fetchData1(); let data2 = await fetchData2(); console.log(data1, data2); } catch (error) { console.error('Error:', error); } } async function processMultipleData() { try { let [data1, data2] = await Promise.all([fetchData1(), fetchData2()]); console.log(data1, data2); } catch (error) { console.error('Error:', error); } }