es6 阮一峰
一、简介
- ECMAScript 和 javaScript 的关系是,前者是后者的规格,后者是前者的一种实现
- 各大浏览器的最新版本,对 ES6 的支持可以查看kangax.github.io/compat-table/es6/。随着时间的推移,支持度已经越来越高了,超过 90%的 ES6 语法特性都实现了。
- Node 是 JavaScript 的服务器运行环境(runtime)。
- babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。
下面的命令在项目目录中,安装 Babel:
$ npm install --save-dev @babel/core
- Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,可以必须使用
babel-polyfill
,为当前环境提供一个垫片。
二、变量声明
ES5 只有两种声明变量的方法:var命令和function命令。
ES6 一共有 6 种声明变量的方法:var命令、function命令、let、const命令、import命令和class命令。
2.1 let命令
(1)基本用法–块级作用域
- 原本JS只有函数作用域,没有块级作用域。ES6 新增了let变量声明命令,声明的变量仅在块级作用域内有效
- for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
(2)不存在变量提升
之前由于JS的预处理机制,var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。
let命令所声明的变量一定要在声明后使用,否则报错,因为预处理机制只找var和function
(3)会有暂时性死区、不允许重复声明
(4)块级作用域与函数声明
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句
2.2 const命令
(1)基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变,且只在声明所在的块级作用域内有效。
这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址)
(2)其他同let
不存在变量提升、会有暂时性死区、不允许重复声明
三、变量的解构赋值
es6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构
解构赋值的拷贝是浅拷贝
结构规则:
- 解构成对象,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
- 解构成数组,等号右边必须为可迭代对象
3.1 用途
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义
(4)提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
(5)函数参数的默认值
(6)遍历 Map 结构
(7)输入模块的指定方法
3.2 参考文献
https://blog.csdn.net/qq_17175013/article/details/81490923
四、字符串的扩展
4.1 方法扩展
- repeat方法返回一个新字符串,表示将原字符串重复n次。
- 除indexof方法外,定义了三种方法
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
4.2 模板字符串
用用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
模板字符串中嵌入变量,需要将变量名写在${}之中。
五、数值的扩展
5.1 方法扩展
- 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法
- Number.isinteger() 用来判断一个数值是否为整数。
六、函数的扩展
6.1 rest 参数(形式为…变量名)
rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
arguments对象不是数组,而是一个类似数组的对象。rest 参数是一个真正的数组,数组特有的方法都可以使用。
注:
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错 function f(a, ...b, c) { // ... }
函数的length属性,不包括 rest 参数。
(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
6.2 name属性
返回该函数的函数名。
function foo() {}
foo.name // "foo"
6.3 箭头函数
允许使用“箭头”(=>)定义函数。
var sum = (num1, num2) => { return num1 + num2; }
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
注:
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
6.4 函数绑定运算符
双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
七、数组的扩展
7.1 entries(),keys()和values()——用于遍历数组
它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
八、对象的扩展
8.1 super关键字
this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
九、symbol
9.1 概述
一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
是一种类似于字符串的数据类型,不能使用new命令,不能添加属性,不能与其他类型的值进行运算,因为生成的 Symbol 是一个原始类型的值,不是对象。
Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
十、Set数据结构
10.1 Set
- 类似于数组,但是成员的值都是唯一的,没有重复的值。所以可以用来去重
// 去除数组的重复成员
[...new Set(array)]
//去除字符串里面的重复字符
[...new Set('ababbc')].join('');// "abc"
//Array.from方法可以将 Set 结构转为数组。
//这就提供了去除数组重复成员的另一种方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
- 不会发生类型转换,所以5和"5"是两个不同的值。
- Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。
- Set 结构的实例有四个遍历方法,可以用于遍历成员。
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
10.2 WeakSet
- WeakSet 结构与 Set 类似,也是不重复的值的集合。
- WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 的成员只能是对象,而不能是其他类型的值。
- WeakSet 中的对象都是弱引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
10.3 Map
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
-
方法以及属性:
(1)size 属性,返回 Map 结构的成员总数
(2)set(key, value),设置键名key对应的键值为value
(3)get(key),读取key对应的键值,如果找不到key,返回undefined。
(4)has(key),表示某个键是否在当前 Map 对象之中。
(5)delete(key),delete方法删除某个键,返回true。如果删除失败,返回false。
(6) clear(),清除所有成员,没有返回值。
-
四个遍历方法,可以用于遍历成员。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。
- 与其他数据结构的互相转换
(1)Map 转为数组
Map 转为数组最方便的方法,就是使用扩展运算符(…)。
(2)数组 转为 Map
将数组传入 Map 构造函数,就可以转为 Map。
(3)Map 转为对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名
十一、Proxy(代理器)
11.1 概述
目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,在内部重新定义拦截函数。
十二、Promise 对象
12.1 含义
- Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
- Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
- Promise对象有以下两个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- 优点:
(1)可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
(2)提供统一的接口,使得控制异步操作更加容易。
- 缺点:
(1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。
(2)如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
(3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
所以处理异步可以选择Generator
12.2 基本用法
- Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(ERROR);
}
});
- Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- Promise.prototype.then()方法
作用是为 Promise 实例添加状态改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
- Promise.prototype.catch()
用于指定发生错误时的回调函数。
- Promise.prototype.finally()
不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
- Promise.all()
用于将多个 Promise 实例,包装成一个新的 Promise 实例
12.3 应用
- 加载图片
我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
- Generator 函数与 Promise 的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。
十三、Iterator 和 for…of 循环
13.1 Iterator(遍历器)的概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
13.2 Iterator(遍历器)的作用及遍历过程
- 作用
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三创造了一种新的遍历命令for…of循环
- 遍历过程
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
13.3 for…of 循环
for…of循环,作为遍历所有数据结构的统一的方法,for…of循环内部调用的是数据结构的Symbol.iterator方法。
for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串。
for…in循环读取键名,for…of循环读取键值。如果要通过for…of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
注:
对于普通的对象,for…of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for…in循环依然可以用来遍历键名。第一种解决办法是使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。另一个方法是使用 Generator 函数将对象重新包装一下。
十四、Generator 函数
14.1 基本概念
Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个Iterator对象。
Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
14.2 yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
yield表达式就是暂停标志。
yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
注:yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。
14.3 处理异步
在前面说过Promise有一些缺点,我们可以用Generator 函数实现异步编程
协程有点像函数,又有点像线程。它的运行流程大致如下。
第一步,协程A开始执行。
第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
第三步,(一段时间后)协程B交还执行权。
第四步,协程A恢复执行。
在Generator 函数中,协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作。
十五、async函数
15.1 含义
Generator 函数的语法糖。
//Generator 函数
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//async函数
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
15.2 优点
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async函数对 Generator 函数的改进,体现在以下四点:
(1)内置执行器。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便,可以用then方法指定下一步的操作。
15.3 使用
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。可以将需判断的await放再try…catch结构里面,不管这个异步操作是否成功,接下来的await都会执行。
十六、Class
16.1 定义
啦啦啦,JS也有类了
通过class关键字,可以定义类,类的内部所有定义的方法,都是不可枚举的
ES6 的类,完全可以看作构造函数的另一种写法。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
16.2 私有方法和属性、静态属性
私有:引入一个新的前缀#表示私有属性或私有方法,代替private关键字,外部不能访问
class Foo {
#a;
#b;
constructor(a, b) {
this.#a = a;
this.#b = b;
}
#sum() {
return #a + #b;
}
printSum() {
console.log(this.#sum());
}
}
静态属性:使用static关键字static myStaticProp = 42;
16.3 new.target 属性
用来返回new命令作用于的那个构造函数,如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
子类继承父类时,new.target会返回子类。利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
16.4 继承
通过extends关键字实现继承
十七、Module(模块)
17.1 概念
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
优点:
(1)将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
(2)不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
17.2 引入和使用
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
- export
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量、函数或者类。
// 变量
var firstName = 'Michael';
var lastName = 'jackson';
var year = 1958;
export {firstName, lastName, year};
// 函数
export function multiply(x, y) {
return x * y;
};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() { ... }
function v2() { ... }
//as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
- 使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。import命令具有提升效果,会提升到整个模块的头部,首先执行。
import {firstName, lastName, year} from './profile.js';
function setName(element) {
element.textcontent = firstName + ' ' + lastName;
}
//如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile.js';
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
17.3 几种模块化方法的比较
十八、编码规范
- 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
- 使用数组成员对变量赋值时,优先使用解构赋值。
- 使用扩展运算符(…)拷贝数组。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
使用 Array.from 方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
- 如果只是需要key: value的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。
相关阅读
es6入门到五连绝世之双杀(double kill )欢迎来到e6语法笔记教学一、解构赋值篇1.1、对象解构1.2、数组解构1.3、字面量解构二、循环
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。
一、为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文
写在开头: 此 Webpack 教程是阮老师在 Webpack 1.x 的版本上做的。现在 Webpack 的版本已经改动较大,建议有基础的同学,就直接上官网
ES6 学习(二)[多行字符串``,重复字符串repeat,原始字符串
1\ 我们平时定义字符串一般用引号(单引号,双引号),这些定义的单行字符串内部不能出现特殊字符(换行符等等),我们可以通过转义