freeCodeCamp JavaScript 算法和数据结构第二章。ECMAScript(ES)是 JavaScript 的标准。因为所有主流浏览器都遵循此规范,所以 ECMAScript 和 JavaScript 是可以互换的。JavaScript 在不断迭代,每年都会发布新功能。2015 年发布的 ES6(ECMAScript6)为该语言添加了许多强大的新功能,在 ES6 点课程中,学习这些新特性,包括箭头函数、解构、类、promise 和模块。以下为我在学习和实战练习过程中所做的笔记,可供参考。
一、var、let 和 const 关键字
使用 var
关键字声明变量时,它是全局声明的,如果在函数内部声明则是局部声明的。
1 | var numArray = []; |
let
关键字的行为类似,但有一些额外的功能。 在代码块、语句或表达式中使用 let
关键字声明变量时,其作用域仅限于该代码块、语句或表达式。
1 | let printNumTwo; |
默认情况下,一些开发人员更喜欢使用 const
分配所有变量,除非他们知道需要重新分配值,他们才使用 let
。
但是,重要的是要了解使用 const
分配给变量的对象(包括数组和函数)仍然是可变的。 使用 const
声明只能防止变量标识符的重新分配:
1 | const s = [5, 6, 7]; |
const
声明并不会真的保护数据不被改变。 为了确保数据不被改变,JavaScript 提供了一个函数 Object.freeze
:
1 | let obj = { |
二、函数和操作符
在 JavaScript 里,我们会经常遇到不需要给函数命名的情况,尤其是在需要将一个函数作为参数传给另外一个函数的时候。 这时,我们会创建匿名函数:
1 | const myFunc = function() { |
ES6 提供了其他写匿名函数的方式的语法糖。 你可以使用箭头函数:
1 | const myFunc = () => { |
和一般的函数一样,你也可以给箭头函数传递参数:
1 | const doubler = (item) => item * 2; |
如果箭头函数只有一个参数,则可以省略参数外面的括号:
1 | const doubler = item => item * 2; |
可以给箭头函数传递多个参数:
1 | const multiplier = (item, multi) => item * multi; |
ES6 里允许给函数传入默认参数,来构建更加灵活的函数:
1 | const greeting = (name = "Anonymous") => "Hello " + name; |
ES6 推出了用于函数参数的 rest 操作符帮助我们创建更加灵活的函数。 rest 操作符可以用于创建有一个变量来接受多个参数的函数。 这些参数被储存在一个可以在函数内部读取的数组中:
1 | function howMany(...args) { |
ES6 引入了展开操作符,可以展开数组以及需要多个参数或元素的表达式:
1 | const arr = [6, 89, 3, 45]; |
...arr
返回一个解压的数组。 也就是说,它展开数组。 然而,展开操作符只能够在函数的参数中或者数组中使用。
用 ES6 的语法在对象中定义函数的时候,可以删除 function
关键词和冒号,用 ES6 编写简洁的函数声明:
1 | const person = { |
三、解构赋值
解构赋值是 ES6 引入的新语法,用来从数组和对象中提取值,并优雅地对变量进行赋值。
1 | // ES5 代码 |
可以给解构的值赋予一个新的变量名, 通过在赋值的时候将新的变量名放在冒号后面来实现:
1 | const { name: userName, age: userAge } = user; |
将对象的属性值赋值给具有不同名字的变量:
1 | const user = { |
在 ES6 里面,解构数组可以如同解构对象一样简单。与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。 所以,你不能选择哪个元素来给变量赋值,而对数组进行解构却可以让我们做到这一点:
1 | const [a, b,,, c] = [1, 2, 3, 4, 5, 6]; |
使用解构赋值交换两数的值:
1 | let a = 8, b = 6; |
在解构数组的某些情况下,我们可能希望将剩下的元素放进另一个数组里面。以下代码的结果与使用 Array.prototype.slice()
类似:
1 | const [a, b, ...arr] = [1, 2, 3, 4, 5, 7]; |
在某些情况下,你可以在函数的参数里直接解构对象:
1 | const profileUpdate = (profileData) => { |
三、模板字符串
模板字符串是 ES6 的另外一项新的功能。 这是一种可以轻松构建复杂字符串的方法,模板字符串可以使用多行字符串和字符串插值功能。
1 | onst person = { |
这里发生了许多事情。 首先,使用反引号,而不是引号('
或者 "
)将字符串括起来。
其次,注意代码和输出中的字符串都是多行的。 不需要在字符串中插入 \n
。
上面使用的 ${variable}
语法是一个占位符。 这样一来,你将不再需要使用 +
运算符来连接字符串。 当需要在字符串里增加变量的时候,你只需要在变量的外面括上 ${
和 }
,并将其放在模板字符串里就可以了。 同样,你可以在字符串中包含其他表达式,例如 ${a + b}
。 这个新的方式使你可以更灵活地创建复杂的字符串。
四、构造函数
ES6 提供了一个新的创建对象的语法,使用关键字 class。值得注意的是,class
只是一个语法糖,它并不像 Java、Python 或者 Ruby 这一类的语言一样,严格履行了面向对象的开发规范。
在 ES5 里面,我们通常会定义一个构造函数 constructor
,然后使用 new
关键字来实例化一个对象:
1 | var SpaceShuttle = function(targetPlanet){ |
class
语法只是简单地替换了构造函数 constructor
的写法:
1 | class SpaceShuttle { |
应该注意 class
关键字声明了一个新的函数,里面添加了一个构造函数。 当用 new
创建一个新的对象时,构造函数会被调用。
你可以从对象中获得一个值,也可以给对象的属性赋值。这些操作通常被称为 getters 以及 setters。
Getter 函数的作用是可以让对象返回一个私有变量,而不需要直接去访问私有变量。Setter 函数的作用是可以基于传进的参数来修改对象中私有变量。 这些修改可以是计算,或者是直接替换之前的值:
1 | class Book { |
五、模块脚本
起初,JavaScript 几乎只在 HTML web 扮演一个很小的角色。 今天,一切不同了,很多网站几乎全是用 JavaScript 所写。
为了让 JavaScript 更模块化、更整洁以及更易于维护,ES6 引入了在多个 JavaScript 文件之间共享代码的机制。 它可以导出文件的一部分供其它文件使用,然后在需要它的地方按需导入。
如需要在 HTML 文档里创建一个 type
为 module
的脚本:
1 | <script type="module" src="filename.js"></script> |
假设有一个文件 math_functions.js
,该文件包含了数学运算相关的一些函数。 其中一个存储在变量 add
里,该函数接受两个数字作为参数返回它们的和。 你想在几个不同的 JavaScript 文件中使用这个函数。 要实现这个目的,就需要 export
它:
1 | export const add = (x, y) => { |
导出变量和函数后,就可以在其它文件里导入使用从而避免了代码冗余。 当然还可以这样导出:
1 | const add = (x, y) => { |
导出语句中添加更多值也可以导出多项:
1 | export { add, subtract }; |
import
可以导入文件或模块的一部分:
1 | // 从 math_functions.js 文件里导入多个项目 |
假设你有一个文件,你希望将其所有内容导入到当前文件中,可以用 import * as
语法来实现:
1 | import * as myMathModule from "./math_functions.js"; |
在文件中只有一个值需要导出的时候,通常会使用默认导出的 export
的语法。 它也常常用于给文件或者模块创建返回值:
1 | // 命名函数 |
export default
用于为模块或文件声明一个返回值,在每个文件或者模块中应当只默认导出一个值。 此外,你不能将 export default
与 var
、let
或 const
同时使用。
export default
需要一种 import
的语法来导入默认的导出:
1 | import add from "./math_functions.js"; |
六、JavaScript Promise
Promise 是异步编程的一种解决方案 - 它在未来的某时会生成一个值。 任务完成,分执行成功和执行失败两种情况。
Promise
是构造器函数,需要通过 new
关键字来创建。
构造器参数是一个函数,该函数有两个参数 - resolve
和 reject
。 通过它们来判断 promise 的执行结果:
1 | const myPromise = new Promise((resolve, reject) => { |
Promise 有三个状态:pending
、fulfilled
和 rejected
。
没有调用 promise 的完成方法,promise 会一直阻塞在 pending
状态里,Promise 提供的 resolve
和 reject
参数就是用来结束 promise 的。 Promise 成功时调用 resolve
,promise 执行失败时调用 reject
, 如下文所述,这些方法需要有一个参数:
1 | const myPromise = new Promise((resolve, reject) => { |
当程序需要花费未知的时间才能完成时(比如一些异步操作),一般是服务器请求,promise 很有用。
服务器请求会花费一些时间,当结束时,需要根据服务器的响应执行一些操作。 这可以用 then
方法来实现, 当 promise 完成 resolve
时会触发 then
方法:
1 | myPromise.then(result => { |
当 promise 失败时会调用 catch
方法。 当 promise 的 reject
方法执行时会直接调用:
1 | myPromise.catch(error => { |