文章目录

  • let
  • const
  • 解构赋值
  • 解构赋值的用途
  • 数组的解构赋值
  • 对象的解构赋值
  • 字符串的解构赋值
  • 函数参数的解构赋值
  • 模板字符串
  • 字符串新增方法
  • includes(), startsWith(), endsWith()
  • repeat()
  • padStart(),padEnd()
  • trimStart(),trimEnd()
  • replaceAll()
  • 属性的简洁表示法
  • 数值的扩展
  • 函数的扩展
  • 函数参数的默认值
  • 箭头函数
  • rest 参数(...变量名)
  • 数组的扩展
  • 扩展运算符
  • Array.from()
  • Array.of()
  • flat、flatMap
  • 对象的扩展
  • 属性的简洁表示法
  • 解构赋值
  • 对象的新增方法
  • Object.is()
  • Object.assign()
  • Object.getOwnPropertyDescriptors()
  • Object.keys(),Object.values(),Object.entries()
  • Set
  • 实例化对象
  • ES6模块化暴露、引入模块


let

ES6 新增了let命令,用来声明变量,它的用法类似于var。
1.let所声明的变量,只在let命令所在的代码块(块级作用域:{}、if、else、while、for)内有效。

{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

2.变量不能重复声明。
let不允许在相同作用域内,重复声明同一个变量。但for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)。

3.不存在变量提升(即变量可以在声明之前使用,值为undefined)。

主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

es6转换为pdf在线_数组


4.暂时性死区(temporal dead zone,简称 TDZ)

在代码块内,使用let命令声明变量之前,该变量都是不可用的。

var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
typeof x; // ReferenceError
let x;
typeof undeclared_variable // "undefined"

5.不影响作用域链。
当前作用域没有的变量会向上一级寻找。

let name = "zagiee";
function fn(){
	console.log(name);
}
fn();//zagiee

ES6 允许块级作用域的任意嵌套。下面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。内层作用域可以定义外层作用域的同名变量。

{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 报错
}}}};

const

1.一定要赋初始值。
2.一般常量使用大写。
3.与let命令相同:只在声明所在的块级作用域内有效,const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
4.常量的值不能修改,但对于数组和对象的元素修改,不算对常量的修改,不会报错。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把它指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行,执行后a变成空数组了
a = ['Dave'];    // 报错

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值。

解构赋值的用途

(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)提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用。

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};
let { id, status, data } = jsonData;

(4)遍历 Map 结构

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

数组的解构赋值

let [a, b, c] = [1, 2, 3];//相当于声明了三个变量,还对它们进行了赋值。

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。

let [x, y] = [1, 2, 3];
x // 1
y // 2

如果解构不成功,变量的值就等于undefined。

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。

es6转换为pdf在线_javascript_02


事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"

解构赋值允许指定默认值。

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null];//x = null,只有当一个数组成员严格等于undefined,默认值才会生效。

对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。变量没有对应的同名属性,会导致取不到值,最后等于undefined。

let { rat, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
rat// undefined

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

let { log, sin, cos } = Math;

const { log } = console;
log('hello') // hello

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz, bar} = { foo: 'aaa', bar: 'bbb' };
baz // "aaa",foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
foo // error: foo is not defined
baz // "bbb"

解构赋值允许指定默认值。

var {x, y = 5} = {x: 1};
x // 1
y // 5

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
let x;
{x} = {x: 1};//{x}会被理解成一个代码块
// SyntaxError: syntax error

// 正确的写法
let x;
({x} = {x: 1});

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

函数参数的解构赋值

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

模板字符串

模板字符串是增强版的字符串,用反引号(`)标识。
1.它可以当作普通字符串使用。
2.也可以用来定义多行字符串(所有的空格和缩进都会被保留)。
3.在字符串中嵌入变量(将变量名写在${}之中,{}中可以写任意的 JavaScript 表达式)。

let name = "Bob", time = "today";
`Hello ${name}, 
how are you ${time}?`

4.如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

let greeting = `\`Yo\` World!`;

5.模板字符串内还可以调用函数。

function fn() {
  return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar

字符串新增方法

includes(), startsWith(), endsWith()

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
三个方法都支持第二个参数,表示开始搜索的位置。

let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
s.includes('Hello', 6) // false

repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

padStart(),padEnd()

如果某个字符串不够指定长度,会在头部(padStart())或尾部(padEnd())补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'12'.padStart(10, '0') // "0000000012"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。

trimStart(),trimEnd()

它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';
s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

replaceAll()

字符串的实例方法replace()只能替换第一个匹配。replaceAll()方法,可以一次性替换所有匹配。用法与replace()相同,返回一个新字符串,不会改变原字符串。

'aabbcc'.replaceAll('b', '_')// 'aa__cc'

属性的简洁表示法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

//属性简写
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于const baz = {foo: foo};

//方法简写
const o = {
  method() {//method: function() {
    return "Hello!";
  }
};

数值的扩展

用Number方法将0b和0o前缀的二进制和八进制字符串数值转为十进制

Number('0b111')  // 7
Number('0o10')  // 8

新的数据类型 BigInt只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。

0b1101n // 二进制
0o777n // 八进制
0xFFn // 十六进制
42n === 42 // false,BigInt 与普通整数是两种值,它们之间并不相等。
typeof 123n // 'bigint'

JavaScript 原生提供BigInt函数,可以用它生成 BigInt 类型的数值。转换规则基本与Number()一致,将其他类型的值转为 BigInt。

BigInt(123) // 123n
BigInt('123') // 123n
BigInt('123n') // SyntaxError
BigInt(1.5) // RangeError

函数的扩展

函数参数的默认值

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

if (typeof y === 'undefined') {
  y = 'World';
}

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined:解决方法:第一行换成function foo({x, y = 5} = {}) {

与解构赋值相结合。

es6转换为pdf在线_前端_03

箭头函数

对于普通函数来说,内部的this指向函数运行时所在的对象。而箭头函数没有自己的this对象,this始终指向定义时上层作用域中的this,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

var f = v => v;//当形参只有一个时可以省略小括号,当代码体只有一句时可以省略花括号和return
// 等同于
var f = function (v) {
  return v;
};

window.num='e'
var sum = (num) => {
	return this.num; 
}
sum()//"e"

箭头函数的一个用处是简化回调函数。

// 普通函数写法
[1,2,3].map(function (x) {
  return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);

不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

es6转换为pdf在线_前端_04


不能使用argument,下面代码会报argument is not defined

es6转换为pdf在线_es6_05

箭头函数的应用场景:

es6转换为pdf在线_数组_06


使用箭头函数后,

es6转换为pdf在线_es6转换为pdf在线_07


es6转换为pdf在线_javascript_08


箭头函数适用于与this无关的回调:定时器,数组的方法回调;不适用于与this有关的回调:事件回调,对象的方法。

rest 参数(…变量名)

用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function test(...values) {
  return values;
}
test(2, 5, 3) // [2, 5, 3]

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

数组的扩展

扩展运算符

扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

该运算符主要用于函数调用。

function add(x, y) {
  return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42

其它应用:

//求数组最大元素:
Math.max.apply(null, [14, 3, 77])// ES5 的写法
Math.max(...[14, 3, 77])// ES6 的写法
// 等同于
Math.max(14, 3, 77);//由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max()函数,将数组转为一个参数序列,然后求最大值。

//通过push()函数,将一个数组添加到另一个数组的尾部:
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

复制数组,直接复制的话,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。

const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2]

扩展运算符提供了复制数组的简便写法。下面的两种写法,a2都是a1的克隆。

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

扩展运算符提供了数组合并的新写法。

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]// [ 'a', 'b', 'c', 'd', 'e' ]
//两种方法都是对原数组成员的引用(浅拷贝)。如果修改了引用指向的值,会同步反映到新数组。

扩展运算符可以与解构赋值结合起来,用于生成数组。将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

扩展运算符还可以将字符串转为真正的数组。

[...'hello']// [ "h", "e", "l", "l", "o" ]

Array.from()

Array.from()方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};//一个类似数组的对象,Array.from()将它转为真正的数组。
// ES5 的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6 的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

字符串和 Set 结构都具有 Iterator 接口,因此可以被Array.from()转为真正的数组。

Array.from('hello')// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

Array.of()

Array.of()方法用于将一组值,转换为数组。如果没有参数,就返回一个空数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]

这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

Array() // []
Array(4) // [, , , ,]参数只有一个正整数时,实际上是指定数组的长度。
Array(3, 11, 8) // [3, 11, 8]只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。

flat、flatMap

flat()将多维数组转化为低维数组:(参数是深度,默认为1,降1维)

console.log([1,2,3,[4,5],6].flat());//[1, 2, 3, 4, 5, 6]
console.log([1,2,3,[4,5,[7,8]],6].flat());//[1, 2, 3, 4, 5, [7,8], 6]
console.log([1,2,3,[4,5,[7,8]],6].flat(2));//[1, 2, 3, 4, 5, 7,8, 6]

flatMap()相当于两个操作的结合:如果Map返回的结果是多维数组,可用flat降维。

es6转换为pdf在线_数组_09

对象的扩展

属性的简洁表示法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};

解构赋值

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

对象的新增方法

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。

Object.is()用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

Object.is('foo', 'foo')// true
Object.is({}, {})// false

+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

Object.getOwnPropertyDescriptors()

返回指定对象所有自身属性(非继承属性)的描述对象。

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

const source = {
  set foo(value) {
    console.log(value);
  }
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true }

上面代码中,source对象的foo属性的值是一个赋值函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。这是因为Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。

这时,Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以实现正确拷贝。

const source = {
  set foo(value) {
    console.log(value);
  }
};
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }

Object.keys(),Object.values(),Object.entries()

Object.keys()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)// ["foo", "baz"]

Object.values()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)// ["bar", 42]
Object.values('foo')// ['f', 'o', 'o']

Object.entries()将对象转化为一个二维数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);
Object.fromEntries(entries)// { foo: "bar", baz: 42 }

Set

去除数组的重复成员

[...new Set([1, 2, 2, 2, 5, 3, 4, 5])]//方法1
Array.from(new Set([1, 2, 2, 2, 5, 3, 4, 5]));//方法2

实例化对象

ES5通过构造函数实例化一个对象的语法:

function Phone (brand, price){
this.brand = brand;
    this.price = price;
}
// 添加方法
Phone.prototype.call = function () {
    console.log("打电话!");
}
// 实例化对象
let Oneplus = new Phone('一加','4000');
console.log(Oneplus);
Oneplus.call();

es6转换为pdf在线_前端_10


ES6通过class实例化对象的实现:

class Phone{
    constructor(brand, price) {//构造方法名字不能改
        this.brand = brand;
        this.price = price;
    }
    call(){//不能用es5 的方法写call:function(){},因为call内部的this会指向window对象。
        console.log("打电话!!");
    }
}
let Huawei = new Phone('华为',5000);
console.log(Huawei);
Huawei.call();

es6转换为pdf在线_es6转换为pdf在线_11


静态属性属于类而不属于实例对象:

class Phone{
 static name = 'phone';
}
Phone.price = 1000;
Phone.prototype.tag = 'fashion';

let Huawei = new Phone('华为',5000);
console.log(Huawei.name);//undefined
console.log(Phone.name);//phone
console.log(Huawei.price);//undefined
console.log(Huawei.tag);//fashion

ES6模块化暴露、引入模块

m2中使用m1的功能:
m1.js:暴露

//1.分别暴露
//export let name = 'zagiee';
//export function eat(){
 //    console.log("eat");
 //}
 //2.统一暴露
 //let name = 'zagiee';
 //function eat(){
 //    console.log("eat");
 //}
 //export {name, eat};
 //3.默认暴露
 export default {
     name : 'zagiee',
     eat : function(){
         console.log('eat');
     }
 }

m2.html:引入

<script type="module">
    //1.通用的导入方式
    // import * as m1 from "m1.js";
    // console.log(m1.name);
    //m1.eat();//若用的是默认暴露时,要写成m1.default.eat();
    //2.解构赋值方式
    // import {name, eat} from "m1.js";
    // console.log(name);//直接用即可,无需前缀
    // import {default as m1} from "m1.js";//只针对默认暴露
    // console.log(m1.name);
    // 3.简便方式,只针对默认暴露
    //import m1 from "m1.js";
    //console.log(m1.name);
    //4.动态import,用的时候再导入提高执行效率。
    const btn = document.getElementById('btn');
    btn.onclick = function(){
        import('./m1.js').then(module=>{//返回结果是一个promise对象,成功的值就是m1.js暴露出来的对象。
            module.eat();
        })
    }
</script>