ES6教程

ES6 是 JavaScript 语言的下一代标准,使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言

ES6是有史以来最实质的升级,特性涵盖范围甚广,小到受欢迎的语法糖,例如箭头函数(arrow functions)和简单的字符串插值(string interpolation),大到烧脑的新概念,例如代理(proxies)和生成器(generators);它将彻底改变程序员们编写JS代码的方式。

ps:es6是基础走向进阶必须学的

变量声明let、const

代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。

1
2
3
4
5
6
7
8
9
10
11
// 代码块内,在声明变量 PI 之前使用它会报错。
var PI = "a";
if(true){
console.log(PI); // Cannot access 'PI' before initialization
const PI = "3.1415926";
}

var PI = "a";
if(true){
console.log(PI); // a
}

let

  • let 声明的变量只在 let 命令所在的代码块内有效
  • 只能声明一次可以重新赋值,重复声明会报错
  • for 循环计数器很适合用 let,使用var就需要立即执行函数,let不需要
  • 不存在变量提升,在声明变量前调用,会报错!
1
2
3
4
5
console.log(a);  //ReferenceError: a is not defined
let a = "apple";

console.log(b); //undefined
var b = "banana";

const

  • const 声明一个只读的常量,一旦声明,常量的值就不能改变。
  • 声明必须初始化,否则会报错

解构赋值

解构赋值是对赋值运算符的扩展。解构的源,解构赋值表达式的右边部分。解构的目标,解构赋值表达式的左边部分。

数组结构的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1. 可嵌套
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1 b = 2 c = 3

2. 可忽略
let [a, , b] = [1, 2, 3];
// a = 1 b = 3

3. 不完全解构
let [a = 1, b] = [];
// a = 1, b = undefined

4. 剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1 b = [2, 3]

5. 字符串等(解构的目标若为可遍历对象,皆可进行解构赋值)
let [a, b, c, d, e] = 'hello1231';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'

6. 解构默认值
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 2] = [undefined]; // a = 2
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2

对象模型的解构赋值

  • 对象模型的解构赋值需要保持 键名统一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1. 可嵌套可忽略
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj;
// x = 'hello'

2. 不完全解构
let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj;
// x = undefined
// y = 'world'

3. 剩余运算符
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}

4. 解构默认值
let {a = 10, b = 5} = {a: 3};
// a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;

Symbol

新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

  • Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。
  • Symbol 接受一个字符串作为参数,为新创建的 Symbol 提供描述
  • 相同参数生成的 symbol 值不相等

Symbol 常作为属性名来使用,每一个 Symbol 的值都是不相等的,可以保证属性不重名

Symbol 作为对象属性名时不能用.运算符,要用方括号,因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。

1
2
3
4
5
let syObject = {};
syObject[sy] = "kk";

syObject[sy]; // "kk"
syObject.sy; // undefined

Symbol.for 方法可以检测上下文中是否已经存在使用该方法且相同参数创建的 symbol 值,如果存在则返回已经存在的值,如果不存在则新建。

1
2
3
4
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');

console.log(s1 === s2); // true

需要注意的是

Symbol 值作为属性名时,不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。

如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

使用场景

  1. 当一个复杂对象中含有多个属性的时候,很容易将某个属性名覆盖掉,利用 Symbol 值作为属性名可以很好的避免这一现象
  2. 模拟类的私有方法,ES6 中的类是没有 private 关键字来声明类的私有方法和私有变量的,但是我们可以利用 Symbol 的唯一性来模拟。

Map 与 Set

Map

Map 对象保存键值对,任何值(对象或者原始值) 都可以作为一个键或一个值。如果你需要“键值对”的数据结构,Map比Object更合适。

Maps 和 Objects 的区别

  • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

Map的构建

  • map的键可以是字符串、对象、函数、NaN

  • Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键,在扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

1
2
3
4
5
6
7
var myMap = new Map();
var keyObj = {},

myMap.set(keyObj, "和键 keyObj 关联的值");

myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.get({}); // undefined, 因为 keyObj !== {}

!!!虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。

1
2
3
4
5
6
7
var myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN); // "not a number"

var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"

作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

1
2
3
4
5
6
7
8
9
10
11
var map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"

//接受数组作为参数,实际上执行的算法
var map = new Map();
items.forEach(([key, value]) => map.set(key, value));

只有对同一个对象的引用,Map结构才将其视为同一个键

1
2
3
4
5
var map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined
// 表面是针对同一个键,但实际上这是两个值,内存地址是不一样的

Map 实例属性方法

  • size属性
  • set(key, value)
  • get(key)
  • **has(key)**,返回布尔值
  • **delete(key)**,返回布尔值
  • **clear()**,没有返回值

Map 遍历方法

for…of

对 Map 进行遍历键值对

1
2
3
4
5
6
7
8
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}

/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}

对 Map 进行遍历键对、值对

1
2
3
4
5
6
7
for (var key of myMap.keys()) {
console.log(key);
}

for (var value of myMap.values()) {
console.log(value);
}

结合数组方法

Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...),结合数组的map方法、filter方法,可以实现Map的遍历和过滤(Map本身没有mapfilter方法)。

1
2
3
4
5
6
7
8
9
let map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');

let map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生Map结构 {1 => 'a', 2 => 'b'}

forEach

Map还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。forEach方法还可以接受第二个参数,用来绑定this

1
2
3
4
5
6
7
8
9
var reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};

map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);

Map对象的操作

Map和Array的转换

Map转为数组最方便的方法,就是使用扩展运算符(…),也可以使用 Array.from 函数

1
2
3
4
5
6
7
var kvArray = [["key1", "value1"], ["key2", "value2"]];

// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
var myMap = new Map(kvArray);

var outArray1 = Array.from(myMap);
var outArray2 = [...myMap];

Map 的克隆

1
2
3
4
5
var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);

console.log(original === clone);
// 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。

Map 的合并

1
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); var second = new Map([[1, 'uno'], [2, 'dos']]);  // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three var merged = new Map([...first, ...second]);

Set

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

set判断值唯一性,也会考虑值的类型,相当用===

常用于去除数组重复成员

1
2
3
4
5
6
1. 方法一
[...new Set([1, 2, 3, 4, 4])]

2. 方法二
var s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));

Set属性方法

属性

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

方法

  • add(value):添加某个值,返回Set结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

遍历操作

keys()values()entries()forEach()

Set结构键名和键值是同一个值,所以key方法和value方法的行为完全一致

1
2
3
4
5
6
for (let x of new Set(['red', 'green', 'blue'])) {
console.log(x);
}
// red
// green
// blue

Set 中的特殊值

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
  • undefined 与 undefined 是恒等的,所以不重复;
  • NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。

类型转换

1
2
3
4
5
6
7
8
// Array 转 Set    new Set()
var mySet = new Set(["value1", "value2", "value3"]);

// Set 转 Array 用...操作符
var myArray = [...mySet];

// String 转 Set
var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"}

weakset和weakmap

待补充

Proxy 与 Reflect

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API

  1. Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
  2. Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;

第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key,receiver) {
if(key === 'age'){
return '年龄是保密的';
}
return Reflect.get(target, key,receiver);
}
}
let proxy = new Proxy(target, handler)
proxy.name //return Tom
proxy.age //return 年龄是保密的

Proxy 支持的拦截操作

  • target,目标对象 ;propKey,即key;receiver, Proxy 实例本身
  • ctx 表示目标对象上下文,args 表示目标对象的参数数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
get
get(target, propKey, receiver)

set
set(target, propKey, value, receiver)

apply
apply(target, ctx, args)

has
has(target, propKey)
// 判断 target 对象是否存在 propKey 属性,此方法不判断一个属性是对象自身的属性,还是继承的属性。

deleteProperty(target, propKey)
1
2
3
4
5
6
7
8
9
10
11
function sub(a, b){
return a - b;
}
let handler = {
apply: function(target, ctx, args){
console.log('handle apply');
return Reflect.apply(...arguments);
}
}
let proxy = new Proxy(sub, handler)
proxy(2, 1) // handle apply 1

Reflect 的好处

  1. target[key] = value,没有返回值,Reflect 有返回值,可以看设置是否成功
  2. 原来set get 都挂载在Object上,不方便做统一管理
  3. Reflect 和 Proxy 使用方法上一一对应

字符串

子串的识别

1
2
3
4
5
6
7
8
9
10
11
let string = "apple,banana,orange";
//找在哪
string.indexOf('a') // 0
string.lastIndexOf('a') // 15

//找有没有
//以下三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引。
string.includes("banana"); // true
string.startsWith("apple"); // true
string.endsWith("apple"); // false
string.startsWith("banana",6) // true

字符串重复

repeat():返回新的字符串,表示将字符串重复指定次数返回。

如果参数是小数,向下取整;参数是 负数、infinity 会报错;参数是 NaN,等同于 repeat 零次;传入字符串,先将字符串转化为数字

字符串补全

padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。

padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

  • 参数:第一个参数是生成字符串最小长度,第二个参数是用来补全的字符串,若空用空格填充
  • 如果指定的长度小于或者等于原字符串的长度,则返回原字符串
  • 如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串
1
2
3
4
5
console.log("h".padStart(5,"o"));  // "ooooh"
console.log("h".padEnd(5,"o")); // "hoooo"
console.log("h".padStart(5)); // " h"
console.log("hello".padStart(5,"A")); // "hello"
console.log("hello".padEnd(10,",world!")); // "hello,worl"

模板字符串

用反引号 ` 除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中通过 ${} 的方式加入变量、表达式、函数

模板字符串中的换行和空格都是会被保留的

1
2
3
4
5
function f(){
return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2); // Game start,have fun!

标签模板

模板字符串可以紧跟在一个函数后面,该函数将被调用来处理这个模板字符串,这种称为“标签模板”功能。

该函数依次会接收到多个参数

  • 所有静态文字都作为数组传递给第一个参数。
  • 占位符表达式的所有值都作为其余参数传递。
  • 变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。
1
2
3
4
5
6
7
function tag(stringArr, value1, value2) {
// ...
}
//等同于
function tag(stringArr, ...values) {
// ...
}

数值

二进制表示法新写法: 前缀 0b 或 0B;八进制表示法新写法: 前缀 0o 或 0O

1
2
console.log(0b11 === 3); // true
console.log(0o11 === 9); // true

对象

对象字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 属性的简洁表示法: ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值
const age = 12;
const name = "Amy";
const person = {age, name};
person //{age: 12, name: "Amy"}

// 简写方法:
const person = {
sayHi(){
console.log("Hi");
}
}
person.sayHi(); //"Hi"

// 属性名表达式: 允许用表达式作为属性名,但要将表达式放在方括号内。
const obj = {
["he"+"llo"](){
return "Hi";
}
}
obj.hello(); //"Hi"

拓展运算符

拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象,是一个非常常用的方法!

拓展运算符 后跟null、undefined、空对象,不会报错

1
2
3
4
5
6
7
8
// 基本用法
let person = {name: "Amy", age: 15}; let someone = { ...person }; someone; //{name: "Amy", age: 15}

// 可用于合并对象,合并时 属性名相同时,后面的覆盖前面的
let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
person; //{age: 15, name: "Amy"}

对象新方法

Object.assign(target, source_1, ···)

  • 用法:将源对象的所有可枚举属性复制到目标对象中。

  • 同名属性,遵循后面覆盖前面的原则

  • 如果只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。

    • null 和 undefined 放在第一个参数时,不能转化为对象,会报错
    • null 和 undefined 放在第二个参数或后面,会忽略而不会报错
    1
    2
    3
    4
    5
    Object.assign(3);         // Number {3}
    typeof Object.assign(3); // "object"

    Object.assign(null); // TypeError: Cannot convert undefined or null to object
    Object.assign(1,undefined); // Number {1}
  • assign 的属性拷贝是浅拷贝,如果源对象某个属性值是对象,那么拷贝得到的是这个对象的引用

数组的处理

1
2
Object.assign([2,3], [5]);  // [5,3]
//先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。

Object.is(value1, value2)

用来比较两个值是否严格相等,与(===)基本类似

与(===)的两点区别:

1
2
3
4
5
6
7
//一是+0不等于-0
Object.is(+0,-0); //false
+0 === -0 //true

//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN //false

数组

Array.of方法用于将一组值,转换为数组

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Array.of()  将参数中所有值作为元素形成数组。
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(1, '2', true)); // [1, '2', true]
console.log(Array.of()); // []

// Array.from(arrayLike[, mapFn[, thisArg]]),将类数组对象或可迭代对象转化为数组
// mapFn 可选函数参数,用于对每个元素进行处理,放入数组的是处理后的元素
// thisArg 用于指定 map 函数执行时的 this 对象
console.log(Array.from([1, , 3])); // [1, undefined, 3]
let map = {
do: function(n) {
return n * 2;
}
}
let arrayLike = [1, 2, 3];
console.log(Array.from(arrayLike, function (n){
return this.do(n);
}, map)); // [2, 4, 6]

函数

增加了默认参数,只有在未传递参数或undefined时,才使用默认参数,null被认为是有效的参数值

不定参数,用来表示不确定参数个数,形如,…变量名

1
2
3
4
5
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4

箭头函数

  • 箭头函数严格模式下this也指向window
  • 箭头函数 忽略任何形式的this指向改变,静态this指向
  • 箭头函数 不是个 构造器,不能new
1
2
3
4
5
6
7
8
9
10
11
12
参数 => 函数体
1. 最基本写法:
var f = v => v;
2. 当箭头函数没有参数或者有多个参数,要用 () 括起来
var f = (a,b) => a+b;
3. 当函数体有多行语句,用 {} 包裹起来
var f = (a,b) => {
let result = a+b;
return result;
}
4. 当要返回对象的时候,要用 () 将对象包裹起来
var f = (id,name) => ({id: id, name: name});
  • 箭头函数 没有 this、super、arguments 和 new.target 绑定
    • this,指向window
    • 使用arguments ,会报错
    • 不可以作为构造函数,不能使用 new 命令,否则会报错

箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象

class类

class 的本质是 function,可以看作一个语法糖,让对象原型的写法更加清晰,是构造函数的另一种写法

  • class (类)作为对象的模板被引入,可以通过 class 关键字定义类;
  • 类 不可以重复声明,类定义不会被提升
  • 类里面的方法不加function关键字 方法与方法之间不用,号隔开

constructor关键字

  • constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法
  • 一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。类包含多个 constructor 的方法,则抛出 SyntaxError错误
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
constructor (name) {
this.name = name //constructor内定义的方法和属性是实例对象自己的,
}
say () { //而constructor外定义的方法和属性则是所有实例对象可以共享的 注意!
console.log('hello')
}
}
console.log(typeof Person) //funciton
console.log(Person.prototype.constructor === Person) //true
let jon = new Person()
jon.hasOwnPrototype('name') //true
jon.hasOwnPrototype('say') //false

静态方法

不需要通过实例对象,可以直接通过类来调用的方法,其中的 this 指向类本身

  • 静态方法可以被子类继承
  • 静态方法可以通过类名调用,不能通过实例对象调用,否则会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
static doSay () {
this.say()
}
}
Person.doSay() //hello

//静态方法可以被子类继承
class Sub extends Person {
}
Sub.doSay() // hello

//静态方法可以通过类名调用,不能通过实例对象调用,否则会报错
class Person {
static sum(a, b) {
console.log(a + b)
}
}
var p = new Person()
Person.sum(1, 2) // 3
p.sum(1,2) // TypeError p.sum is not a function

setter 和 getter

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
get name () {
return 'getter'
}
set name(val) {
console.log('setter' + val)
}
}

let jon = new Person()
jon.name = 'jon' // setter jon
jon.name // getter

super

super关键字用于访问和调用 父类上的函数,可以调用父类的构造函数 也可以调用父类的普通函数

  • super在调用父类的构造函数时,会将父类构造函数的属性方法绑定到子类实例的构造对象中!super(),相当于执行了 new Father();
  • super方法必须放在子类构造函数this调用之前,不然new Father()会覆盖掉this调用生成的对象
  • super() 里面有参数,相当于给父类的构造函数传参数

extends

子类可以继承父类中的一些 方法和属性

模块

模块化,为了确定模块的依赖关系,以及输入和输出的变量。

ES6 的模块化分为导出(export) @与导入(import)两个模块。

特点

  • ES6 的模块自动开启严格模式

  • 模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。

  • 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。

  • 每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。

export、import、as

  • export 命令可以出现在模块的任何位置,但必需处于模块顶层。
  • import 命令会提升到整个模块的头部,首先执行。
  • as 对于模板内部变量重命名,可以导入重命名 也可以导出重命名
1
2
3
4
5
6
7
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }

/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom

import

  • 只读属性:可以改写 import 变量类型为对象的属性值,不能改写 import 中基本类型的值。

  • 单例模式:多次重复执行同一句 import 语句,那么只会执行一次

  • 静态执行特性:import 是静态执行,所以不能使用表达式和变量。

    1
    2
    import { "f" + "oo" } from "methods";
    // error

export default 命令

  • 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
  • export default 中的 default 是对应的导出接口变量。
  • 通过 export 方式导出,在导入时要加{ },export default 则不需要。
  • export default 向外暴露的成员,可以使用任意变量来接收。
1
2
3
4
5
6
var a = "My name is Tom!";
export default a; // 仅有一个
export default var c = "error";
// error,default 已经是对应的导出变量,不能跟着变量声明语句

import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收

Promise对象

  • Promise是解决异步流程化的一种手段,从语法上说,Promise 是一个对象

  • Promise 有三种状态:pending(进行中)、resolved(已成功)和 rejected(已失败)

    • Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变
  • Promise 是构造函数,需要new,只有一个函数参数 excutor执行器,执行器有两个参数,resolve接收成功,reject接收失败

  • promise接受then方法调用,接受两个函数参数,第一个函数接收resolve的参数,第二个参数接收reject参数

  • excutor是同步执行,then是异步调用,在 JavaScript 事件队列的当前运行完成之前,then回调函数永远不会被调用

  • 通过 .then 形式添加的回调函数,是可以链式调用的,中间需要return出去

  • 浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error));

1
2
3
4
5
6
7
8
9
10
const p = new Promise(function(resolve,reject){
resolve(1);
}).then(function(value){ // 第一个then // 1
console.log(value);
return value * 2;
}).then(function(value){ // 第二个then // 2
console.log(value);
}).then(function(value){ // 第三个then // undefined
console.log(value);
});

Generator 函数

Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

Generator 有两个区分于普通函数的部分:

  • 函数名之前有个 * ; * 用来表示函数为 Generator 函数
  • 函数内部有 yield 表达式,用来定义函数内部的状态

Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}

f = func();
f.next();
// one
// {value: "1", done: false}

f.next();
// two
// {value: "2", done: false}

f.next();
// three
// {value: "3", done: true}

f.next();
// {value: undefined, done: true}

async 函数

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数

1
2
3
4
5
6
7
8
9
async function helloAsync(){
return "helloAsync";
}

console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}

helloAsync().then(v=>{
console.log(v); // helloAsync
})

await

await 操作符用于等待一个 Promise 对象,async 函数中遇到 await 就会先暂停执行,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值

await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误