48-ECMAScript6详解

ECMAScript6详解

ECMAScript6 简介:
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了
它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
ECMAScript 和 JavaScript 的关系:
要讲清楚这个问题,需要回顾历史
1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA
希望这种语言能够成为国际标准
ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript
这个版本就是 1.0 版
因此,ECMAScript (宪法)和 JavaScript(律师) 的关系是,前者是后者的规格,后者是前者的一种实现
ES6 与 ECMAScript 2015 的关系:
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本
ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等
搭建前端环境:
Node 环境:
简单的说 Node.js 就是运行在服务端的 JavaScript
JavaScript程序,必须要依赖浏览器才能运行,没有浏览器怎么办,OK,Node.js帮你解决
Node.js是脱离浏览器环境运行的JavaScript程序,基于Google的V8引擎,V8引擎执行Javascript的速度,非常快,性能非常好
Node.js有什么用:
如果你是前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务
那Node.js是一个非常好的选择
Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js
当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择
下载:
官网:https://nodejs.org/en/
中文网:http://nodejs.cn/
我的地址:
链接:https://pan.baidu.com/s/1AHeIQ_TxlkkfI4p1zWYn-Q
提取码:alsk
LTS:长期支持版本
Current:最新版
安装:Windows下双击点击安装——>Next——>finish
注意:
node-v14.5.0-x64.msi 最新版本,如果是win7系统的话,可能安装不了
如果是win7系统,安装node-v10.14.2-x64.msi这个版本
查看版本:
在dos窗口中执行命令查看版本号
//node -v
创建文件夹 lagou-node
用vscode打开目录,其目录下创建 hello.js
//console.log("hello,nodejs");
打开命令行终端:Ctrl + Shift + y
输入命令
//node hello.js
第一次运行,可能会报错

48-ECMAScript6详解_第1张图片

兼容性的问题,以管理员身份运行即可

48-ECMAScript6详解_第2张图片

实际上你可以看成我们将浏览器可以执行并且显示js的功能提取出来,形成了node.js,即他是单纯的以js来操作的,使得js独立出来
服务器端应用开发
const http = require("http"); // node中自带的require引入方法,http也是node中自带的服务对象,js自然可以远程的进行数据传递,包括他们自身
http.createServer( function(request,response){

    // 发出http请求的头部信息
    // http的状态码:200;OK
    // 请求的内容类型:text/plain
    response.writeHead(200,{"Content-Type": "text/plain"});

    //响应的数据 "hello,welcome!" ,此时,并不支持中文(以后一定会解决!)
    response.end("hello,welcome!");
} ).listen(8993); // 监听端口,注意端口的占用,否则会报错(运行时报错)

console.log("服务器已启动,请访问 http://127.0.0.1:8993");
服务器启动成功后,在浏览器中输入:http://localhost:8993/ 查看webserver成功运行,并输出html页面
停止服务:ctrl + c
其实node.js安装后,就可以在cmd里执行对应目录下的js文件了,前提是环境变量配置,但是一般来说node.js是默认给你配置好的,一般也包括npm
NPM环境:
NPM全称Node Package Manager,是Node.js包管理工具
是全球最大的模块生态系统,里面所有的模块都是开源免费的,也是Node.js的包管理工具,相当于后端的Maven的pom.xml
如果一个项目需要引用很多第三方的js文件,比如地图,报表等,文件杂而乱,自己去网上下载,到处是广告和病毒
那么,我们就想办法,把这些js文件统一放在一个仓库里,大家谁需要,谁就去仓库中拿过来,方便多了
npm就是这个仓库系统,如果你需要某个js文件,那就去远程仓库中下载,放在本地磁盘中,进而引用到我们的项目中
NPM工具的安装位置:
node的环境在安装的过程中,npm工具就已经安装好了
Node.js默认安装的npm包和工具的位置:Node.js目录\node_modules
在这个目录下你可以看见 npm目录,npm本身就是被NPM包管理器管理的一个工具,说明 Node.js已经集成了npm工具
//在命令提示符输入 npm -v 可查看当前npm版本
npm -v
使用npm管理项目:
全新创建一个目录,作为项目目录,使用dos命令进入此目录,输入命令
//npm init

//接下来是一堆项目信息等待着你输入,如果使用默认值或你不知道怎么填写,则直接回车即可。
//package name: 你的项目名字叫啥
//version: 版本号
//description: 对项目的描述
//entry point: 项目的入口文件(一般你要用那个js文件作为node服务,就填写那个文件)
//test command: 项目启动的时候要用什么命令来执行脚本文件(默认为node app.js)
//git repository: 如果你要将项目上传到git中的话,那么就需要填写git的仓库地址(这里就不写地址了)
//keywirds: 项目关键字
//我也不知道有啥用,所以我就不写了,可能是不能用的单词,就如java关键字类似,有些地方不能写
//author: 作者的名字(也就是你叫啥名字)
//license: 发行项目需要的证书(这里也就自己玩玩,就不写了)

最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
我们之后也可以根据需要进行修改
上述初始化一个项目也太麻烦了,要那么多输入和回车,想简单点,一切都按照默认值初始化即可
npm init -y
修改npm镜像 和 存储地址
NPM官方的管理的包都是从 http://npmjs.com下载的,国外的,所以这个网站在国内速度很慢
这里推荐使用淘宝 NPM 镜像 http://npm.taobao.org/ ,淘宝 NPM 镜像是一个完整npmjs.com 镜像
同步频率目前为 10分钟一次(每10分钟去国外的下载一次),以保证尽量与官方服务同步
设置镜像和存储地址:
#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
//npm config set registry https://registry.npm.taobao.org
#设置npm下载包时保存在本地的地址(建议英文目录)
//npm config set prefix "E:\\repo_npm" 分号(引号)可加可不加,会帮你创建对应目录,可以检查一下
#查看npm配置信息
//npm config list
npm install命令的使用 :
//npm install jquery
使用 npm install 安装依赖包的最新版
模块安装的位置:项目目录\node_modules
安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本,以后在执行时,或者操作时,会去里面对应的地址下载或者自动下载,一般可能需要执行,或者自动的刷新(类似于idea的pom的刷新,这些我们并不需要管),当然安装时一般都会下载,所以上面说明的是安装后的操作可能性
同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 < dependencies >

48-ECMAScript6详解_第3张图片

注意:上述可能因为网速导致失败,在排除代码没错的情况下,失败了,可能是网速问题,即多试几次
ES6基本语法 :
ES标准中不包含 DOM 和 BOM的定义,只涵盖基本数据类型、关键字、语句、运算符、内建对象、内建函数等通用语法
本部分只学习前端开发中ES6的必要知识,方便后面项目开发中对代码的理解
let声明变量:
/*
{
    var a = 0; // var声明的变量是全局变量,注意在方法里不同,var不够严谨
    let b = 0; // let声明的变量是局部变量,通常不是括起来的,let够严谨
}
//单纯的{},必然执行且操作,相当于一个匿名对象且执行
console.log(a);
console.log(b); //b is not defined:b没有定义
*/

//------------------------------------------------

/*
// var可以声明多次
// let只能声明一次
var m = 1;
var m = 2;  //var可以重复声明
let n = 3;
let n = 4; //SyntaxError: Identifier 'n' has already been declared(语法错误:n已经声明过了)

console.log(m);
console.log(n);
*/

//------------------------------------------------

// var 声明的变量会全局存储
// let 声明的变量只能在执行后才存储

console.log( x ); //没有报错,输出:undefined,注意,在其他的var x = x或者alert(x)中,后面的代码不会执行,但是这里会,说明log中处理了错误,类似于java的try,即错误的捕获,具体可以百度查看
var x = "苹果";

console.log(y);  //y is not defined(y没有定义)
let y = "香蕉";
可以得出let才是真正的是一个严谨的语法,即严谨的局部变量
const声明常量:
const 声明常量,为只读变量
一旦声明之后,其值是不允许改变的
一但声明必须初始化,否则会报错 SyntaxError: Missing initializer in const declaration(语法错误,声明常量丢失了初始化)
const PI = 3.14;
PI = 3.1415;  //Assignment to constant variable.(声明的是常量)

console.log( PI ); 
解构赋值:
解构赋值是对赋值运算符的扩展
它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值
解构,顾名思义,就是将集合型数据进行分解,拆分,把里面的值逐一遍历获取
在代码书写上简洁且易读,语义更加清晰明了,也方便了复杂对象中数据字段获取
var arr = [1,2,3];

// 传统的js
let a = arr[0];
let b = arr[1];
let c = arr[2];
console.log(a,b,c); //可以连着写,因为log方法可以输出多个参数,用空格隔开

//es6的解构
var [x,y,z] = arr;
console.log(x,y,z);
//解构,就是将数组的值,一一赋值给对应的数组值,语法就是反过来,但对应的值必须是一个数组,如arr
//其中的[]括号里的,是一些变量,相当于将数组的值一一赋值给这些变量,而不用非常麻烦的一一操作了
//且x,y,z就是var的类型

//-----------------------------------------------------

var user = {
    username : "吕布",
    weapon:"方天画戟",
    horse:"赤兔马"
};

// 传统的js
let mingzi = user.username;
let wuqi = user.weapon;
let zuoji = user.horse;
console.log("姓名:"+mingzi+",武器:"+wuqi+",坐骑:"+zuoji);

//es6的解构
let {username,weapon,horse} = user;  // 注意:解构的变量名必须是对象中的属性,否则就是空定义
console.log("姓名:"+username+",武器:"+weapon+",坐骑:"+horse);

//解构,实际上就是用集合的方式的声明获得赋值
模板字符串 :
模板字符串相当于加强版的字符串
用反引号 `,除了作为普通字符串,还可以用来定义多行字符串
还可以在字符串中加入变量和表达式
定义多行字符串
/*
let str = `hello,
你俩在哪呢?
心情不好,出来喝点啊!`;
使用""要换行,必须使用换行符,直接换行会报错的,因为换行是占一行,而`会识别,必须要有另外一个`才会结束
实际上最终的结果还是返回字符串,只是中间有很多操作
console.log(str);
*/

//-----------------------------------

/*
let name = `吕布`;
let age = 24;

// 传统的拼接字符串
var info1 = "我叫:"+ name +",我今年"+age+"岁!";
console.log(info1);

// es6的拼接字符串
var info2 = `我叫:${name},我明年${age+1}岁!`;
console.log(info2);
*/
//其中使用`时将内容进行判断,${}取对应变量值,然后作为字符串,所以这个变量是可以进行计算的,因为只取最终结果
//所以这就是类似于vue的取找当前值进行操作
//-----------------------------------

function test(){
    return "吃喝玩乐";
}

let str = `悲催的人生,从${test()}开始`;
//那么既然是变量,那么方法自然也可以使用
console.log( str );

声明对象简写:
定义对象的时候,可以用变量名作为属性名
let name = `吕布`;
let age = 28;

//传统
let user1 = {
    name : name,
    age : age
};
console.log(user1);

//es6新语法中的简写
let user2 = {name,age}; //当key和value的名称一致时,可以直接省略写一个
console.log(user2);
定义方法简写 :
// 传统
let user1 = {
    say : function(){
        console.log("大家好!");
    }
};

user1.say();

//es6
let user2 = {
    say(){ //这样就看起来是你操作say()一样
        console.log("大家好啊!");
    }
};

user2.say(); 
对象拓展运算符 :
拓展运算符 {…} 将参数对象中所有可以遍历的属性拿出来,然后拷贝给新对象
拷贝对象(深拷贝) :
科幻电影中的一滴血或三滴血,就可以制作出一个完全一模一样的克隆人
let user1 = {
    name:"项羽",
    age:34
};

let user2 = {...user1}; 
// 深拷贝(克隆),需要三点血才可拷贝,只能拷贝相应对象
//若let user1 =1,则user2的值就是{},{}里面什么都没有

console.log(user1);
console.log(user2);
合并对象:
let user1 = {
    name:"项羽",
    age:34
};

let user2 = {head:"诸葛亮"};

let user = {...user1,...user2}; //获得对应的三滴血形成一个新的对象,逗号隔开

console.log( user );
函数的默认参数:
形参处已声明,但不传入实参会怎样?
function test(name , age = 18){ 
    //当age是undefined时,就会将18赋值给age,如不传参或传入undefined,那么age就等于18
    console.log(`我叫${name},我今年${age}`);
}

test("吕布",33); //我叫吕布,我今年33岁
test("貂蝉"); //我叫貂蝉,我今年18岁
test("关羽",null); //我叫关羽,我今年null岁
test("马超",""); //我叫马超,我今年岁
test("张飞",undefined); //我叫张飞,我今年18岁
test() //我叫undefined,我今年18岁
test(1) //我叫1,我今年18岁
test(1,2,3) //我叫1,我今年2岁,多余的参数若没有形参获取,相当于什么都没有做
test(1,2,) //我叫1,我今年2岁
test(1,) //我叫1,我今年18岁
//可以得出参数少的或者多的,都是从左边参数获得
//没获得的就是undefined,逗号只是用来分割,无其他作用,写或者不写,并不会造成影响
//但这个逗号在{}里面可以防止忘记写逗号而使得数据不对
//因为{}里面必须是一起的(如key的单词之间不可换行,不可有空格),所以逗号有非常的保护作用
函数的不定参数:
定义方法时,不确定有几个参数?
function test( ...arg ){ //使用三个点来定义一个不定长参数,实际上就是一个数组
    console.log(`传入了${arg.length}个参数`);
    for(var i = 0 ;i<arg.length;i++){
        console.log(arg[i]);
    }
}


test(1);
test(1,2);
test(1,2,3,4,5,6);
test();
test("郭","嘉",28);
箭头函数 :
箭头函数提供了一种更加简洁的函数书写方式,基本语法是:参数 => 函数体
// 传统
var f1 = function(a){
    return a*10;
}

console.log( f1(10) );

// es6
var f2 = a=>a*10; //有返回值的可以直接这样
console.log( f2(20) );

// 当箭头函数一个参数时,()可以省略
// 当箭头函数没有参数或者有多个参数,要用()括起来
// 当箭头函数的函数体有多行语句,用{}括起来,表示代码块
// 当只有一条语句,并且需要返回时,可以省略{},结果会自动返回

var f3 = (a,b) => {
    let sum = a+b;
    return sum;
}
console.log( f3(3,7) );

// 可以将f3进行简化
var f4 = (a,b) => a + b;
console.log( f3(11,22) );
Promise(了解):
英文意思:承诺
用来解决回调函数的嵌套噩梦 (后面学习ajax才能更好的理解)
我们回顾一下嵌套噩梦:

48-ECMAScript6详解_第4张图片

使用promise解决掐架找人的噩梦

在这里插入图片描述

找到一个人成功后,再继续找下一个人,逐渐形成了"队伍"
组建队伍的过程中,如果找某个人失败了,则"队伍"失败
其实有点类似"击鼓传花"的游戏,一个成功拿到花之后,才能传递给下一个人,依次类推!
setTimeout(()=>{
    console.log(1);
    setTimeout(()=>{
        console.log(2);
        setTimeout(()=>{
            console.log(3);
            setTimeout(()=>{
                console.log(4);
           },1000);
       },1000);
   },1000);
},1000);

next = n =>
  //Promise的构造函数接收一个参数,是函数,
//并且传入两个参数:resolve(异步操作执行成功后的回调函数),reject(异步操作执行失败后的回调函数)
  new Promise(function(resolve, reject) { //实际就是返回这个函数,相当于方法里的返回值
    setTimeout(function() {
      resolve(n); //传参数优先内部的,当然也可以自己传自己(对于大多数语言来说都可以)
        //因为也是一个变量
        //由上知,是方法的返回值,即可以获得这个参数,一般来说,需要这个方法执行完毕,他才会给res
   }, 1000);
 });
next(1) 
//每调用一次方法,那么Promise就创建一次,并会执行他里面的方法,而当resolve方法执行时
//那么res就会执行,很明显,必须要resolve执行才可,如果发生错误,自然的就不会执行
//即停止了,也就不会使得其他的程序一直占用,因为then是逐步操作的,没有在其他程序上进行
 .then(res => { // 成功
    console.log(res);
    return next(2); //在then方法中调用的next方法,一定要用return ,否则不会通过resolve把数据往下传递
 })
 .then(res => {
    console.log(res);
    return next(3);
 })
 .then(res => {
    console.log(res);
 })
 .catch(() => {  //处理失败:catch方法的第二个参数是失败的回调
    console.log("出错啦!");
 });

简单来说,promise就是可以操作一个兜底的数据,而不会容易一直操作下去,当然,这里也只是了解而已,还有很多用法的
模块化 :
如果在a.js文件中定义了5个方法,现在b.js文件中想使用a中的5个方法,怎么办?
java语言的做法是import引入之后,就能使用了,es6的模块化,就是这个过程
将一个js文件声明成一个模块导出之后,另一个js文件才能引入这个模块
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取,就如Class一样
传统的模块化(user.js):
function addUser(name){
    return `保存${name}成功!`;
}

function removeUser(id){
    return `删除${id}号用户!`;
}

// 声明模块并导出
// module.exports={ 导出时,导出对应的key名称,来使用
//     save:addUser,
//     delete:removeUser
// }

// 声明模块导出的简写
module.exports={  //可以直接导出对应方法
    addUser,
    removeUser
}
/*
var d =function fa(){
    console.log(1)
}
var a = {function (){
    console.log(1)
},d,a}
console.log(a)

单纯的直接写无名方法的话,那么就所以方法只有这一个,名称就是function,多余的无名都是这个,不会增加键值对
因为在键值对里,后面写的同样key,会覆盖前面的key,即覆盖前面同样的键
即{ function: [Function: function], d: [Function: fa], a: undefined }这些可能
前面是无名,有名的方法,但不可直接写有名的方法,实际上最后都是键值对
而一般的es6语法,最后都会趋向于键值对

var a = {function(){

},aa:1,bb:2}
console.log(a)
上面的无名

*/

let user = require("./user.js"); //引入user模块
//即获得导出的对应js,导入时,会查看是否有对应方法,即导出,获得对应导出的信息

console.log( user ); //{ addUser: [Function: addUser], removeUser: [Function: removeUser] }
//即他导出的就是一个{}对象,只不过该对象被赋值,那么自然不会自己执行
let result1 = user.addUser("吕布");
let result2 = user.removeUser(101);

console.log(result1);
console.log(result2);
ES6的模块化:
let name = "老孙";
let age = 66;
let fn = function(){
    return `我是${name}!我今年${age}岁了!`;
}
// 声明模块并导出
export{
    name,
    age,
    fn
}

import {name,age,fn} from "./user.js"
console.log(name);
console.log(age);
console.log(fn);
//默认全局变量
/*
var {name,age,fn} = {name,
    age,
    fn}; 
*/

/*
注意:无论是谁的导出,都默认名称和值相同的,前面说明的:
let user2 = {name,age}; //当key和value的名称一致时,可以直接省略写一个,但是他并没有赋值,实际上导出也算是一个赋值,而该赋值的变量只是操作导入和导出的中转站而已

记得也要满足: // 注意:解构的变量名必须是对象中的属性,否则就是空定义

*/
运行test.js,报错:SyntaxError: Unexpected token { (语法错误,在标记{的位置 )
原因是node.js并不支持es6的import语法,我们需要将es6转换降级为es5!
使用vue脚手架可能可以执行(可能自带转码器或者本身可以识别,甚至可以支持es6的该语法)
babel环境(兼容器):
babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有的环境中执行
这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持,并且如果出现相应的报错,可以拿取错误信息判断,并决定是否操作这个方式来进行处理,具体错误的捕获,可以查看百度
安装babel客户端环境:
创建新目录 lagou-babel,在终端中打开,运行命令:
//npm install --global babel-cli
查看版本:
//babel --version
如果报错:(win7系统中):
‘babel’ 不是内部或外部命令,也不是可运行的程序或批处理文件。(babel命令在当前系统中不被认可)
由于babel是通过npm安装,所以babel会默认安装到 E:\repo_npm(改变默认位置的)
在 E:\repo_npm 这个目录下,进入dos命令,执行 babel --version, 如果是成功的,说明babel已经安装成功
但是在vscode的终端中打不开,那么可能是两个原因:
环境变量没有配置
系统环境变量中 path中加入,E:\repo_npm;
在任意位置进入dos窗口,babel --version可以了,说明环境变量配置成功
vscode关掉,重新以"管理员身份运行 "
如果报错:(win10系统中):

在这里插入图片描述

windows10默认禁止运行有危险的脚本,修改一下系统策略就好了
开始菜单-> Windows PowerShell (切记要以管理员身份运行),输入代码
//set-ExecutionPolicy RemoteSigned

48-ECMAScript6详解_第5张图片

安装转码器:
在安装的模块所在的目录下,创建lagou-babel专属目录,在其中初始化项目
//npm init -y
创建babel配置文件 .babelrc ,并输入代码配置:
/*
{
 "presets": ["es2015"],
 "plugins": []
}
*/
安装转码器
//npm install --save-dev babel-preset-es2015
转码
创建dist目录,用来存放转码后的文件
/*
babel user.js --out-file .\dist\user.js
或
babel user.js -o .\dist\user.js
*/
运行转码后的文件
//node .\dist\test.js
//注意:转码的文件可能你并不明白,实际上并不需要了解,因为比较底层,一般转码只是操作es6到es5,反过来不会操作,而是创建一个对应文件,内容不变
ES6模块化的另一种写法:
as的用法
let name = "老孙";
let age = 66;
let fn = function(){
    return `我是${name}!我今年${age}岁了!`;
}

// 声明模块并导出
export{
    name as a, //起名称
    age as b,
    fn as c
}

import {a,b,c} from "./user.js" 
//a,b,c在转码时,若随便起名称的话,会造成两边变量key不对(转化的由自定义,变成对应了),所以最好相同
//若可以直接识别,可能可以随便起名称

console.log(a);
console.log(b);
console.log( c() );

记得转码
也可以接收整个模块:
import * as info from "./user.js" 
//通过*来批量接收,全部放入info里,是键值对形式,相当于info是一个对象,es6中,并没有存在以前版本的总体获取,所以只能这样,因为我是简化的而已

console.log(info.a);
console.log(info.b);
console.log( info.c() );

默认导出:
可以将所有需要导出的变量放入一个对象中,然后通过default export进行导出
export default{
    name:"换行",
    eat(){
        return "吃点啥"
    }

}
import p from "./person.js";
console.log(p.name,p.eat())

//前面我们知道es6没有总体获取,所以为了进一步满足,即出现了export default,来解决这个问题,实际上之前是module.exports也不是简便的,那么es6也不是

上面注意注意转码(除非可以直接识别)
重命名export和import:
如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题
为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做:
/*******************student1.js**************************/
export let name = "我是来自student1.js";
/*******************student2.js************************/
export let name = "我是来自student2.js";  //export可以导出后面定义的变量,具体怎么定义,看你操作
/*******************test_student.js************************/
import {name as name1} from './student1.js';
import {name as name2} from './student2.js'; //一般可以分开操作也行,逗号隔开即可
console.log( name1 ); // 我是来自student1.js
console.log( name2 ); // 我是来自student2.js

你可能感兴趣的:(笔记,html,ES6)