ECMAScript 6 基础
ECMAScript 6也叫ECMAScript 2015,它是2015年发布一个继ES5.1以后的一个版本,不建议直接在浏览器里使用,因为兼容性非常低
在学习ES6的过程当中,我们建议使用
nodejs
做为学习环境 ,因为nodeJS
就可以兼容ES6
什么是NodeJS
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 。
Node 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。 发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。
- NodeJS指包含ECMAScript,没有DOM与BOM(所以,就不会有window对象,也不会有document对象)
- 普通的JS代码是运行在浏览器上面的,而NodeJS代码可以像其它的后台编程语言一样运行在PC电脑上面,只需要在当前的电脑上面去安装开发环境(与其它后台语言如Java一样,Java也需要去安装jdk)
- node的全局对象不是window,在node下面,它的全局对象是
Node中如何运行JS代码
在浏览器里在,如果要运行某一个JS文件里面的代码,我们可以直接通过<script>
标签导入到网页里面就可以了,但是在node里面不行
$ node 文件名.js
$ node 1.js
$ node server.js
如果当前的JS文件与我们要执行的DOS命令的路径不一致,这个时候就要注意把路径加上去,如下所示
扩展知识:
.代表当前文件夹
..代表上级文件夹
我们可以通过cd
命令去切换文DOS命令所在的文件夹,cd
的全称是change directory
# 切换 到0924lianxi这个目录
$ cd 0924lianxi
# 切换 到上及目录
$ cd ..
dir
命令显示当前路径下面的所有文件夹与文件
在上面的图片里面,01.js是当前文件夹下面的文件,页0924lianxi则一个文件夹,因为它的前面有一个<DIR>
这个标记,有这个标记都是文件夹
变量的定义
在之前的ES5里面,我们去定义变量的时候,使用的是关键字var
来定义变量,var
有以下几个特点
-
没有块级作用域(只在定义在
function
里面的变量才有作用域) -
可以在定义之前调用
console.log(a); var a=123;
-
可以重复定义
var a=123; var a="hello";
上面的三个问题在ES6都解决了
在ES6里面定义变量多了两个关键字,一个是let ,一个const
通过let定义的变量
console.log(a); //报错
let a=123;
let定义的变量是先定义后调用(先声明后调用)
{
let a=123; //let定义的变量形成了作用域
}
console.log(a); //报错
通过let定义的变量,它有块级作用域,以花括号为块级作用域
在使用let去定义变量的时候,一定要注意下面这种情况
let a="yangbiao";
{
console.log(a);
let a;
}
这种情况,a会报错,提示a没有被定义
在使用let去定义的时候,无论使用是的全局变量还是局变量,都要在定义之后使用
let a="123";
console.log(a);
let a="hello world"; //语法报错,提示a已经被定义了
console.log(a);
通过const来定义
通过const 定义的数据与之前let定义的数据保持一个特性,同时,定义好的数据在这里是不允许更改栈内存值
const只锁了栈里面的数据,而并不能锁到堆里面去,所以在下面这种情况下面,要注意
const a=123;
console.log(a);
a=456; //报错 基本数据类型在栈里面,所以栈是不可更改数据的
但是下面这种情况是可以的
const person={
name:"杨标",
sex:"男"
}
person.sex="男神";
console.log(person); //不会报错
这个时候,person存在栈里面的地址并没有发生改变,上面的代码只是改变了堆里面的数据,所以并不会报错。但是下面这种情况就不行
const person={
name:"杨标",
sex:"男"
}
person={
name:"向继发",
sex:"男"
} //直接报错
console.log(person);
当person在进行第二赋值的时候,就会报错,因为这个时候改变了栈里面的地址值
在使用const定义的时候,切记不能这么定义
const a; //此种方案不可取
像上面这种定义方式是不可行的,它不报错,但是没有实际的值,后期也不能改变,但是我们可以这么使用
const a={}; //此种方案可取
最后一种特殊情况
const sayHelo=function(){
console.log("hello world");
}
//它会报错
sayHelo=function(){
console.log("你好");
}
sayHello();
方法的每次定义都是重新赋值,通过const定义的方法是绝对不能够被覆盖的
解构
数组解构
解构是变量的另一种取值与赋值方式,它针对于对象与数组
let a=1;
let b=2;
let c=3;
上面的这种写法可以直接用解构写成下面的情况
let [a,b,c]=[1,2,3];
如果参数的名称与参数的个数实现了一一对应,则正好相等,但是如果不对应则要注意
let [a,b,c]=[1,2]; //这个时候a=1,b=2,c=undefined;
//---------上面的定法其实就相当于下面的定法
let a=1;
let b=2;
let c;
let [a,b,[c,d]]=[1,2,["hello","world"]]; //这种情况也可以通过解构来解出来
对象的解构
对象的解构与数组的解构原理相同
let stu={
userName:"杨标",
sex:"男",
age:18
}
//如果我现在想取得用户名,怎么办
/*
let userName = stu.userName;
let sex=stu.sex;
let age=stu.age;
*/
let {age,userName}=stu; //相当于在对象里面拿出age属性值与userName的属性值
console.log(age,userName);
但是要注意,这里的变量名一定要与属性名相同,同时解构还可以像下面这种情况一下,进行二次解构
let stu={
userName:"杨标",
sex:"男",
age:18,
hobby:["看书","睡觉","玩游戏"]
}
let {hobby:[a,b]}=stu;
console.log(a,b); // a就是看书,b就是睡觉
刚刚上面的操作都是把对象里面的东西拿出来,现在我们试着去反向操作
let userName="张三";
let age=18;
let sex="男";
/*
let stu={
userName:userName,
sex:sex,
age:age
}
*/
let stu={userName,sex,age};
console.log(stu);
我们在创建对象的过程当中,如果发现属性名与属性变量值相同,这个时候,可以像上面这种方式一样,去直接解构赋值。如果你希望属性名与属性值在这里不相同,这个时候,我们可以按下面的方式 去操作
let userName="张三";
let age=18;
let sex="男";
/*
let stu={
userName:userName,
sex:sex,
age:age
}
*/
let stu={
stuName:userName, //直接更换属性名
sex,
age
};
console.log(stu)
解构只与赋值方式有关,与前面的关键字没有关系 ,用let也可以,用var也行
函数的扩展
ES6对方法的书写方法,this指向以及参数等都做了一些扩展
箭头函数
箭头函数有时候也叫lamdba
表达式,它是JS里面函数书写的另一种方式,现在我们来看一下箭头函数的书写更改
箭头函数的语法格式
没有参数的箭头函数
/*
setTimeout(function(){
console.log("hello world");
},2000);
*/
setTimeout(()=>{
console.log("hello world");
},2000);
在上面的代码里面,我们可以看到,后面的箭头函数去掉了关键字function
, 在括号与代码体之间使用=>
相连。上面的例子我们都没有使用参数。现在我们来看有参数的箭头函数
有参数的箭头函数
/*
setTimeout(function(a,b){
console.log(a,b);
},1000,"hello","world");
*/
setTimeout((a,b) => {
console.log(a,b);
}, 1000,"hello","world");
有参数的箭头函数与没有参数的箭头函数是一样的,把参数写在这前function
后面的扩号里面,如果是一个参数的时候,扩号可以省略
//这个箭头函数里面的参数只有一个,所以我们可以把扩号省略掉
setTimeout(userName => {
console.log(userName);
}, 2000,"杨标");
定义普通的箭头函数
我们之前在使用箭头函数的时候,都是在setTimeout
的回调函数里面(回调函数使用箭头函数是非常频繁),如果现在只是定义一个普通的方法,是否也可以使用箭头函数呢?
function sayHello(userName){
console.log("大家好,我叫:"+userName);
}
上面的普通函数能否通过箭头函数去定义呢?
const sayHelo=userName=>{
console.log("大家好,我叫"+userName)
}
sayHelo("杨标");
//下面是两个参数的定义
const sayHello2=(userName,sex)=>{
console.log("大家好,我叫"+userName+",我的性别是"+sex);
}
//下面是没有参数的定义
const sayHello3=()=>{
console.log("hello world");
}
如果方法体里面只有一行代码,这个时候,我们还可以省略掉花括号
const sayHello4 = () => console.log("hello world");
const sayHello2 = (userName,sex) => console.log("大家好,我叫"+userName+",我的性别是"+sex);
const sayHello = userName => console.log("大家好,我叫"+userName);
箭头函数与普通函数区别
箭头函数除了书写与定义方式与普通函数不一样以外,还有关键的一个点需要注意就是this指向性的问题。特别是对象里面的this指向与回调函数里面的this指向
/*
var obj={
userName:"杨标",
sayHello:function(){
//bind会生成一个新的方法
var newfn=function(){
console.log(this.userName)
}.bind(this);
setTimeout(newfn,1000)
}
}
*/
//官方说明:箭头函数会绑定this指向,把箭头函数外部的this绑定到箭头函数里面去
var obj={
userName:"杨标",
sayHello:function(){
setTimeout(()=>{
console.log(this.userName);
},1000)
}
}
obj.sayHello();
箭头函数会绑定this
的指向,它会将箭头函数外部的 this
绑定到箭头函数里边去(可以反向理解:跳过当前,取上级)。
我们在定义对象的时候,一定要注意,对象里面不允许定义箭头函数的属性。下面的情况,我们就拿不到当前对象的 this
var obj={
userName:"杨标",
//这里将属性方法sayHello定义成了箭头函数 ,这个时候sayHello的this拿不到当前对象
sayHello:()=>{
console.log(this.userName); //undefined
setTimeout(()=>{
console.log(this.userName); // undefined
},1000)
}
}
obj.sayHello();
函数参数的默认值
当我们在定义方法的时候,可能需要使用参数,在定义参数的时候可以给它一个默认值,这些在之ES5里面,我们需要在方法体里面处理一次,现在则不用了
ES5里面
const a=(userName,sex,age)=>{
//打印三个变量,
//如果age没有传值 ,则age默认的值为18
age=age||18;
console.log(userName,sex,age);
}
a("杨标","男",40);
a("杨标","男"); //这个时候没有传值,age就会使用方法体里面设置的默认值18
ES6里面
const b=(userName,sex,age=18)=>{
console.log(userName,sex,age);
}
b("张三","女"); //没有传入参数,这个时候age就会使用定义的时候设置的默认值18
b("张三","女",22); //这个时候传入了具体的值,所以它是age为22
如果有多个参数的默认值,也是可以的
const fn=(z,x=1,y=2)=>{
console.log(z,x,y);
}
fn(10); //10,1,2
fn(); //undefined,1,2
fn(20,30); //20,30,2
注意:默认值,我们要放在参数的最后
数组的扩展
ES在数组对象里面扩展了一些新的方法,这些新的方法,我们挑一些常用的来列举一下
-
Array.from()
我们在ES5里面,如果要将一个类数组转换成数组,则需要使用
Arary.prototype.slice.call()
这种方法来进行数组的转换,在ES6里面多了一个新的方法来进行转换,这就是Array.from()
,它可以将类数组转换成数组function abc(){ console.log(Array.isArray(arguments)); var arr=Array.from(arguments); console.log(Array.isArray(arr)); } abc("a","b","c","d");
-
Array.of()
该方法用于创建一个数组
在ES5里面,我们创建数组有两种情况
var arr=new Array(); var arr=[];
当我们在创建数组的时候,会出现直接赋值的情况
var arr=new Array(6); //这个时候创建了个长度为6的数组 var arr=new Array("a"); //这个时候创建了一个数组,里面的第一个元素是"a"ss
Array.of(...items:[])
这个方法返回的仍然是一个数组var arr = Array.of(6); //这是创建了一个数组,里面的元素是6 console.log(arr);
这个方法得到的返回值是一个数组,而它的参数仍然可以是类数且的展开,所以我们也可以通过这一种方式把它转换成数组
function abc(){ console.log(Array.isArray(arguments)); //false // var arr=Array.from(arguments); var arr=Array.of(...arguments); console.log(Array.isArray(arr)); //true } abc("a","b","c","d");
-
Array.ptototype.fill()
这个方法是将数组填充成指定的元素
var arr=new Array(10); arr.fill("h"); console.log(arr); //这会得到10个“h”的元素 arr.fill("h",2,5); //2代表开始索引 5代表结束索引的前一个 arr.fill("s",2,-1); //2代表开始索引 ,-1代表倒数第一个(不包含)
-
Array.prototype.find()
在数组里面查找指定条件的元素,找到以后就返回这个结果,如果找不到就返回
undefined
,如果有多个找到到第一个就停止了let result=cityList.find((item,index,a)=>{ return item.nm=="深圳"; }) console.log(result);
通过find去找的时候,不要试图去更改你更有在遍历的这个数组,不然会影响结果
-
Array.prototype.findIndex()
在数组里面去找根据条件去找索引,找到以后就返回索引 ,找不到就返回-1
let result = cityList.findIndex((item,index,a)=>{ return item.id==20; }) console.log(result);
Set单值集合
Set它是一个类数组,查是它是一个不重复的类数组,在里面,它不允许出现重复的元素(以前的时候,我们听说过键值对,但是Set只有值,没有键)
Set的初始化
首先,我们要学生会创建一个Set
let s=new Set(); //空的集合
let s=new Set(["a","b","c","d","e"]); //集合里面存放了5个元素
Set是一个不重复的单值集合,所以当我们向里面添加重复的元素的时候,它会自动过滤掉
let s1=new Set(["a","b","c","d","e","a","c"]);
这个时候得到的结果仍然是Set { 'a', 'b', 'c', 'd', 'e' }
,它将重复的元素直接过滤掉了,所以我们可以使用这一个特性直接数组去重
let arr = ["a", "b", "c", "d", "e", "a", "c"];
let s=new Set(arr);
var arr1=Array.from(s);
var arr2=Array.of(...s);
console.log(arr2);
这个时候,我们可以使用刚刚学过的转换数组的方法去将数Set转换成数组
Set对象的常用方法
- add()向集合里面添加元素,重复的元素添加不进去,它没有限定数据类型 ,它返回是当前添加进去以后的这个Set对象(它返回了自己,所以可以形成一个链式语法)
- delete()向集合里面删除元素,如果删除成功,则返回
true
,如果删除失败,则返回false
- has()判断当前Set集合里面有没有这个元素
- size属性,获取当前Set集合里面的元素个数
Set对象的遍历
首先我们使用for in
遍历了一次,这不能用为什么?,因为for...in
只能遍历有索引的集合(有索引的集合我们叫有序集合)
for(let i in arr){
//i代表提索引
}
Set是一个单值集合,它没有键(也就没有索引),所以不能使用for..in
去遍历,但是我们可以使用迭代器(Iterable)去遍历
let s=new Set();
s.add("a");
s.add("b");
s.add("c");
s.add(123).add(456);
for(var item of s){
console.log(item);
}
Map键值对集合
Map与Set的数据类相似,它们都是一个集合, 但是map是一个键值对集合,它可以在添加的元素的过程当中,实现键与值的对象
Map的初始化
let m=new Map(); //创建了一个空的map集合
上面的代码是真初始化了一个空的map集合,我们也可以向构造函数Map里面传递参数,直接初始化
let obj={
userName:"杨标",
sex:"男"
}
var entry = Object.entries(obj);
let m=new Map(entry);
除了使用上面的方式来进行初始化以外,还可以使用下面的方式
let m=new Map([["userName","杨标"],["sex","男"],["age",18]]);
通过对map的初始化,我们可以知道它与Set的区别在于,它在添加元素的时候,有两个参数,一个是键,一个是值。map与对象非常相似,对象也叫键值对,map也是键值对,它们都是一个key对应一个value
Map对象的常用方法
- set()方法,向map集合里面添加键值对,但是要注意,键不允许重复,如果重复则添加不进去,会把前面的值把它替换掉。添加完成以后返回的就是这个map,所以它可以实现链式语法
- delete()方法,通过一个key向map集合里面删除一个键值对,删除成功返回true,删除失败返回false
- has()方法,判断map集合里有没有这一个键
- size属性,判断map里面键值对长度
Map对象的遍历
map是一个键值对集合,所以通过情况下会有三种需求的遍历
- 遍历所有的key
- 遍历所有的value
- 遍历所有的key与value
let m=new Map();
m.set("userName","杨标");
m.set("sex","男").set("age",18);
//---------
let keyIterator = m.keys(); //返回一个key的迭代器
while((abc=keyIterator.next()).done==false){
console.log(abc.value);
}
//-----------
let valueIterator=m.values();
while((abc=valueIterator.next()).done==false){
console.log(abc.value);
}
上面的遍历方式是针对key与value的两种不同方式,但是我们仍然可以使用我们后面学过的迭代器遍历方法for...of
来进行
/* 第一步
for(let item of m){
// console.log(item);
console.log(item[0],item[1]);
}*/
/* 第二步 演变
for(let item of m){
let [key,value]=item;
console.log(key,value);
}
*/
//第三步 演变
for(let [key,value] of m){
console.log(key,value);
}
对象的扩展
之前我们已经对于对象的解构进行了扩展,现在我们来对对象的其它地方做一次扩展
对于对象方法的扩展
在以前创建对象里面的属性方法的时候,我们可以通过 function
来进行
let a={
sayHello:function(){
console.log("你好");
}
}
//在ES6时面,创建属性方法就更加简便了
let b={
//通过这种方式定义的,就一定是一个属性方法,不能用构造方法的形式调用
sayHello(){
console.log("你好啊,这是新方法");
}
}
注意: 在第二种方式里面,这一种方式创建的属性方法它没有构造器(不能当成构造函数使用)
new a.sayHello(); //这是没有问题的,把 sayHello当成了构造函数使用
new b.sayHello(); //这个就会报错,提示 b.sayHello is not a constructor,不是一个构造函数
对访问器属性的扩展
在之前的ES5里面,我们可以通过Object.defineProperty
来定义对象的访问器属性
let stu={
firstName:"杨",
lastName:"标"
}
//访问器属性可以在get与set的方法里面实现联动效果
Object.defineProperty(stu,"userName",{
//定义了get的取值方法
get:function(){
return this.firstName+this.lastName;
},
//定义了set的赋值方法
set:function(newValue){
this.firstName=newValue[0];
this.lastName=newValue.substr(1);
}
});
console.log(stu.userName); //这里是取值,会自动调用get方法
stu.userName="张三丰"; //赋值,会自动调用set方法
console.log(stu.firstName,stu.lastName);
在ES6里面,对访问器属性又重新做了一次规划
首先,我们可以模拟其它的编程语言一样(如Java),去写一个get
与set
的方法
let stu={
firstName:"杨",
lastName:"标",
//这个方法,我们把它当成取值的方法
getUserName(){
return this.firstName+this.lastName;
},
//下面这个方法,我们把它当成赋值方法
setUserName(newValue){
this.firstName=newValue[0];
this.lastName=newValue.substr(1);
}
}
console.log(stu.getUserName());
stu.setUserName("张三丰");
console.log(stu.firstName,stu.lastName);
但是这个时候要注意,它们的getUserName()
与setUserName()
它们都是方法,不是属性。这个时候ES6是这么做的
let stu={
firstName:"杨",
lastName:"标",
//这个方法,我们把它当成取值的方法
get userName(){
console.log("这是取值");
return this.firstName+this.lastName;
},
//下面这个方法,我们把它当成赋值方法
set userName(newValue){
this.firstName=newValue[0];
this.lastName=newValue.substr(1);
}
}
console.log(stu.userName);
stu.userName="张三丰";
console.log(stu.firstName,stu.lastName);
ES6的访问器属性本质上面还是一个方法,它在取值操作的方法前面添加了一个get
关键字,在赋值操作的方法前面添加了一个set
关键字。在使用访问器属性的时候,就不要把它当成方法了,当成一个普通的属性使用就可以了
ES6对象新特性
对象的创建
在ES5里面,我们可以使用下面三种方式创建对象
var obj=new Object()
var obj={}
- new一个构造函数
在ES6里面多了几种创建对象的方法,其中最主要的是通过关键字class
来进行创建
在ES5里面,如果我们要使用构造函数去创建对象,那么,我们必须要先定义一个构造函数
function Person(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
this.sayHello=function(){
console.log(this.name,this.sex,this.age);
}
}
let p=new Person("杨标","男",18);
console.log(p);
在上面的方法里面,我们创建了一个构造函数,现Person就是这个构造函数
在ES6里面,我们则可以使用另一种方式创建
class Person{
//class 创建的构造函数里面,一定有一个方法constructor 你如果不写,它默认给你生成一个
//如果不向里面传值 ,则可以不写
constructor(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
}
sayHello(){
console.log("hello world");
}
}
let p=new Person("杨标","男",18); //new的是Person,但是执行是constructor()构造方法
console.log(p);
我们通过class
关键字来创建了一个构造函数,要注意,这个class
创建的这个Person
它通过typeof
去检测的时候得到的是一个function
,但是这个方法只能通过关键字new
去调用
在class
创建构造函数Person里面,默认会有一个方法constructor()
这个方法,你可以自己写,但是你也不写(如果不写,系统会自动给你生成。当我们不需要向对象里面传递参数的时候,我们就可以省略掉)
constructor()
方法会在new的一瞬间自已调用,不需要用户手动去调用
对象的继承
在之前的ES5里面,我们可以通过prototype
或__proto__
去实现对象的继承关系 ,但是它们各有优缺点
通过prototype实现
/**
* 在以前的ES5里面
*/
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
this.sayHello = function () {
console.log(this.name, this.sex, this.age);
}
}
function Student(){
this.study=function(){
console.log("学生在学习")
}
}
//如果要让这个学生继承自人
Student.prototype=new Person(); //这是通过prototype来继承
let s=new Student(); //得到一个s对象
console.log(s instanceof Student); //true
console.log(s instanceof Person); //true
这种方式,可以实现继承,但是有个非常大的缺点,就是它不能够向父级对象传递参数,同时它也有一个最大优点就是保留了原型链的特点,我们通过 instanceof去检测的时候,无论是检测父级还是检测子级,结果都为
true
通过__proto__实现继承
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
this.sayHello = function () {
console.log(this.name, this.sex, this.age);
}
}
function Student(name,sex,age){
this.__proto__=new Person(name,sex,age);
this.study=function(){
console.log("学生在学习")
}
}
let s=new Student("杨标","男",19);
//这种方式解决了我们的参数不能传递的问题 但是又出现了一个新的问题
console.log(s instanceof Student); //false
console.log(s instanceof Person); //true
通过这种方式,我们可以实现继承的时候传递参数,但是我们的原型又被破坏了,当我们通过
instanceof
去检测的时候,我们发现这个时候检测父级为true,而检测当前对象又为false
ES5里面这两种情况都没有很好的去解决对象继承的问题。但是在ES6里面,则已经通过 class
关键字与另一个继承关键字extends
实现了
在ES6里面,如果要实现继承,需要使用新的关键字extends
/**人的对象的构造函数 */
class Person{
constructor(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
}
sayHello(){
console.log(this.name,this.sex,this.age);
}
}
/**学生对象Student通过extends继承了Person */
class Student extends Person{
constructor(name,sex,age,sid){
super(name,sex,age);
this.sid=sid;
}
study(){
console.log("我在学习");
}
}
let s=new Student("杨标","男",18,"20190101");
console.log(s);
console.log(s instanceof Student);
console.log(s instanceof Person);
注意事项:在使用 extends
关键字实现继承的时候,我们要注意里面有这些注意点
- 在子类可以不写
constructor()
构造函数,系统会默认帮你创建 - 但是如果在子类写了
constructor()
以后,一定要先调用super()
,否则会报错 - 在构造函数里面,
this
关键字必须在super
之后
通过这一种方式实现的继承,它可以解决之前ES5里面存在的问题(要么不能传值,要么破坏原型链);
子级对象方法重写
当父级对象的方法无法再满足子级对象的时候,我们可以在子级对象里面重写这个方法,只要方法名相同的就行了。因为在JS里面,调用的时候都是先看自己有没有,如果自己有就不再去父级去找了
/**人的对象的构造函数 */
class Person{
constructor(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
}
sayHello(){
console.log(this.name,this.sex,this.age);
}
}
/**学生对象Student通过extends继承了Person */
class Student extends Person{
constructor(name,sex,age,sid){
super(name,sex,age);
this.sid=sid;
}
study(){
console.log("我在学习");
}
sayHello(){
console.log("我是子及元素");
super.sayHello(); //在这里又通过关键字super去调用了父级元素的这个方法
}
}
let s=new Student("张三","男",22);
//当子级元素有的时候,不找父级元素要
s.sayHello(); //这个时候调用的就是子级元素的方法
静态方法
ES6可以像其它的编程语言一样,实现静态方法了,只需要借助关键字static
class Person{
constructor(){
this.userName="张三";
}
sayHello(){
console.log("我在向你打招呼",this.userName);
}
static sleep(){
console.log("我在睡觉",this.userName);
}
}
class Student extends Person{
static sleep(){
console.log("学生在睡觉");
}
}
Person.sleep();
Student.sleep();
说明:
- 静态就去不需要new就可以通过class后面的构造函数名直接调用,如
Person.sleep()
- 静态方法可以实现继承
- 静态方法可以实现重写
在其它的编程语言里面,对象里面的静态方法是不能够被继承的,也不能被重写
String字符串的扩展
在之前,我们在进行字符串对象的学习的时候,我们已经学过了字符串的相应的方法,其中有些方法它本来就是ES6里面的方法,只是在浏览器里面它的兼容性非常好,所以我们直接使用
- padStart()
- padEnd();
- includes();
- startsWith();
- endsWith();
模板字符串
在之前学习字符的时候我们使用单引号或双引号来进行定符的定义
let str="hello world";
let str1='hello world';
上面的两种定义方式都是普通的字符串,在ES6里面多了一个新的字符串字义方式,使用反引号来完成
let str2=`hello world`;
通过这一种方式定义的字符串我们叫模板字符串,在模板字符串里面,我们可以很方便的去拼接我们的变量
let userName="杨标",sex="男",age=18;
如果按照普通的ES5的方式去拼接会很麻烦
let str="大家好,我的姓名是:"+userName+",我的性别是:"+sex+",我的年龄是:"+age;
let str="大家好,我的姓名是:".concat(userName).concat(",我的性别是:").concat(sex).concat(",我的年龄是:").concat(age);
但是在ES6里面,则很简单
let str=`大家好,我的姓名是:${userName},我的性别是:${sex},我的年龄是:${age}"`;
如果我们要在模板字符串里面去拼接变量,这个时候我们只需要在拼接的地方给一个 ${变量名}
就可以了
同时模板字符串什么叫模板字符串,是因为保留了模板的功能
let str=`大家好!
我的姓名是${userName},
我的性别是${sex},
我的年龄是${age}!
${new Date().toLocaleString()}
杨标`;
console.log(str);
如果我们将字符串通过上面的方式去书定,这个时候打印的str也是按照这样的格式输出打印的,它也会换行,也面也会有空格
正则表达式
在JS里面,有一个对象比较特殊,它即不通过object创建,也不通过花括号创建,它直接就可以定义出来,在ES5与ES6当中都有一个构造函数RegExp
,它是正则表达式的构造函数
let reg=new RegExp();
//typeof reg; 得到object 它是一个对象
它仍然有另一种快速的构建方式
let reg=/ /;
正则表达式到底是干什么用的呢?
正则表达式是对数据进行快速验证的一种方式,它通过一些特殊的组合来对数据进行验证
一、元字符
元字符 | 对应说明 |
---|---|
. | 匹配除换行符之外的任意字符 |
\w | 匹配字母数字下划线,等同于:[a-zA-Z0-9_] |
\s | 匹配任意空白符 |
\d | 匹配数字,等同于[0-9] |
\b | 匹配单词边界 |
| | 或匹配,如 /x|y/ 正则可匹配x或y两个字符 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
二、反义字符
反义字符 | 对应说明 |
---|---|
[^x] | 匹配除“x”之外的所有字符,其中“x”可以为任意字符 |
[^xyz] | 同上,匹配除“x、y、z”之外的任意字符 |
\W | 匹配除了字母、数字、下划线之外的所有字符,等同于:[^\w] |
\S | 匹配除空白符之外的任意字符,等同于:[^\s] |
\B | 匹配不是单词边界的字符,等同于:[^\b] |
\D | 匹配不是数字的所有字符,等同于:[^\d] |
三、转义字符
转义字符 | 对应说明 |
---|---|
\xnn | 匹配十六进制数 |
\f | 匹配换页符,等同于:\x0c |
\n | 匹配换行符,等同于:\x0a |
\r | 匹配回车符,等同于:\x0d |
\t | 匹配水平制表符,等同于:\x09 |
\v | 匹配垂直制表符,等同于:\x0b |
\unnnn | 匹配Unicode字符,如:\u00A0 |
四、重复匹配
匹配字符 | 对应说明 |
---|---|
* | 重复出现零次或多次 |
+ | 重复出现一次或多次 |
? | 重复出现零次或一次 |
重复出现n次 | |
{n,} | 至少重复出现n次 |
{m,n} | 重复重现m到n次,其中,m<n |
五、修饰符
修饰符 | 对应说明 |
---|---|
i | ignoreCase的缩写,表示忽略字母的大小写 |
m | multiline的缩写,更改^和$的含义,使它们分别在任意一行的行首和行尾匹配, 而不仅仅在整个字符串的开头和结尾匹配。 (在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
g | global的缩写,进行全局匹配,即对字符串进行全文匹配,直到字符串遍历结束 |
- test()方法,用于测试某一个字符串是否满足正则表达式条件,如果满足就返回true,否则就返回false
- exec()方法,用于提取字符串中满足的条件的字符串,要注意,它是一次提取一个,我们要通过while循环去提取,直到提取不到为止
正则表达式提取歌词
var str=`[00:00.50]不谓侠(Cover 萧忆情Alex) - Critty
[00:01.50]词:迟意
[00:02.50]曲:潮汐-tide
[00:03.50]编曲:潮汐-tide
[00:30.80]衣襟上
[00:31.71]别好了晚霞
[00:34.16]余晖送我牵匹老马
[00:38.14]正路过
[00:39.25]烟村里人家
[00:41.40]恰似当年故里正飞花
[00:44.99]醉过风
[00:46.68]渴过茶
[00:48.59]寻常巷口寻个酒家
[00:52.44]在座皆算老友
[00:56.10]碗底便是天涯
`
//编写时间的正则表达式
var reg=/\d{2}:\d{2}.\d{2}/g;
//获取时间以后的数组
var timesArray=[];
//使用正则表达式方法exec去提取满足条件的字符串
while((item=reg.exec(str))){
timesArray.push(item[0]);
}
//使用字符串方法replace来替换,获取时间以后的文字
var lrcArray = str.replace(reg,"").replace(/\[\]/g,",").split(",");
lrcArray.shift();
在使用正则表达式的时候,我们还有一个方法是字符串对象里面的方法,它也可以使用正则表达式,那就是replace
,同时字符串里面还有一个方法match()也是使用正则表达式做为参数,用于检测匹配正则表达式的字符串,然后提取出来,放在一个数组里面,这样刚刚上面的代码我们就使用下面的方式进行简化
/*
var timesArray=[];
while((item=reg.exec(str))){
timesArray.push(item[0]);
}
*/
这是原来的方式 ,使用正则对象的exec()
方法去提取,但是它一次只能提取一个,所以我们要使用 while
去循环
这个时候,当我们使用字符串对象里面的match()
方法的时候,我们就可以直接一次性获取了
var timesArray=str.match(reg);
Symbol数据类型
在JS里面已经有5种基本数据类型 ,分别是Number
, String
,Boolean
,Null
,Undefined
这五种,在ES6里面,添加了一种新的基本数据类型叫Symbol
,理解为独一无二的
Symbol理解为独一无二,通过调用Symbol()
的方法,返回一个变量,这个变量变是Symbol的数据类型
//0x0001
let s1=Symbol(); //得到一个Symbol数据类型
//0x0002
let s2=Symbol(); //又一个
//每次调用Symbol()方法所得到的数据值都不一样
//可以理解为Symbol()返回的是一个内存地址,每一个内存地址都不一样
console.log(s1==s2);
为什么需要一个这样的数据类型?
它是一个独一无二的东西,可以使用它做为对象的属性名。在ES5里面,所有的对象的属性名都是string
类型 ,既然是字符串类型所以就有可能会被出现重复的情况被覆盖。但是有了symbol这个东西以后就不会了
在ES5里面
let obj1={
userName:"杨标",
sex:"男"
}
let obj2={
userName:"张三丰",
age:18
}
//obj1是目标对象。obj2是源对象
Object.assign(obj1,obj2);
console.log(obj1);
在上面的代码里面,我们可以看到obj1与obj2在这里它们都有一个共同的属性名叫userName
,当我们通过Object.assign()
去进行浅拷贝 ,把obj2拷贝到obj1里面去的时候,这个时候就发现obj1的属性userName
被覆盖了
在ES6里面
let s1 = Symbol(); //独一无二的,不可能重复
let obj1 = {
age: 18,
[s1]: "杨标" //symbol做为属性名的时候,要使用[]括起来
};
{
let s1=Symbol(); //独一无二的,不可能重复的
let obj2 = {
sex: "男",
[s1]:"张三丰"
}
Object.assign(obj1,obj2);
console.log(obj1);
}
我们把不希望被覆盖的属性使用symbol
来表示,Symbol做为属性名的时候,要使用[]
包含起来
在上面的图片面,我们可以看到里面有两个Symbol
,但是这两个Symbol属性不好区别,所以我们在创建Symbol的数据类型的时候,可以给它一个描述 ,这个描述相当于一个注释 ,不带任何实际意义
let s1 = Symbol("第一次"); //独一无二的,不可能重复
let obj1 = {
age: 18,
[s1]: "杨标" //symbol做为属性名的时候,要使用[]括起来
};
{
// symbol里面的参数也没有实际意思,它做一个描述信息,相当于一段注释
let s1=Symbol("第二次"); //独一无二的,不可能重复的
let obj2 = {
sex: "男",
[s1]:"张三丰"
}
Object.assign(obj1,obj2);
console.log(obj1);
}
Symbol属性不参于for...in的遍历。如果要获取这个属性名,需要使用特殊的方法Object.getOwnPropertySymbols(obj1)
let sarr = Object.getOwnPropertySymbols(obj1);
console.log(sarr);
获取到的sarr
的结果为[ Symbol(第一次), Symbol(第二次) ]
Symbol的属性不能使用.
来进行调用,要使用[]
来进行调用,但是在调用时候也要注意,不能够直接调用。例如下面的方式就是错的
console.log(obj1[Symbol("第一次")]); //undefined
这个时候会返回结果undefined
, 因为Symbol每次返回的都是一个不同的值,所以在调用Symbol的属性的时候需要先通过之前的Object.getOwnPropertySymbols(obj1)
获取到symbol的属性名集合,然后再去通过这个属性名去调用
let sarr = Object.getOwnPropertySymbols(obj1);
console.log(sarr); //Symbol属性名的数组
console.log(obj1[sarr[0]]); //调取对象里面的Symbol属性
在之前学习的数据类型里面,Number里面等基本数据类型里面都有一些固定的值,例如Number.MAX_VALUE
或Number.MIN_VALUE
。同样Symbol这个类型,也有几个特殊的固定值,最重要的一个就是Symbol.iterator
具体的使用请参看迭代器章节
生成器函数Generator
在之前的ES5里面,只要我们定义好方法以后,都是调用执行,调用以后都是立即执行(除延时调用与循环调用),但是生成器函数不一样
function abc(){
console.log("hello");
return "world";
}
var str = abc(); //当我们调用abc()这个方法的时候,这个方法立即执行,并且返回了一个值给str
console.log(str);
下面请看ES6里面的生成器函数
function * abc(){
console.log("hello");
return "world";
}
生成器函数与普通函数不同,它在function
关键字与方法名之间加了一个*
生成器函数调用执行以后,这个方法没有立即执行,而是生成了一个方法执行状态,我们可以接收这个状态是暂停的,这个方法内部的代码还没有立即执行
let result=abc(); //这个result就是这个方法的执行状态
在这里,我们可以调用result.next()
的方法让它继续执行代码
function * abc(){
// console.log("方法开始");
//和return 一模一样
//当生成器的代码碰到了 yield以后,就会返回这个值,但是方法没有停止,,而是暂停
//你下次再调用next()的方法的时候,代码就从这个暂停的地方开始
yield "我";
yield "爱";
yield "北";
yield "京";
yield "的";
yield "雪";
}
var result=abc();
// while((item=result.next()).done==false){
// console.log(item.value)
// }
for(let item of result){
console.log(item);
}
当生成器里面的函数碰到了yield以后,它就会暂停,同时,会把这个值先返回给你,如果你再次调用next()
方法,则这个执行result会从上次暂停的地方继续执行,直到碰到下一个yield
yield关键字只能在生成器里面使用
迭代器(Iterable、Iterator)
Iterable:能力,可迭代的能力
Iterator:迭代器,它是一个接口
迭代器它是一个接口(当一个对象只要是实现了这个接口(Iterator),就具备接口的能力(Iterable)),迭代器它是一个能力,一个对象的内部如果有了迭代器这个东西,那么它就具备迭代的能力
迭代的能力就是可以从对象的内部一个一个取值的能力( 底层使用的还是生成器函数),在JS里面,有以下几种数据类型是自带迭代能力
Array
,Set
,Map
,NodeList
,arguments
,TypedArray
,String
只要是具备迭代能力的,就一定可以遍历,也可以使用展开运算符
迭代器是一个一个的取值,它通过next()
来获取结果,这个结果是一个对象,里在有一个value
属性与一个done属性,当这个done
的属性为true的时候,则代表没有取到值了,当一个元素要进行迭代器的遍历的时候,我们应该使用如下的方法来进行遍历
let s=new Set();
s.add("a");
s.add("b");
s.add("c");
s.add(123).add(456);
let result = s.values(); //它返回的类型是一个Iterable
let item=null; //item用于存储得到的值
while((item=result.next()).done==false){
console.log(item.value);
}
所有具备迭代器接口的集合都在ES6里面提供了一种新的遍历方法for...of
,这一种遍历方法针对的不是索引,所以这一种遍历方法拿不到索引
let arr=["a","b","c","d"];
for(var item of arr){
//item代表当前遍历的每一项
console.log(item);
}
Set使用 for ... of遍历
let s=new Set();
s.add("a");
s.add("b");
s.add("c");
s.add(123).add(456);
for(var item of s){
console.log(item);
}
自定义对象实现Iterable
之前在学习生成器函数的时候,我们已经知道 ,生成的yield就是一次一次的返回值,并且调用next()
的方法继续执行,也可以使用for...of
遍历,如果一个对象要实现Iterable迭代能力,怎么办呢
在Symbol
的数据类型里面,有一个值叫 Symbol.iterator
, 这个值就是就是迭代专有属性,只有一个对象有了这个属必到后就可以进行迭代了for...of
了
let obj = {
userName: "杨标",
sex: "男",
age: 18,
// 迭代是一个一个的取值
[Symbol.iterator]: function* () {
let i = 0;
let keys = Object.keys(this);
while (keys[i] !== undefined) {
yield this[keys[i]];
i++;
}
}
}
/*
for(let item of obj){
console.log(item);
}
*/
console.log(...obj);
在上面的代码里面,我们在obj
对象里面实现了一个接口 [Symbol.iterator]
,实现这个接口以后,当前对象obj
就具备了迭代 Iterable
的能力,如果我们希望把所有的对象都具备迭代能力,怎么处理
Object.prototype[Symbol.iterator]=function*(){
let i = 0;
let keys = Object.keys(this);
while (keys[i] !== undefined) {
yield this[keys[i]];
i++;
}
}
反射Reflect
反射是JS里面操作对象的另一种方式,它使用Reflect
来进行,具体请看下面代码
let obj={
userName:"标哥"
}
-
defineProperty(target,property,descriptor)方法,定义某一个属性
target代表要操作的目标对象,property要操作的属性,descriptor属性的描述信息
//相当于在obj的对象上面定义了一个属性age Reflect.defineProperty(obj,"age",{ value:18, writable:true, configurable:true, enumerable:true //这四个特性是之前我们es5里面所讲的4个特性 }); console.log(obj);
-
deleteProperty(target,property)方法,删除某一个属性
Reflect.deleteProperty(obj,"userName"); console.log(obj);
-
get(target,propertyKey,receiver),获取某一个对象的某一个属性值
target代表要操作的对象,properyKey代表要操作的属性名,receiver如果是一个访问器属性,这个参数代表访问器属性里面的this
let obj={ userName:"杨标", //访问器属性 get myName(){ return this.userName; } } let obj1={ userName:"张三丰" } var str = Reflect.get(obj,"userName"); console.log(str); //杨标 var str1=Reflect.get(obj,"myName"); console.log(str1); //杨标 //receiver 第三个参数代表访问器属性里面的this var str2=Reflect.get(obj,"myName",obj1); console.log(str2); //张三丰
-
set(target,properyKey,value,receiver)设置某一个属性值
参数说明请参考get的情况
let obj={ userName:"杨标", //它是一个特殊的属性,叫访问器属性 get myName(){ return this.userName; }, set myName(newValue){ this.userName=newValue; } } let obj1={ userName:"张三丰" } //这个地方操作的是obj Reflect.set(obj,"userName","张无忌"); console.log(obj); //这个地方操作的是obj1 Reflect.set(obj,"userName","张无忌",obj1); console.log(obj1);
-
has()判断当前对象是否有这个属性,无论是当前的还是父级的都可以,不可遍历(enumerable:false)属性也可以
class Person{ constructor(){ this.userName="杨标" } } class Student extends Person{ constructor(){ super(); this.sid="H19040001" } } let s=new Student(); Reflect.defineProperty(s,"age",{ value:18, enumerable:false, configurable:false, writable:false }) //获取当前属性 let flag1 = Reflect.has(s,"sid"); console.log(flag1); //true //获取父级属性 let flag2=Reflect.has(s,"userName"); console.log(flag2); //true //获取不可遍历属性 let flag3 = Reflect.has(s,"age"); console.log(flag3); //true
-
getProrotypeOf()获取当前构造方法的prototype
class Person{ constructor(){ this.userName="杨标" } } class Student extends Person{ constructor(){ super(); this.sid="H19040001" } } let p = Reflect.getPrototypeOf(Student); console.log(p); //结果为Person的构造方法
-
setPrototypeOf()给某一个对象设置一个父级对象(原型对象)
class Person{ constructor(){ this.userName="杨标" } } class Student{ constructor(){ this.sid="H19040001" } } let p=new Person(); let s=new Student(); //给S指定一个父级p Reflect.setPrototypeOf(s,p); console.log(s.userName); //杨标
-
apply(target:function,thisArg,arguments)方法
let obj={ userName:"张三", sayHello(){ console.log(this.userName); }, getUserName(){ return this.userName; } } let stu={ userName:"李四" } obj.sayHello.apply(stu,[]); Reflect.apply(obj.sayHello,stu,[]); //当这个方法有返回值的时候,Reflect也有返回值 let str = Reflect.apply(obj.getUserName,obj,[]); console.log(str);
apply方法会改变方法内部的this指向,所以在使用Reflect.apply的时候,我们也可以改变里面this的指向
-
constructor()执行某一个构造方法
function Student(){ console.log("我执行了"); this.name="张三"; } class Person{ constructor(){ this.age=18; } } let obj = Reflect.construct(Student,[]); console.log(obj); let obj2=Reflect.construct(Person,[]); console.log(obj2);
constructor把方法当成构造方法去执行,返回创建的对象,
[]
里面放的是参数 -
ownKeys(),该方法与ES5里面的
Object.getOwnPropertyNames()
相同这个方法可以拿到
enumerable:false
的属性,但是Object.keys()
这个方法是获取不到的let obj={ userName:"杨标", sex:"男" } Object.defineProperty(obj,"age",{ value:18, enumerable:false }) //Object.getOwnPropertyNames()可以获取刚才enumerable:false的 let arr1=Object.keys(obj); console.log(arr1); //[ 'userName', 'sex' ] let arr = Object.getOwnPropertyNames(obj); console.log(arr); //[ 'userName', 'sex', 'age' ] let arr2 = Reflect.ownKeys(obj); console.log(arr2); //[ 'userName', 'sex', 'age' ]
? 思考:
Reflect.has()
,Object.hasOwnProperty()
,Reflect.ownKeys()
,Object.getOwnPropertyNames()
,Object.keys()
,in
它们对属性的检测分别是什么结果,请从不可遍历属性enumerable:false
, 子级与父级的属性这些情况去判断
? 对比结果:
- Object.keys()只能获取自己的,并且是
enumerable:true
的 for... in
是检测这个属性在不在这个对象,同时也会去父级检测 ,但是不能检测enumerable:false
代理Proxy
将之前的一个对像的操作进行代理下来,不直接操作原来的对象,转进行操作代理对象,可以理解为把之前的ES5里面的对象的get
与set
做了一次操作
let obj={
userName:"张成",
answer(){
console.log("我没杀人")
}
}; //这是原始对象
// 律师
let lawyer=new Proxy(obj,{
// 代理配置信息
//get对属性取值
get(target,properyName,receiver){
// 在取值之前,可以进行一系列的操作
//常规的代理是需要极反射结合在一起的
if(properyName=="userName"){
return "在我律师来之前,我保持沉默";
}
return Reflect.get(target,properyName,receiver);
},
//set对属性赋值
set(target,properyName,value,receiver){
//在Set的赋值之前,我们可以进行一系列的操作
Reflect.set(target,properyName,value,receiver);
}
});
console.log(lawyer.userName);
传统的ES5里面,只有get与set的操作能够被拦截,但是在ES6的代理里面,我们可以有很多操作都能实现
模块化
在很久很久以前,JS也在模块化,它有两个规范(AMD/CMD规范),后面慢慢被另一种方式取代了,叫
CommonJS
规范,而在ES6里面又出了一个模块化的规范叫ESModule(这个规范在Nodejs不支持,需要借用第三方平台 如webpack或glup等)
NodeJS是没有DOM的,所以它就不会有<script>
标签,没有这个标签JS文件之间怎么引用的呢?
在NodeJS里面提供一种特殊的文件引入机制(CommonJS)以实现模块化,它使用关键字module.exports
或 exports
来完成
ES6本身是支持模块化的(ES Module),但是在NodeJS里面不支持
模块导入
CommonJS使用 require()
有一份去导入一份JS文件时候,相当于把这个JS文件拿到了当前的JS文件里面去
Person.js文件
console.log("这是Person.js文件");
Student.js文件
require("./Person.js");
//相当于把Person.js文件拿到了Student.js 里面来
当我们去执行Student.js
里面的代码的时候,Person.js
使用require()
方法去导入的时候,它会形成一个缓存,如果多次导入,它不会再去执行,所以当我们把Student.js的代码换成如下的时候
require("./Person.js");
require("./Person.js");
require("./Person.js");
require("./Person.js");
require("./Person.js");
我们去执行Student.js的时候,仍然只发现它控制台输出了一句话。这是因为第一次require()
的时候它是真正的导入 ,后面要是发现了导入同样的文件,它会直接从缓存里面去拿值。
如果不希望它从缓存里面去拿,每次都从新导入 ,这个时候 ,我们需要在被导入的文件后面添加一句代码,删除缓存
console.log("这是Person.js文件");
//添加这样的代码,则表示 每次导入这个文件完成以后,就从缓存里面删除
delete require.cache[module.filename];
当我们在Person.js
的文件里面添加了上面的代码以后,表示每次导入完成以后都从缓存里面删除自己这个模块。这个时候,当我们在Student.js里面多次导入Person.js的时候,我们会看到控制台输出了多条记录
当使用require()
去导入 JS文件的时候,里面的代码会执行,但是在这个文件里面的变量,方法,对象等这些都不会拿过来。如果想拿到这些变量,方法与对象,则需要被导入的文件(这里指Person.js)手动导出来
模块导出
模块的导出使用的不是方法,而是两个对象
在NodeJS的模块化规范CommonJS
里面,每个文件内部都有两个自带的对象,分别是exports
与module.exports
exports
它是一个指针,指向了module.exports
,module.exports
默认是一个空的对象,这个空对象就是CommonJS专门用于公开这个文件里面的变量,方法以及对象的集合
现在,我们针对导入与导出的情况做如下代码测试
person.js
console.log("这是Person.js文件");
let a="hello world";
module.exports=a;
Student.js
let a = require("./Person.js");
console.log(a);
通过上面的代码,我们已经可以看到a这个变量拿到了Stduent.js里面来执行,它没有报错,这就说明a被导出来了
如果我们需要导出多个变量或方法以及对象的时候,可以用如下的方式来进行
person.js
console.log("这是Person.js文件");
let a="hello world";
let b="你好,世界";
module.exports={
a,b //解构
}
student.js
// let obj = require("./Person.js");
// console.log(obj);
//导入以后直接解构
let {a,b} = require("./Person.js");
console.log(a);
console.log(b);
导出多个的时候,我们可以在导出的时候封装成一个对象,在导入的过程当中,我们也可以直接使用对象
exports与module.exports的区别
在之前,我们已经提过了,exports
是一个指针,指向了module.exports
,而CommonJS
里面真正负责公开的是module.exports
,这两个东西最初是相等的,都是指内存推里面存放的一个空对象{}
如果我们在导出的时候,直接使用下面方式
let a="hello world";
exports=a;
这个时候是不行的,因为exports的内存栈地址已经不指向了module.exports
,所以这个a是导不出去的
a.js代码
console.log("hello world");
let stra="变量stra";
exports=stra;
//exports已经不指向 module.exports
//真正的导出是module.exports {}
//不允许直接更改exports的值,因为它是一个指针,指向module.exports
b.js代码
let stra = require("./a.js");
//这个地方的stra它是一个空对象
console.log(stra);
这个时候,得到的stra它仍然是一个空的对象{}
当我们对exports=stra
赋值以后的内存图如下
如果真的要使用exports
导出,则应该使用对象操作属性的方法,如下所示
c.js代码
let str="hello world";
exports.str=str;
exports.sayHello=function(){
console.log("你好啊,世界")
}
d.js代码
let obj = require("./c.js");
console.log(obj);
//这个对象也可以直接解构
module.exports与exports最好不要同时使用,如果同时使用会出现exports
找不到module.exports
的情况
let str="hello world";
module.exports={
abc:"abc"
}
exports.str=str;
exports.sayHello=function(){
console.log("你好啊,世界")
}
如果是像上面这种方式去给的,那么exports
又会失效,这个时候的module.exports
重新赋值的,而exports
还指向原来的对象
根据内存图的解释 ,如果非要一起使用,我们都不应该改变内存地址
let str="hello world";
// module.exports={
// abc:"abc"
// }
module.exports.abc="abc"; //不改变exports的地址,直接在这个对象上面的属性赋值
exports.str=str;
exports.sayHello=function(){
console.log("你好啊,世界")
}
现在我们来实现一个完整的对象继承
Person.js代码
class Person{
constructor(userName,sex,age){
this.userName=userName;
this.sex=sex;
this.age=age;
}
sayHello(){
console.log(`大家好,我叫${this.userName}, 我的性别是${this.sex},我的年龄是${age}`);
}
}
module.exports=Person;
Student.js代码
let Person= require("./Person.js");
//这个Student如果想去继承Person对象,怎么办呢
class Student extends Person {
constructor(userName,sex,age,sid){
super(userName,sex,age);
this.sid=sid;
}
}
let s=new Student("杨标","男",18,"H19040001");
console.log(s.userName);
评论区