SKT-otto

导航

ES6(4)


1 、Set 和 WeakSet用法
1.1 什么是set
Set是ES6给开发者带来的一种新的数据结构,你可以理解为值的集合。我们平时见到的数组Array也是一种数据结构,但是Set跟其他数据结构不同的地方就在于:它的值不会有重复项。

1.2 set的基本用法
Set的用法:
 var s = new Set();
    console.log(s);
    //打印结果:Set {}
Set本身是一个构造函数,你可以理解为一个类,使用的时候需要用new来创建一个实例。以上的案例就是这样创建出一个Set结构,我们打印出来看看,控制台输出:Set{ }。
        它不是值的集合吗,那如果我们想给Set对象增加一些值成员,我们要怎么做?
        你可以这样:
 var s = new Set([1,2,3]);
    console.log(s);
    //打印结果:Set {1, 2, 3}
案例中,用数组[ 1,2,3 ]作为参数传入,得到的变量s为Set { 1, 2, 3 }。
        除了上面的方法,你还可以这样:
var s = new Set();
    //使用add方法添加成员
    s.add(1);
    s.add(2);
    s.add(3);
    console.log(s);
    //打印结果:Set {1, 2, 3}
如果你不想用数组作为参数来传值初始化变量s,你还可以通过Set 结构提供的add方法来添加方法,也是没问题的。

1.3 成员值唯一
不过,为Set 结构添加成员值的时候,要注意一点是,set结构的成员值是没有重复的,每个值都是唯一的,如果人为地给它添加相同的成员值,会发生什么呢?
var s = new Set();
    s.add(1);
    s.add(1);
    console.log(s);
    //打印结果: Set {1}
 很明显,set结构会自动忽略相同的值,只会保留一个相同的值。
        Set 结构除了提供add方法用于添加成员以外,还提供了哪些方法和属性呢?

1.4 size属性
size属性:获取成员的个数。
 let s = new Set([1,2,3,4]);
    s.size; //结果:4
结果为4,跟我们预期一致,很简单。
 
1.5 delete属性
 delete( )方法:用户删除Set结构中的指定值,删除成功返回:true,删除失败返回:fasle。
//用数组作为参数初始化
    var s = new Set([1,2,3]);
    console.log(s);
    //打印结果:Set {1, 2, 3}
    //使用delete方法删除指定值
    s.delete(2);//结果:true
    s.delete(4);//结果:false
    console.log(s);
    //打印结果:Set {1, 3}
 
案例中,我们使用delete( )方法删除了指定值:2,结果返回true。删除指定值:4的时候,返回false,原因是变量s中找不到数字4。
        当Set结构的成员比较多,你觉得一个一个删除比较麻烦的时候,你可以用下面这个方法。

1.6 clear方法
clear( )方法:清除所有成员。
//用数组作为参数初始化
    var s = new Set([1,2,3]);
    console.log(s);
    //打印结果:Set {1, 2, 3}
    s.clear();
    console.log(s);
    //打印结果:Set {}
  上面的代码中,在使用clear( )方法之前和之后都打印了变量s的值,我们看到,clear( )方法使用之后,Set结构的成员都被清除了,一个不留。

1.7 has方法
has( )方法:判断set结构中是否含有指定的值。如果有,返回true;如果没有,返回fasle。
 //用数组作为参数初始化
    var s = new Set([1,2,3]);
    s.has(1);//结果:true
    s.has(4);//结果:false
 
当判断变量s中是否含有数字1时,得到的结果是true,判断是否含有数字4的时候,得到的结果是false。

1.8 enteries方法
entries( )方法:返回一个键值对的遍历器。
 //用数组作为参数初始化
    var s = new Set(['a','b','c']);
    s.entries();
    //结果:SetIterator {["a", "a"], ["b", "b"], ["c", "c"]}
 
注意得到的结果,成员值“a”对应的键值对是[“a”,”a”],也就是说:Set结构是键名和键值是同一个值。

1.9 keys和values方法
 keys( )方法:返回键名的遍历器。
 values( )方法:返回键值的遍历器。
//用数组作为参数初始化
    var s = new Set(['a','b','c']);
    s.keys();
    //结果:SetIterator {"a", "b", "c"}
    s.values();
    //结果:SetIterator {"a", "b", "c"}
 
我把两个函数放在一起演示,是因为上面entries()方法的使用告诉我们,Set结构的键名和键值是同一个值,那么我们就用Set结构提供的keys( )和values()方法来检验一下。从得到的结果可以看出:两者确实一致。
//用数组作为参数初始化
    var s = new Set(['a','b','c']);
    //用for...of遍历
    for(let [i,v] of s.entries()){
        console.log(i+' '+v);
    }
    //打印结果:a  a   
    //         b  b 
    //         c  c  

1.10 foreach方法
forEach( )方法:遍历每一个成员。
 //用数组作为参数初始化
    var s = new Set(['a','b','c']);
    //使用回调函数遍历每个成员
    s.forEach(function(value,key){
        console.log(value,key)
    });
    //打印结果:a  a
    //         b  b
    //         c  c
使用方式跟数组的forEach一样。当然,得到的value是key的值是一样的。

1.11 set用途之一
利用Set结构的成员值不能重复的特点,我们可以轻松的完成一些效果。
比如:有一天面试官要小菜实现数组去重的效果。
这个时候,小菜想起了ES6的Set结构的特性,再也不需要去遍历数组的元素,然后再一个个对比了。 然后自信地写起下了代码:
 //目标数组arr,要求去重
    let arr = [1,2,2,3,4,4,4];
    let s = new Set(arr);
    //结果:Set {1,2,3,4}
    let newArr = Array.from(s);
    //结果:[1,2,3,4],完成去重
 上面的代码重点是一个含有重复元素的数组,作为参数,用于初始化一个Set实例对象,因为Set结构不会含有重复成员,就会自动忽略相同的元素,只保留一个相同的值。

1.12 Weakset结构
讲到Set结构,就不能不讲WeakSet结构。Set和WeakSet两者名字就很像,注定它们有很多一样的地方。
        WeakSet结构同样不会存储重复的值,不同的是,它的成员必须是对象类型的值。
 //初始化一个WeakSet对象
    let ws = new WeakSet([{"age":18}]);
    //结果:WeakSet {Object {age: 18}}
  我们初始化一个WeakSet对象,参数一个数组,数组的成员必须是对象类型的值{"age":18},否则就会报错。
 //初始化一个WeakSet对象
    let ws = new WeakSet([1,2]);
    //结果:报错
   以上的写法就会报错,因为数组的元素不是对象类型的,是数字1,2。
        实际上,任何可遍历的对象,都可以作为WeakSet的初始化参数。比如:数组。
 let arr1 = [1];
    let arr2 = [2];
    //初始化一个WeakSet对象,参数是数组类型
    let ws = new WeakSet([arr1,arr2]);
    //结果:WeakSet {Object {age: 18}}
同样,WeakSet结构也提供了add( ) 方法,delete( ) 方法,has( )方法给开发者使用,作用与用法跟Set结构完全一致。
 
        另一个不同点是:WeakSet 结构不可遍历。因为它的成员都是对象的弱引用,随时被回收机制回收,成员消失。所以WeakSet 结构不会有keys( ),values( ),entries( ),forEach( )等方法和size属性。

2 、Map和WeakMap用法
什么是Map?
介绍什么是Map,就不得不说起Object对象,我们都知道Object对象是键值对的集合:
 //Object对象
    {"name":"前端君","gender":1}
 
ES5中的key键名的类型要求一定是字符串,当然,ES6已经允许属性名的类型是Symbol。在,ES6 提供了Map结构给我们使用,它跟Object对象很像,但是不同的是,它的key键名的类型不再局限于字符串类型了,它可以是各种类型的值;可以说,它比Object对象更加灵活了,当然,也更加复杂了。

Map的基本用法
Map结构提供了一个构造函数给我们,我们使用的时候需要用new来创建实例:
   let m = new Map();
 如果想要在创建实例的同时,初始化它的内容,我们可以给它传参,形式跟Set结构类型,都是需要用数组作为参数,我们来试试看看:
 let m = new Map([
            ["name","前端君"],
            ["gender",1]
    ]);
    console.log(m);
    //打印结果:Map {"name" => "前端君", "gender" => 1}
大家注意Map( )方法里面的参数,首先它是一个数组,而里面的内容也是由多个数组组成,“name”:“前端君”作为一个键值对,将它们装在一个数组里面,[“name”:“前端君”],另外一组键值对也一样:[“gender”:1 ]。这就是初始化一个Map结构实例的基本写法。
        初始化成实例后,Map结构还提供了一些实例的属性和方法供我们实现对实例的操作。我们一起看看都有哪些属性和方法。

set方法
set( )方法作用:给实例设置一对键值对,返回map实例。
 let m = new Map();
//set方法添加
//添加一个string类型的键名
m.set("name","前端君"); 
//添加一个数字类型的键名
m.set(1,2);
console.log(m);
//打印结果:Map {"name" => "前端君", 1 => 2}
set方法的使用很简单,只需要给方法传入key和value作为键名和键值即可。注意:第三行代码中,我们传入的key是数字1,这就证明了,Map结构确实可以存储非字符串类型的键名,当然你还可以设置更多其它类型的键名,比如:
 //数组类型的键名
m.set([1],2);
//对象类型的键名
m.set({"name":"Zhangsan"},2);
//布尔类型的键名
m.set(true,2);
//Symbol类型的键名
m.set(Symbol('name'),2);
//null为键名
m.set(null,2);
//undefined为键名
m.set(undefined,2);
  以上6种类型值都可以作为键名,可以成功添加键值对,没毛病。
        使用set方法的时候有一点需要注意,如果你设置一个已经存在的键名,那么后面的键值会覆盖前面的键值。我们演示一下:
 
let m = new Map();
m.set("name","前端君");
console.log(m);
//结果:Map {"name" => "前端君"}
//再次设置name的值
m.set("name","隔壁老王");
console.log(m);
//结果:Map {"name" => "隔壁老王"}
 
上面的案例,我们第一次把name的值设置为“前端君”,当再次使用set方法设置name的值的时候,后者成功覆盖了前者的值,从此“前端君” 变 “隔壁老王”。
get方法
get( )方法作用:获取指定键名的键值,返回键值。
let m = new Map([["name","前端君"]]);
m.get("name");//结果:前端君
m.get("gender");//结果:undefined
   get方法使用也很简单,只需要指定键名即可。获取存在对应的键值,如果键值对存在,就会返回键值;否则,返回undefined,这个也很好理解。
 
delete方法
delete( )方法作用:删除指定的键值对,删除成功返回:true,否则返回:false。
let m = new Map();
m.set("name","前端君");
//结果:Map {"name" => "前端君"}
m.delete("name");//结果:true
m.delete("gender");//结果:false
  我们使用delete方法,删除“name”的时候成功,返回了true。删除“gender”的时候,由于Map结构中不存在键名:“gender”,所以删除失败,返回false。
clear方法
跟Set结构一样,Map结构也提供了clear( )方法,让你一次性删除所有键值对。
let m = new Map();
m.set("name","前端君");
m.set("gender",1);
m.clear();
console.log(m);
//打印结果:Map {}
使用clear方法后,我们再打印一下变量m,发现什么都没有,一个空的Map结构,说明clear方法起作用了。
has方法
has( )方法作用:判断Map实例内是否含有指定的键值对,有就返回:true,否则返回:false。
let m = new Map();
m.set("name","前端君");
m.has('name');//结果:true
m.has('age');//结果:false
   Map实例中含有键名:name,就返回了true,键名age不存在,就返回false。好理解吧,比较简单。

entries方法
entries( )方法作用:返回实例的键值对遍历器。
let m = new Map([
    ["name","前端君"],
    ["age",25]
]);
for(let [key,value] of m.entries()){
    console.log(key+'  '+value);
}
//打印结果:name  前端君
//              age  25
案例中的 m.entries( ) 返回键值对的遍历器,使用了for...of来遍历这个遍历器,得到的值分别赋值到key和value,然后控制台分别输出它们。
 
keys和values方法
keys( )方法:返回实例所有键名的遍历器。
values( ) 方法:返回实例所有键值的遍历器。
既然都是遍历器,那就用for...of把它们遍历出来吧:
let m = new Map([
    ["name","前端君"],
    ["age",25]
]);
//使用keys方法获取键名
for(let key of m.keys()){
    console.log(key);
}
//打印结果:name
//age
//使用values方法获取键值
for(let value of m.values()){
    console.log(value);
}
//打印结果:前端君
//                 25
keys方法和values方法的使用方式一致,只是返回的结果不同。

forEach方法
除了使用以上三个方法实现遍历以外,我们还可以使用forEach遍历每一个键值对:
 let m = new Map([
    ["name","前端君"],
    ["age",25]
]);   
m.forEach(function(value,key){
    console.log(key+':'+value);
});
    //打印结果:name:前端君
    //                 age:25
 forEach方法接收一个匿名函数,给匿名函数传参value和key,分别是Map实例的键名和键值,这个方法的使用相信大家一定不会陌生。
 
size属性
其中一个常用的属性就是size:获取实例的成员数。
let m = new Map();
    m.set(1,3);
    m.set('1','3');
    m.size;//结果:2
 
使用set方法给实例m添加了两个键值对成员,所以实例的 size为:2。
什么是WeakMap?
WeakMap结构和Map结构很类似,不同点在于WeakMap结构的键名只支持引用类型的数据。哪些是引用类型的值呢?比如:数组,对象,函数。
关于什么是引用类型,其中涉及到了传址和传值的区别,还记得装修工张师傅和王师傅的例子吗?
WeakMap的基本用法
WeakMap结构的使用方式和Map结构一样:
 let wm = new WeakMap();
 两者都是使用new来创建实例。如果添加键值对的话,我们同样是使用set方法,不过键名的类型必须要求是引用类型的值,我们来看看:
let wm = new WeakMap();
//数组类型的键名
wm.set([1],2);
//对象类型的键名
wm.set({'name':'Zhangsan'},2);
//函数类型的键名
function fn(){};
wm.set(fn,2);
console.log(wm);
//打印:WeakMap {
        [1] => 2,
        Object {name: "Zhangsan"} => 2,
        function => 2
        }
  从打印结果可以看出,以上类型的键名都可以成功添加到WeakMap实例中。
WeakMap与Map的区别?
如果是普通的值类型则不允许。比如:字符串,数字,null,undefined,布尔类型。而Map结构是允许的,这就是两者的不同之处,谨记。
 
        跟Map一样,WeakMap也拥有get、has、delete方法,用法和用途都一样。不同地方在于,WeakMap不支持clear方法,不支持遍历,也就没有了keys、values、entries、forEach这4个方法,也没有属性size。
 
        理由跟WeakSet结构一样:键名中的引用类型是弱引用,你永远不知道这个引用对象什么时候会被垃圾回收机制回收了,如果这个引用类型的值被垃圾机制回收了,WeakMap实例中的对应键值对也会消失。
 
3 、ES6的Promise对象
Promise设计初衷
你需要用ajax进行多次请求,而且,每次请求都依赖上一次请求返回的数据来作为参数,然后继续发出请求,你把代码写成了这样:
 //------请求A 开始---------
    $.ajax({
        success:function(res1){
            //------请求B 开始----
            $.ajax({
                success:function(res2){
                    //----请求C 开始---
                    $.ajax({
                        success:function(res3){
                        }
                    });
                    //---请求C 结束---
                }   
            });
            //------请求B 结束-----
        }
    });
    //------请求A 结束---------
 
上面的案例,假设请求C需要依赖请求B返回的数据,所以,C只能放在B的success函数内;B需要依赖A请求得到的数据作为参数,所以,B只能放在A的success函数内;也就是:请求A包含着请求B,请求B又包含了请求C。
就这样,请求顺序为:请求A -> 请求B -> 请求C,最后你也能顺利的完成了任务。

传统写法的缺点:
1.  如果存在多个请求操作层层依赖的话,那么以上的嵌套就有可能不止三层那么少了,加上每一层还会有复杂的业务逻辑处理,代码可读性也越来越差,不直观,调试起来也不方便。如果多人开发的时候没有足够的沟通协商,大家的代码风格不一致的话,更是雪上加霜,给后面的维护带来极大的不便。
 
2.  如果请求C的需要依赖A和B的结果,但是请求A和B缺互相独立,没有依赖关系,以上的实现方式,就使得B必须得到A请求完成后才可以执行,无疑是消耗了更多的等待时间。
        既然使用这种回调函数层层嵌套(又称:回调地狱)的形式存在缺点,ES6想了办法治它,所以就有了Promise的出现了。
那么我们就知道了:Promise对象能使我们更合理、更规范地进行处理异步操作。
 
Promise基本用法
我们就看看它的基本用法:promise 承诺
 
 Promise对象是全局对象,你也可以理解为一个类,创建Promise实例的时候,要有那个new关键字。参数是一个匿名函数,其中有两个参数:resolve(解决)和reject(拒绝),两个函数均为方法。resolve方法用于处理异步操作成功后业务;reject方法用于操作异步操作失败后的业务。

Promise的三种状态
Promise对象有三种状态:
1.pending:刚刚创建一个Promise实例的时候,表示初始状态;
2.fulfilled:resolve方法调用的时候,表示操作成功;
3.rejected:reject方法调用的时候,表示操作失败;
状态只能从 初始化 -> 成功  或者  初始化 -> 失败,不能逆向转换,也不能在成功fulfilled
和失败rejected之间转换。
let pro = new Promise(function(resolve,reject){
        //实例化后状态:pending
        if('操作成功'){
            resolve();
            //resolve方法调用,状态为:fulfilled
        }else{
            reject();
            //reject方法调用,状态为:rejected
        }
    });
上面的注释,讲清楚了一个Promise实例的状态改变情况。
        初始化实例后,对象的状态就变成了pending;当resolve方法被调用的时候,状态就变成了:成功fulfilled;当reject方法被调用的时候,状态就会有pending变成失败rejected。

Then方法
了解了Promise的创建和状态,我们来学习一个最重要的实例方法:then( )方法。
  then( )方法:用于绑定处理操作后的处理程序。
  pro.then(function (res) {
        //操作成功的处理程序
    },function (error) {
        //操作失败的处理程序
    });
参数是两个函数,第一个用于处理操作成功后的业务,第二个用于处理操作异常后的业务。

Catch方法
对于操作异常的程序,Promise专门提供了一个实例方法来处理:catch( )方法。
 pro.catch(function (error) {
        //操作失败的处理程序
    });
 catch只接受一个参数,用于处理操作异常后的业务。
        综合上面的两个方法,大家都建议将then方法用于处理操作成功,catch方法用于处理操作异常,也就是:
pro.then(function (res) {
        //操作成功的处理程序
    }).catch(function (error) {
        //操作失败的处理程序
    });
之所以能够使用链式调用,是因为then方法和catch方法调用后,都会返回promise对象。
        讲了那么多,如果你之前一点都没接触过Promise的话,现在一定很懵逼,没关系,下面我们用一个案例来串联前面的知识点,演示一下,认真阅读注释:
 //用new关键字创建一个Promise实例
    let pro = new Promise(function(resolve,reject){
        //假设condition的值为true
        let condition = true;
        if(condition){
            //调用操作成功方法
            resolve('操作成功');
            //状态:pending->fulfilled
        }else{
            //调用操作异常方法
            reject('操作异常');
            //状态:pending->rejected
        }
    });
    //用then处理操作成功,catch处理操作异常
    pro.then(function (res) {
        //操作成功的处理程序
        console.log(res)
    }).catch(function (error) {
        //操作失败的处理程序
        console.log(error)
    });
    //控制台输出:操作成功
上面案例的注释十分详细,串联起了上面介绍的所有知识点:创建实例,状态转换,then方法和catch方法的使用。
 
        由于我们设置了变量condition的值为true,所以执行后控制台输出的结果是:“操作成功”。
 
案例
我们看看下面的案例:
 let pro = new Promise(function(resolve,reject){
        if(true){
            //调用操作成功方法
            resolve('操作成功');
        }else{
            //调用操作异常方法
            reject('操作异常');
        }
    });
    //用then处理操作成功,catch处理操作异常
    pro.then(requestA)
        .then(requestB)
        .then(requestC)
        .catch(requestError);
    function requestA(){
        console.log('请求A成功');
        return '请求B,下一个就是你了';
    }
    function requestB(res){
        console.log('上一步的结果:'+res);
        console.log('请求B成功');
        return '请求C,下一个就是你了';
    }
    function requestC(res){
        console.log('上一步的结果:'+res);
        console.log('请求C成功');
    }
    function requestError(){
        console.log('请求失败');
    }
    //打印结果:
    //请求A成功
    //上一步的结果:请求B,下一个就是你了
    //请求B成功
    //上一步的结果:请求C,下一个就是你了
    //请求C成功
案例中,先是创建一个实例,还声明了4个函数,其中三个是分别代表着请求A,请求B,请求C;有了then方法,三个请求操作再也不用层层嵌套了。我们使用then方法,按照调用顺序,很直观地完成了三个操作的绑定,并且,如果请求B依赖于请求A的结果,那么,可以在请求A的程序用使用return语句把需要的数据作为参数,传递给下一个请求,案例中我们就是使用return实现传递参数给下一步操作的。
 
 
 
Promise.all方法
Promise.all( )方法:接受一个数组作为参数,数组的元素是Promise实例对象,当参数中的实例对象的状态都为fulfilled时,Promise.all( )才会有返回。
//创建实例pro1
    let pro1 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('实例1操作成功');
        },5000);
    });
   
    //创建实例pro2
    let pro2 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('实例2操作成功');
        },1000);
    });
   
    Promise.all([pro1,pro2]).then(function(result){
        console.log(result);
    });
    //打印结果:["实例1操作成功", "实例2操作成功"]
 
上述案例,我们创建了两个Promise实例:pro1和pro2,我们注意两个setTimeout的第二个参数,分别是5000毫秒和1000毫秒,当我们调用Promise.all( )方法的时候,会延迟到5秒才控制台会输出结果。
        因为1000毫秒以后,实例pro2进入了成功fulfilled状态;此时,Promise.all( )还不会有所行动,因为实例pro1还没有进入成功fulfilled状态;等到了5000毫秒以后,实例pro1也进入了成功fulfilled状态,Promise.all( )才会进入then方法,然后在控制台输出:["实例1操作成功","实例2操作成功"]。
        这个方法有什么用呢?一般这样的场景:我们执行某个操作,这个操作需要得到需要多个接口请求回来的数据来支持,但是这些接口请求之前互不依赖,不需要层层嵌套。这种情况下就适合使用Promise.all( )方法,因为它会得到所有接口都请求成功了,才会进行操作。
 
Promise.race方法
Promise.race()方法:它的参数要求跟Promise.all( )方法一样,不同的是,它参数中的promise实例,只要有一个状态发生变化(不管是成功fulfilled还是异常rejected),它就会有返回,其他实例中再发生变化,它也不管了。
 //初始化实例pro1
    let pro1 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('实例1操作成功');
        },4000);
    });
    //初始化实例pro2
    let pro2 = new Promise(function(resolve,reject){
        setTimeout(function () {
            reject('实例2操作失败');
        },2000);
    });
    Promise.race([pro2,pro1]).then(function(result){
        console.log(result);
    }).catch(function(error){
        console.log(error);
    });
    //打印结果:实例2操作失败
 同样是两个实例,实例pro1不变,不同的是实例pro2,这次我们调用的是失败函数reject。
        由于pro2实例中2000毫秒之后就执行reject方法,早于实例pro1的4000毫秒,所以最后输出的是:实例2操作失败。
        以上就是对Promise对象的内容讲解,上面提到了一个概念:回调地狱;指的是过多地使用回调函数嵌套,使得调试和维护起来极其的不便。

4 、类基本用法
4.1 类的属性和方法
声明一个类的写法:
 
代码很简短,我们通过关键字class来声明一个名字叫Animal的类,可以看到类里面(花括号 {}里面)有一个叫constructor(构造、建造)方法,它就是构造方法,构造方法里面的this,指向的是该类实例化后的对象,这就是实现了一个类的声明。
 
        其中,构造方法constructor是一个类必须要有的方法,默认返回实例对象;创建类的实例对象的时候,会调用此方法来初始化实例对象。如果你没有编写constructor方法,执行的时候也会被加上一个默认的空的constructor方法。
 
4.2 类的实例化对象
了解了类的声明和constructor构造函数的特点,我们下面来了解如何给类添加属性和方法。
 
我们把类名后面的括号{ }里面的内容称之为类体,我们会在类体内来编写类的属性和方法。上面的案例中,类体内有2个方法:constructor( )、getName()。
        其中constructor方法是构造方法,在实例化一个类的时候被调用。constructor方法是必须的,也是唯一的,一个类体不能含有多个constructor构造方法。我们可以在方法里面自定义一些对象的属性,比如案例中的name属性。
 此外,我们还自定义了一个getName( )方法,它属于类的实例方法,实例化后对象可以调用此方法。

4.3 类的静态方法
掌握了类的属性和方法的写法,接下来,我们学习如何创建对象和使用对象的实例方法:
 
还是同一个类Animal,我们通过new来创建了实例对象dog,构造方法会把传进去的参数“dog”通过this.name赋值给对象的name属性,所以dog的name属性为“dog”,对象dog还可以调用自己的实例方法getName( ),结果返回:“This is a dog”。
        实例对象的创建有几个要注意的事项:
1. 必须使用new创建字来创建类的实例对象
2.先声明定义类,再创建实例,否则会报错
 
4.4 类的继承
说到类class,就不得不说类的继承,ES6使用extends关键字来实现子类继承父类,我们来演示一下:
 
上面的案例中,我们定义两个类,Animal类作为父类,Dog类作为子类,然后通过关键字extends来实现继承,此外,我们还注意到一个关键字super,它相当于是父类中的this。
 
 我们可以用super来引用父类,访问父类的方法,我们来演示一下:
 
 
 
在父类中,我们定义了say方法,想要在子类中调用父类的say方法的话,我们使用super.say( )即可实现。
        使用super有几个要注意的事项:
1. 子类必须在constructor方法中调用super方法
2. 调用super( ),才可以使用this,否则报错
  以上就是关于类继承的介绍,重点在于关键字extends和super,尤其是super的理解和使用,大家需要理解透彻。
 
5 、Module模块
5.1 模块化的初衷
现在的web系统越来越庞大、复杂,需要团队分工,多人协作,大型系统的javascript文件经常存在复杂的依赖关系,后期的维护成本会越来越高。
 
 JavaScript模块化正式为了解决这一需求而诞生。
 竟然模块化这么重要,我们看看ES6的module模块是什么实现的?
  Ps:目前还没有浏览器支持ES6的module模块。
 假设现在有两个js文件,分别是module-A.js和module-B.js,我们把它们视为两个模块。带着这个假设,下面我们来学习module模块的几个概念以及它们的含义。
5.2 模块Module
模块Module:一个模块,就是一个对其他模块暴露自己的属性或者方法的文件。
在这里,我们会把module-A.js和module-B.js分别当作两个模块(moduleA模块和moduleB模块)来对待和处理。用这两个模块来演示如何暴露一个模块的属性或方法。
 
5.3 导出Export
导出Export:作为一个模块,它可以选择性地给其他模块暴露(提供)自己的属性和方法,供其他模块使用。
 
5.4 导入Import
导入Import:作为一个模块,可以根据需要,引入其他模块的提供的属性或者方法,供自己模块使用。
 
5.5 模块化的实现
带着这三个概念,我们来演示一下它们的基本用法:
moduleB模块代码:
 
模块B我们使用关键字export关键字,对外暴露了一个属性:name的值为:字符串“前端君”。一个关键字,一句代码就实现了,是不是很简单。
模块B演示了导出,接下来我们用模块A来演示如何导入。
moduleA模块代码:
 
模块A我们使用关键字import导入了模块B的name属性,并且赋值给变量name。关键字from的作用是指定你想要引入的模块,我们这里指定的是module-B.js文件,也就是上面的模块B。打印结果:“前端君”正是模块B的对外暴露的属性。
 
5.6 批量导出
对于模块B,如果你想导出(暴露)多个属性和方法的话,你可以这样实现:
 
上面,我们定义了2个属性和1个方法,最后用一个对象实现将它们批量导出。我们更推荐的是使用这种方法实现导出,因为当对外暴露的属性和方法较多的时候,这种方法可以更直观地看出当前模块暴露了哪些变量。
 
        而对于这种批量导出,我们导入的时候怎么对应上呢?
 
 
同样,我们使用多个同名变量就可以获取对应的属性和方法,变量名字必须跟导出的一致才能准确获取,位置顺序无要求。

5.7 重命名导出的变量
也许你突发奇想,想给导入的变量换一个名字的话,你可以这样做:
 
使用关键字as,可以实现给变量name更换名字为myname。最后正确输出myname的值:“前端君”。
 
5.8 整体导入
我们还可以使用星号*实现整体导入:
 
使用星号符*将模块B提供的所有属性和方法整体导入赋值给变量obj,我们可以点运算符来获取它的属性和方法。

5.9 默认导出
默认导出,每个模块支持我们导出一个没有名字的变量,我们使用关键语句export default来实现:
 
  我们使用export default关键字对外导出一个匿名函数,导入这个模块的时候,可以为这个匿名函数取任意的名字,我们试一下导入上面那个匿名函数:
 
同样是使用import关键字导入模块B,但是这次不需要使用大括号{ }。我们使用新的名字:sayDefault来代替导入的匿名函数,最后调用一下,打印结果正是模块B默认导出的匿名函数的执行效果。
 
5.10 注意事项
1. 声明的变量,对外都是只读的。
 
上面的代码片段包含了2个模块,其中,模块B导出了字符串变量name,模块A导出变量name之后试图修改它的值,结果报错。
    但是,如果模块B导出的是对象类型的值,就可修改。
 
 上面的代码片段包含了2个模块,模块B导出了对象person,模块A导入后,对其属性name进行修改,结果修改成功,这一点大家要注意,并不是所有导出的变量都不可修改,对象类型就可修改。
2. 导入不存在的变量,值为undefined。
 
模块A想导入的变量height,在模块B中并没有提供,但这不会抛出异常,只是height的值为undefined。
 

posted on 2020-08-06 20:37  SKT-otto  阅读(57)  评论(0编辑  收藏  举报