文章目录
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。 let与const
let 与 const 都不可以重复声明
let
作用:声明变量
特点:
- 不能重复声明,var声明的变量也不可以用let再次声明
- 在函数内不存在变量提升,我们称为临时性死区
- 有着严格的作用域
块状作用域
在for循环、while循环中产生块状作用域,每一次循环在函数内生成不同的作用域。如果用var声明变量的话就是在同一个作用域下声明的全局变量,只有在for循环执行完成后才执行函数。
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}) }
// 输出十个 10
for (let j = 0; j < 10; j++) {
setTimeout(function(){
console.log(j);
}) }
// 输出 0123456789
const
作用:声明只读的常量,一旦声明不能修改。
特点:同样具有作用域,块级作用域、局部作用域、全局作用域
const 一定要初始化,不能定义空值
const a; console.log(a)//会报错
当const 声明的值为对象/对象时,为特殊情况,可以为对象添加属性或方法。而不会报错。对象/数组为引用类型,引用的是地址。
const obj = {}; obj.name = 'tom'; console.log(obj);//{name: tom}
let与const声明的变量与顶层对象的关系
ES6不存在顶层对象的概念(window),但是为了向下兼容,在全局作用域下使用var声明的变量和直接声明的函数仍然是属于顶层对象的,只是let和const声明的变量和常量不再属于顶层对象。
总结
是在任何你可以 使用 const 的时候使用它。这表示尽管对象和数组的内容是可以被修改的,你仍希望保持 该数据结构不可变。而如果你想要改变你的变量,就使用 let 去声明它。
块级作用域
ES5 中 var 的作用域 — 函数级作用域
ES6 中 let 的作用域 — 块级作用域,即 { … } 中的代码块
解构赋值简单说就是模式匹配,等号左右两边的模式相同即可匹配。如果两边模式不同,有的变量可能会自动赋值undefined。左右两边依次匹配,没有匹配项则为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
对象的解构赋值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'
特殊对象的解构,键可以当作对象的属性来获对象的值
let {length:len} = "world";
console.log(len);//5
Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined ,还新增了 Symbol 。
基本用法:
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。
表示独一无二的,通常用于定义对象的属性名。[symbol()]
可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
let sy = Symbol("KK");
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false
遍历器接口
概念:
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6有一种新的遍历方式,for…of,而Iterator的主要作用,就是支持此操作。
一个数据结构有遍历器接口的特征是:
它有Symbol.iterator属性,它有next()方法,返回一个包含value和done属性的对象。
原生具备遍历器接口的数据结构:
Array,Map,Set,String,TypedArray,函数的arguments对象,NodeList对象。
1、解构赋值
2、扩展运算符(…)
3、上文提到的 yield*
4、由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,都默认调用,如
for…of
Array.from()
Map(), Set(), WeakMap(), WeakSet()
Promise.all()
Promise.race()
5、字符串是一个类似数组的对象,原生也具有Iterator接口
使用场景
作为属性名:
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
let sy = Symbol("key1");
// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject); // {Symbol(key1): "kk"}
// 写法2
let syObject = {
[sy]: "kk"
};
console.log(syObject); // {Symbol(key1): "kk"}
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject); // {Symbol(key1): "kk"}
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
注意:
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getownPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getownPropertySymbols() 和 Reflect.ownKeys() 取到。
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
for (let i in syObject) {
console.log(i);
} // 无输出
Object.keys(syObject); // []
Object.getownPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]
symbol.for
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
For…of
用法:
可以遍历数组、字符串、节点列表、argument,Map, Set及各种类数组对象,
返回值为数组的值
let arr = ['red','green','blue'];
for(let val of arr){
console.log(val);
}
//red green blue
而for…in,返回值为键
for(let i in arr){
console.log(i);
}
//0 1 2
与其他遍历方式比较
for…in遍历出的是key,for…of遍历出的是value,且可遍历的类型更多,
forEach只能遍历纯数组,不可以使用break和continue,for…of 遍历的类型多,且可以使用break和continue
Iterator遍历器
ES6定义的一个接口。
实现遍历器接口的数据类型都可以使用for…of。
Object没有实现遍历器接口,Array实现了遍历器接口
字符串新增特性模板字符串 ``(反引号)
var aa = '222';
var b = `这是测试数据 ${aa}`;
console.log(b);//这是测试数据222
方法
repeat(N) 把字符串重复输出N次
padStart(N,补全符) 从前补全
padEnd(N,补全符) 从后边补全
Includes(“x”) 验证字符串中是否包含某字符
startsWith(“x”) 验证字符串是否以某字符开始
endsWith(“x”) 验证字符串是否以某字符结束
数组数组对象新增方法
遍历数组:相似函数map()
arr.forEach(function(val, index, [arr]){})
筛选出数组中符合条件的值:返回值新数组
arr.filer(function(val, index, [arr]){筛选条件语句})
检测数组中是否有符合条件的值:返回值布尔值。相似函数every()
arr.some(function(val, index, [arr]){})
筛选出第一个符合条件的元素 arr.find()
let arr = [1,2,0,3,-8,34,6];
let val = arr.find(function(v){
if(v > 2){
return true;
}
});
//简写
let val = arr.find(function(v){
return val>3;
});
//再简写
let val = arr.find(val => val>3);
console.log(val);
findeIndex()返回符合条件的第一个元素的索引
entries() 与for…of配合使用,遍历数组中的键与值
let arr = [1,2,0,3,-8,34,6];
for(let [k,v] of arr.entries()){
console.log(v);
}
keys()
values()
includes() 检测数组中是否有某值
扩展运算符(…)
适用于所有实现Iterator(遍历器)接口的数据类型。
相当于rest参数的逆运算,把数组元素转换为逗号分隔的列表。
作用:
-
参数展开
收集
function func(a,b,...c){ console.log(a,b,...c); } func(2,3,4,5,6);
展开
-
数组展开
数组连接
arr1 = [1,2,4,5]; arr2 = [3,5,6,67,7]; arr = [...arr1, ...arr2];
-
json 展开
应用:复制数组(克隆,深复制),合并数组等。
(1)克隆
let arr = [100,200,300];
let arr1 = [...arr];
console.log(arr1);
(2)合并数组
let arr1 = [100,200,300];
let arr2 = ['aa','bb'];
let arr3 = [...arr1,...arr2];//ES6
let arr4 = arr1.concat(arr2);//ES5
console.log(arr3);
console.log(arr4);
(3)与解构赋值一起用,arg会被赋值为未匹配元素组成的数组
var [a,b,...arg] = [1,2,3,4,5,6];
console.log(a);//1
console.log(b);//2
console.log(arg);//[3,4,5,6]
(4)作用于字符串,将字符串转为字符元素组成的数组
let arr = [...'hello'];
console.log(arr);//[ 'h', 'e', 'l', 'l', 'o' ]
如何判断两个数组是否相等?
js中没有直接判断两个数组相等的方法,因为数组是对象,通过“”或“=”判断的是两个数组是否引用同一个对象。正确的判断方式如下:
function ArrayIsEqual(arr1,arr2){//判断2个数组是否相等
if(arr1===arr2){//如果2个数组对应的指针相同,那么肯定相等,同时也对比一下类型
return true;
}else{
if(arr1.length!=arr2.length){
return false;
}else{//长度相同
for(let i in arr1){//循环遍历对比每个位置的元素
if(arr1[i]!=arr2[i]){//只要出现一次不相等,那么2个数组就不相等
return false;
}
}//for循环完成,没有出现不相等的情况,那么2个数组相等
return true;
}
}
Array构造函数新增方法
Array.from()
可以把类数组对象和实现了遍历器接口的数据类型转化为真正的数组对象。
let arrLike = {
0:"ss",
1:"sa",
2:"ww",
length:3
};
let arr = Array.from(arrLike);
console.log(arr);
Array.of()
可以把一组值转为数组对象。与我们使用构造函数创建数组的方式很相似。
let arr = Array.of(10,20,30);
console.log(arr);//[ 10, 20, 30 ]
let arr1 = new Array(10l,20,30);
console.log(arr1);
与构造函数不同的是,参数为一个时,代表创建有一个指定元素的数组。而用构造函数创建时,一个参数表示创建一个长度为指定长度的空数组。
类ES6中新增了类的用法:可以用关键字class来声明类,类名首字母大写
类的本质就是构造函数,它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
定义
// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
//定义方法
sum(a, b){}
}
注意:
- 类不能重名
- 必须有constructor构造函数
- 方法不能用function关键字,且后边没有分号
- 在打印实例对象时,constructor 中的所有属性和方法都可以打印出来,但是直接声明的方法不可打印出来。实例对象拥有类中的所有属性何方法,包括constructor 内外所有的方法和属性。
静态方法
通过关键字static定义静态方法。
访问:只能通过类名访问,通过实例对象访问会报错。
class Person{
constructor(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log('eat');
}
}
static say(){
console.log('say');
}
}
var p = new Person('John', 10);//Person { name: 'John', age: 10, eat: [Function] }
Person.say();//say
p.say();//报错, TypeError: p.say is not a function
继承:extends
子类可以继承父类中constructor中的属性和方法,constructor之外的方法不可继承。
注意:如果父类中没有constructor,子类不可继承。
在继承中,子类的constructor中必须写super()用以继承父类的属性和方法。需要注意的是,子类的constructor函数的参数是包括父类参数在内的所有参数,而super函数的参数为父类中定义的参数。
class Son extends Person{
constructor(name,age,sex){
super(name,age);
this.sex = sex;
}
}
var s = new Son('lili',20,'girl');
console.log(s);//Son { name: 'lili', age: 20, eat: [Function], sex: 'girl' }
父类中无constructor,不可继承
var Father = {
// ...
}
class Child extends Father {
// ...
}
var c = new Child();
//console.log(c);//TypeError: Class extends value #<Object> is not a constructor or null
Object.setPrototypeOf(Child.prototype, Father);//可以实现继承
super的用法:
(1)将super用于子类的constructor,可继承父类的属性和方法。
注意:子类 constructor 方法中必须有 super ,且必须出现在 this 之前。
(2)将super用于子类的普通函数,可以执行父类的方法。但不能执行父类的静态方法。
(3)super在普通函数中代表父类的原型对象,在静态方法中代表父类。
class Father{
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log(this.x+this.y);
}
say(){
return "我是爸爸";
}
}
class Son extends Father{
constructor(x,y){
super(x,y);//调用父类的构造函数
}
say(){
console.log(super.say() + "的儿子");//通过super方法调用父类的普通方法
}
}
var son = new Son(1,2);
son.sum();
当子类需要拓展父类的方法时,在子类的构造方法中应该先调用父类的构造方法再定义自己的属性和方法。
即子类的this要放在super之后。
class Father{
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log(this.x+this.y);
}
}
class Son extends Father{
constructor(x,y){
super(x,y);//调用父类的构造函数
this.x = x;
this.y = y;
}
plus(){
console.log( this.x - this.y);
}
}
var son = new Son(5,2);
son.sum();
son.plus();
对象
对象属性的简写:
let username = 'aa';
let age = 12;
let obj = {
username,
age
}
console.log(obj);//{ username: 'aa', age: 12 }
构造函数新增方法
Object.is()
用于比较,类似于全等于“===”。
但有一些区别,当判断0与-0时,返回false;判断NaN与NaN时,返回true。用全等号结果与它相反
console.log(Object.is(0,-0));//false
console.log(Object.is(NaN,NaN));//true
Object.assign(源,目标)
合并到源
var obj3 = Object.assign({},obj1,obj2); //合并到ob j3
合并对象。
Object.keys() 返回对象的属性名组成的数组
Object.values() 返回对象的属性值组成的数组
Object.entries() 返回包含对象的属性名及属性名的二维数组
函数箭头函数
语法:
参数 => 函数体
()=>{}
const fn = (x) => {console.log(x)};
fn(6);
特点:
- 如果函数体内只有一条语句,那么大括号与return可以省略。如果参数只有一个,小括号也可以不写。
- 箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply() 。
- 箭头函数没有原型属性
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 常用于函数作为参数的情况
//ES5写法:
function sum(a,b){
return a+b;
}
//ES6写法
const sum = (a,b) => a+b;
作用:
sort 排序(原理冒泡排序)
let arr = [2,3,5,1,44];
arr.sort(function(a,b){
return a-b
});
//箭头函数简写
arr.sort((a,b) => a-b);
console.log(arr);
普通函数
rest参数
Rest 参数接受函数的多余参数,组成一个数组,放在形参的最后,形式如下:
function func(a, b, ...theArgs){
// ...
}
rest 参数后面不能再有其他参数(即只能是最后一个参数),否则会报错。
函数的length属性,不包括rest参数。
(function(a){}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
Rest参数和arguments对象的区别:
- rest参数只包括那些没有给出名称的参数,arguments包含所有参数
- arguments 对象不是真正的数组,而rest 参数是数组实例,可以直接应用sort, map, forEach, pop等方法
- arguments 对象拥有一些自己额外的功能
从 arguments 转向数组
Rest 参数简化了使用 arguments 获取多余参数的方法
// arguments 方法
function func(a, b){
var args = Array.prototype.slice.call(arguments);
console.log(args)
}
func(1,2)
// Rest 方法
function func(a, b, ...args){
// ...
}
注意,rest 参数之后不能再有其他参数(即,只能是最后一个参数),否则会报错
function func(a, ...b, c) {
// ...
}
// Rest parameter must be last formal parameter
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, b, ...c)).length // 2
Rest参数可以被结构(通俗一点,将rest参数的数据解析后一一对应)不要忘记参数用[]括起来,因为它是数组
function f(...[a, b, c]) {
return a + b + c;
}
f(1) //NaN 因为只传递一个值,其实需要三个值
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (第四值没有与之对应的变量名)
rest得到的是纯数组,可以用forEach遍历
function fn(...args){
let sum = 0;
args.forEach(function (val){
sum += val;
});
console.log(sum);
}
fn(1,2,4);
Generator 函数
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
Generator 有两个区分于普通函数的部分:
执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
var f = func();
f.next();//one
f.next();//two
f.next();//three
还可以使用 for… of 循环遍历 Generator 函数生产的 Iterator 对象。
return 方法
return 方法返回给定值,并结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。
set与map数据结构set与map的数据类型仍是Object(对象)。作为一种由key值标记的数据容器。
map与set对象承载的数据元素可以按照插入时的顺序,被迭代遍历。
set数据结构
定义:
类似于数组,但其元素是唯一的。参数可以是所有实现了遍历器接口的数据类型
作用:
快速实现数组去重。
Set构造函数
可以接收一个数组;
参数可以是所有实现了遍历器接口的数据类型
创建set数据
let set = new Set();
将set类型转成数组,可以用扩展运算符
let arr = [2,3,4,3,6];
let newarr = [...new Set(arr)];
console.log(newarr);
应用:为数组去重
var arr1 = [3,4,5,6,74,43,2,3,4,5];
var arr2 = new Set(arr1);
//以上即实现了数组去重,但有一个问题就是 arr2 此时已经不是数组类型,而是变成了对象。
//我们可以通过拓展运算符将其转换为数组
var arr2 = [...new Set(arr1)]
size
add() 添加成员
delete(值) 删除成员
has() 判断某值是否存在
clear() 清除所有成员
注意:
set类型与数组不同,它只有值没有索引。
set 遍历
forEach()
keys() 等同于values() 因为它没有键
values()
entries()
For…of
Weakest
与set类似,同样是元素不重合的集合。
语法:
new WeakSet([iterable]);
与 set的区别
示例
var ws = new WeakSet([String],[Number]);
console.log(ws); //WeakSet { <items unkNown> }
new WeakSet([{name:1},{name:2}])
console.log(WeakSet);
add(): 添加元素
has(): 检测是否包含
delete(): 删除元素
clear(): 清空
Map
新增数据结构,与对象类似,但与传统对象不同的是,传统对象的键只能是字符串,而Map的键可以是任意类型的值。Map结构提供了“值-值”的对应,是一种更完善的Hash结构实现。
实现了Iterrator接口。
let m = new Map();
m.set(['ss','dd']);
console.log(m);
参数:必须是二维数组,每个数组有两个值。
for of 遍历对象
方法:
get(key): 获取值
has()
clear()
forEach
For…of
values
keys
Entries
size
WeakMap
键必须是对象,不可遍历,没有实现遍历器接口。
方法:get, set, has, delete,
PromisePromise是异步编程的一种解决方案。它是一个构造函数,使用时需要new,参数必须是回调函数。
Promise就是为了解决回调地狱的问题而产生的。
作用:
- 解决回调地狱
- 向同步操作那样执行异步操作
状态
Promise异步操作有三种状态:
pending:进行中
Fulfilled:异步操作完成
rejected:已失败
除了异步操作的结果,其他任何操作都无法改变这个状态。
语法:
//定义
cost p = new Promise(function(resolve,reject){});//回调函数的参数固定
//调用
p.then(function(){},function(){});//then中的两个函数即对应着构造函数中的resolve(成功时执行)与reject(失败时执行)
then方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
可以多次调用then方法,为了保证链式操作正常执行,通常只写正确状态的函数参数,最后通过catch捕捉错误状态的执行函数。
示例:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前两个回调函数的错误
});
Promise.all()
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
p 的状态由 p1、p2、p3 决定,分成两种情况。
- (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function(id){
return getJSON("/post/" + id + ".json");
});
Promise.all(promises).then(function(posts) {
// ...
}).catch(function(reason){
// ...
});
Promise.race()
var p = Promise.race([p1,p2,p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
模块化ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
as重命名
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
复合使用
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
as 重命名
default 默认接口
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
export { foo, bar } from "methods";
// 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar
import { foo, bar } from "methods";
export { foo, bar };
/* ------- 特点 1 --------*/
// 普通改名
export { foo as bar } from "methods";
// 将 foo 转导成 default
export { foo as default } from "methods";
// 将 default 转导成 foo
export { default as foo } from "methods";
/* ------- 特点 2 --------*/
export * from "methods";