ES6新特性(2)

目录

Set 和 Map 数据结构

Set 数据结构

基本用法

size属性

Set 数据结构方法

add()

delete()

has()

clear()

Array.from方法可以将 Set 结构转为数组。

遍历操作

WeakSet数据结构

Map数据结构

Map数据结构的属性和方法

Proxy

Reflect

同步与异步

Promise 对象

Promise对象_方法

Generator 函数的语法

Async 函数

Fetch API

Fetch API POST请求注意事项

Fetch网络请求应用

封装Fetch网络请求

Class 的基本语法

类的由来

constructor 方法

类的实例

取值函数(getter)和存值函数(setter)

静态方法

静态属性

私有方法和私有属性

Class 的继承

super 关键字

Module 的语法

export 命令

import 命令

模块的整体加载

export default 命令


Set 和 Map 数据结构

Set 数据结构

基本用法

ES6 提供了新的数据结构 Set 。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。Set.prototype.constructor:构造函数,默认就是Set函数。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i);
}
// 2 3 5 4
通过 add() 方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
Set 函数可以接受一个数组作为参数
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
数组去除重复成员的方法
// 去除数组的重复成员
[...new Set(array)]
字符串去除重复字符
[...new Set('ababbc')].join('')
// "abc"
Set 加入值的时候,不会发生类型转换,所以 5 "5" 是两个不同的值。
var mySet = new Set();
mySet.add("5")
mySet.add(5)
console.log(mySet); // Set(2) {'5', 5}

size属性

返回 Set 实例的成员总数
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

Set 数据结构方法

add()

set 添加方法
var mySet = new Set();
mySet.add("5")
console.log(mySet);

delete()

删除某个值,返回一个布尔值,表示删除是否成功
var mySet = new Set();
mySet.add("5")
var flag = mySet.delete("5");
console.log(flag);  // true

has()

返回一个布尔值,表示该值是否为 Set 的成员
var mySet = new Set();
mySet.add("5")
var flag = mySet.has("5");
console.log(flag);  // true

clear()

清除所有成员,没有返回值
var mySet = new Set();
mySet.add("5")
mySet.clear();
console.log(mySet);  // Set(0) {size: 0}

Array.from方法可以将 Set 结构转为数组。

const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

这就提供了去除数组重复成员的另一种方法。

function dedupe(array) {
  return Array.from(new Set(array));
}

dedupe([1, 1, 2, 3]) // [1, 2, 3]

遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员
  • Set.prototype.keys() :返回键名的遍历器
  • Set.prototype.values() :返回键值的遍历器
  • Set.prototype.entries() :返回键值对的遍历器
  • Set.prototype.forEach() :使用回调函数遍历每个成员
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
    console.log(item); // red green blue
}
for (let item of set.values()) {
    console.log(item); // red green blue
}
for (let item of set.entries()) {
    console.log(item); // ['red', 'red'] ['green', 'green'] ['blue', 'blue']
}
let set = new Set([1, 4, 9]);
set.forEach((value) => console.log(value))

WeakSet数据结构

基础用法
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与Set 有个区别
WeakSet 的成员只能是对象,而不能是其他类型的值
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
WeakSet 是一个构造函数,可以使用 new 命令,创建 WeakSet 数据结构
在初始化 WeakSet 的时候,无论是数组还是对象,都是将他们
的成员放入到 WeakSet 中的
const ws = new WeakSet([{name: 'iwen'}]);//再套一层
console.log(ws);
const ws = new WeakSet([[10,20]]);
console.log(ws);
WeakSet 结构三个方法
  • WeakSet.prototype.add(value) :向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value) :清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value) :返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
let user1 = {
    "name1": "iwen"
}
let user2 = {
    "name2": "itbaizhan"
}
let weakSet = new WeakSet([user1])
console.log(weakSet.add(user2)) // WeakSet
{{…}, {…}}
console.log(weakSet.has(user2)) // true
console.log(weakSet.delete(user2)) // true
console.log(weakSet.has(user2)) // false

Map数据结构

含义和基本用法
JavaScript 的对象( Object ),本质上是键值对的集合( Hash 结 构),但是传统上只能用字符串当作键。这给它的使用带来了很大 的限制
var user = {
    "name":"iwen",
    age:20
}
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata'; 
data['[object HTMLDivElement]'] // "metadata"
代码原意是将一个 DOM 节点作为对象 data 的键,但是由于对象只接受字符串作为键名,所以 element 被自动转为字符串 [objectHTMLDivElement] 为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“ 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了 字符串 —值 的对应, Map 结构提供了 的对应,是一种更完善的 Hash 结构实现
    
注意,只有对同一个对象的引用, Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
代码的 set get 方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此 get 方法无法读取该键, 返回 undefined
同理,同样的值的两个实例,在 Map 结构中被视为两个键
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map.set(k1, 111).set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222


const map = new Map();
 // const k1 = ['a'];
 // const k2 = ['a'];
 map.set(['a'], 111).set(['a'], 222);
 console.log(map.get(['a']));
 // undefined
 console.log(map.get(['a']));
 // undefined
由上可知, Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键
        const map = new Map();
        map.set(['hello'], 10);
        var h = ["hello"];
        map.set(h, 20)
        console.log(map.get(h));//20
        console.log(map.get(['hello']));//undefined

Map数据结构的属性和方法

size 属性
size 属性返回 Map 结构的成员总数
const map = new Map();
map.set('itbaizhan', true);
map.set('sxt', false);
map.size // 2
set()
set 方法设置键名 key 对应的键值为 value ,然后返回整个 Map 结构。
如果 key 已经有值,则键值会被更新,否则就新生成该键
const map = new Map();
map.set('itbaizhan', true);
map.set('sxt', false);
console.log(map);
set 方法返回的是当前的 Map 对象,因此可以采用链式写法
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map);
get(key)
get 方法读取 key 对应的键值,如果找不到 key ,返回 undefined
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map.get("itbaizhan"));
console.log(map.get("it"));
has(key)
has 方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map.has("itbaizhan"));
delete(key)
delete 方法删除某个键,返回 true 。如果删除失败,返回 false
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
map.delete("itbaizhan")
console.log(map);
clear()
clear 方法清除所有成员,没有返回值
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
map.clear()
console.log(map);
遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法
  • Map.prototype.keys() :返回键名的遍历器。
  • Map.prototype.values() :返回键值的遍历器。
  • Map.prototype.entries() :返回所有成员的遍历器。
  • Map.prototype.forEach() :遍历 Map 的所有成员。
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
for (let key of map.keys()) {
    console.log(key);
}  
for (let value of map.values()) {
    console.log(value);
}
for (let item of map.entries()) {
    console.log(item[0], item[1]);
}
map.forEach(function (value, key, map) {
    console.log(key,value);
});

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“ 元编程 meta programming ),即对编程语言进行编程
Proxy 可以理解成,在目标对象之前架设一层 拦截 ,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理,用在这里表示由它来“ 代理 某些操作,可以译为 代理器
 
var user = {};
 var obj = new Proxy(user, {
    get: function (target, propKey) {
        return "不给你看" 
   },
    set: function (target, propKey, value) {
        if (propKey === "age") {
            if (!Number.isInteger(value)) {
                throw new TypeError('The age is not an integer');
           }
            if (value > 200) {
                throw new RangeError('The age seems invalid');
           }
       }
        target[propKey] = value;
   }
 })
 obj.age = 20
 obj.name = "iwen"
 console.log(obj.age);//不给你看

Reflect

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API,一般与 Proxy 配合完成更多的功能
检测一个对象是否存在特定属性
const duck = {
  name: 'Maurice',
  color: 'white',
  greeting: function() {
    console.log(`Quaaaack! My name is
${this.name}`);
 }
}
Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
返回这个对象自身的属性
const duck = {
    name: 'Maurice',
    color: 'white',
    greeting: function () {
        console.log(`Quaaaack! My name is
${this.name}`);
   }
}
console.log(Reflect.ownKeys(duck));//['name', 'color', 'greeting']
为这个对象添加一个新的属性
const duck = {
    name: 'Maurice',
    color: 'white',
    greeting: function () {
        console.log(`Quaaaack! My name is ${this.name}`);
   }
}
Reflect.set(duck, 'eyes', 'black');
console.log(duck);//{name: 'Maurice', color: 'white', eyes: 'black', greeting: ƒ}
Proxy 配合完成更多的功能
var user = {}
var obj = new Proxy(user, {
    set: function (target, name, value) {
        var success = Reflect.set(target,name, value);
        if (success) {
            console.log('property ' + name +' on ' + target + ' set to ' + value);
       }
        return success;
   }
});
obj.name = "iwen"
console.log(obj.name);
//property name on [object Object] set to iwen
//iwen

同步与异步

定义:同步和异步关注的是消息通信机制(可以理解为数据的读取方式)。 同步 ,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。 异步 ,和同步相反调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回调函数来处理这个调用
举例:你去商城买东西,你看上了一款手机,能和店家说你一个这款
手机,他就去仓库拿货,你得在店里等着,不能离开,这叫做
同步。现在你买手机直接去京东下单,下单完成后你就可用做
其他时间(追剧、打王者、 lol )等货到了去签收就 ok . 这就叫
异步
同步代码表现
for (var i = 0; i < 10000; i++) {
    if (i == 9999){
        console.log("循环结束了~~")
   }
}
console.log("ok")
// 循环结束了~~
// ok
for 循环就是同步编程的 , 只有循环结束后 , 才会继续执行下面的代码
while(1){
}
console.log("ok")
永远都不会执行 console.log("ok") , 因为上面的循环是死循环 , 永远都不会结束
异步代码表现
var n = 0;
setTimeout(function () {
    n++;
    console.log(n);
}, 1000);
console.log(n);
// 0
// 1
最常见的异步代码



    
    
    
    Document
    


    
    

Promise 对象

基本概念
Promise 是异步编程的一种解决方案,比传统的解决方案 —— 回调函数和事件—— 更合理和更强大。它由社区最早提出和实现, ES6将其写进了语言标准,统一了用法,原生提供了 Promise 对象
所谓 Promise ,简单说就是一个容器,里面保存着某个未来才会结束 的事件(通常是一个异步操作)的结果。从语法上说,Promise 是 一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理 有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外, Promise 对象提供统一的口,
使得控制异步操作更加容易
基本用法
ES6 规定, Promise 对象是一个构造函数,用来生成 Promise 实例
Promise 是异步编程的一种解决方案,可以将异步操作以同步操 作的流程表达出来
const promise = new Promise(function(resolve,
reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
 } else {
    reject(error);
 }
});
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己 部署 Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
加载图片资源例子




    
    
    Document
    



    
Promise 对象 _Ajax 实操
Promise 封装 Ajax ,让网络请求的异步操作变得更简单
 

Promise对象_方法

then()
Promise 实例具有 then 方法,也就是说, then 方法是定义在原型对象 Promise.prototype 上的。它的作用是为 Promise 实例添加状态改变时的回调函数 then 方法返回的是一个新的 Promise 实例。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法
第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
},function (err){
  console.log("rejected: ", err);
});
catch()
catch() 方法是 .then(null, rejection) .then(undefined, rejection) 的别名,用于指定发
生错误时的回调函数
getJSON("http://iwenwiki.com/api/blueberrypa
i/getChengpinDetails.php").then(
    data =>{
        console.log(data)
        throw new Error('test');
   },
    error =>{
        console.log("error"+error);
   }
).catch(err =>{
    console.log("catch"+err);
})
finally()
finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的
getJSON("http://iwenwiki.com/api/blueberryp
ai/getChengpinDetails.php").then(
     data =>{
         console.log(data)
     },
     error =>{
         console.log("error"+error);
     }
 ).catch(err =>{
     console.log("catch"+err);
 }).finally(() =>{
     console.log("成功失败都会执行");
 })
all()
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise实例优点就是多个promise 都有结果,才会触发 then 或者 catch
var url1 =
"http://iwenwiki.com/api/musicimg/1.png"
var url2 =
"http://iwenwiki.com/api/musicimg/2.png"
var promise1 = new Promise(function
(resolve, reject) {
    var image = new Image();
    image.src = url1;
    image.onload = function () {
        resolve(image)
  }
    image.onerror = function () {
        reject(new Error('Could not load image at ' + url1))
   }
 })
 var promise2 = new Promise(function
(resolve, reject) {
    var image = new Image();
    image.src = url2;
    image.onload = function () {
        resolve(image)
   }
    image.onerror = function () {
        reject(new Error('Could not load image at ' + url2))
   }
 })

 Promise.all([promise1,promise2]).then(data =>{
    console.log(data);
 }).catch(error =>{
    console.log(error);
 })
Promise 对象的异步应用
Promise 对异步处理的回调地狱的改善是非常明显的
原始写法
$.getJSON("http://localhost/generator/list.php",function(data){
          
    $.getJSON("http://iwenwiki.com/api/generator/id.php",{id:data[0]},function(data){
              
    $.getJSON("http://iwenwiki.com/api/generator/name.php",{name:data.name},function(data){
                   console.log(data);
               })
           })
       })
Promise 全新写法
function getIds(){
    return new Promise(function(resolve,reject){
    $.getJSON("http://iwenwiki.com/generator/list.php",function(data){
            resolve(data)
       },function(error){
            reject(error)
       })
   })
}
    getIds().then(data =>{
    return new Promise((resolve,reject) =>{ 
    $.getJSON("http://iwenwiki.com/api/generator/id.php", { id: data[0] },function(data){
            resolve(data)
       },function(error){
            reject(error)
       })
   })
   }).then(data =>{
    return new Promise((resolve,reject) =>{
      $.getJSON("http://iwenwiki.com/api/generator/name.php", { name: data.name
   },function(data){
            resolve(data)
       },function(error){
            reject(error)
       })
   })
}).then(data =>{
    console.log(data);
})




    
    
    Document
    



    
    


Generator 函数的语法

基本用法
Generator 是新的异步网络请求解决方案
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
Generator 函数有多种理解角度。语法上,首先可以把它理解成,
Generator 函数是一个状态机,封装了多个内部状态。 形式上,Generator 函数是一个普通函数,但是有两个特征。
一 是, function 关键字与函数名之间有一个星号;
二是,函数体内部使 用 yield 表达式,定义不同的内部状态( yield 在英语里的意思就是 产出”
由于 Generator 函数返回的遍历器对象,只有调用 next 方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。 yield 表达式就是暂停标志。
ES6 没有规定, function 关键字与函数名之间的星号,写在哪个位
置。这导致下面的写法都能通过
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
for...of 循环
for...of 循环可以自动遍历 Generator 函数,且此时不再需要调用 next 方法。
 function* foo() {
            yield 1
            yield 2
            yield 3
            yield 4
            yield 5
            return 6
        }

        for (let i of foo()) {
            console.log(i); // 1 2 3 4 5 没有6
        }
Generator 函数的异步应用
Generator 对异步处理的回调地狱的改善也是非常明显的

Async 函数

Async Generator 新的语法糖
ES2017 标准引入了 async 函数,使得异步操作变得更加方便
async 函数是什么?一句话,它就是 Generator 函数的语法糖
基本语法
function print(){
    setTimeout(() =>{
        console.log("定时器");
   },1000)
    console.log("Hello");
}
print()

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
 });
}
async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}
asyncPrint('hello world', 50);
异步应用




    
    
    
    Document
    




    



Fetch API

fetch() XMLHttpRequest 的升级版,用于在 JavaScript 脚本里面发出 HTTP 请求
浏览器原生提供这个对象
fetch() 的功能与 XMLHttpRequest 基本相同,但有三个主要的差异
  1. fetch() 使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁
  2. fetch() 采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 象),更合理一些;相比之下,XMLHttpRequest API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码
  3. fetch() 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来
基本用法
fetch() 接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象
fetch(url)
 .then(...)
 .catch(...)
完整网络请求
fetch('http://iwenwiki.com/api/blueberrypai/g
etChengpinDetails.php')
   .then(response => response.json())
   .then(json => console.log(json))
   .catch(err => console.log('Request
Failed', err));
支持的请求参数
 // fetch默认是get请求方式     post请求方式
        fetch("http://iwenwiki.com/api/blueberrypai/login.php", {
            method: "POST",
            headers: {
                "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
            },
            // post请求的参数
            // 接受的参数类型其实和后台也有关系,但是大部分情况下是字符串形式
            body: "[email protected]&password=iwen123&verification_code=crfvw"
        }).then(res => {
            return res.json();
        }).then(data => {
            console.log(data);
        }).catch(error => {
            console.log(error);
        })




    
    
    
    Document




    



Fetch API POST请求注意事项

Fetch POST 请求参数是需要注意,他需要的是字符串格式,但是为了使用方便,我们需要传递对象格式那我们需要将对象格式转换为字符串格式

Fetch网络请求应用

fetch promise 配合完成网络请求,更加美观,代码更加易读

封装Fetch网络请求

index.html


    
    

fetch.js


function formator(data){
    var dataStr = ""
    Object.keys(data).forEach(key => {
        dataStr += key + '=' + data[key] + '&';
    })
    if (dataStr !== '') {
        dataStr = dataStr.substring(0, dataStr.length - 1);
    }
    return dataStr
}



async function ajax(url = "",type = "GET",data = {}){
    // GET请求  url?username=iwen&password=123
    if(type === "GET"){
        var dataStr = formator(data);
        url = url +"?"+ dataStr;
    }

    let requestConfig = {
        method:type,
        headers: {
            "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
        }
    }

    // POST请求
    if(type === "POST"){
        requestConfig.body = formator(data);
        // 拦截对象,为对象增加新的属性
        // 在ES6中,我们不推荐直接为一个对象增加新属性的时候使用赋值方式
        // Object.defineProperty(requestConfig,"body",{
        //     value:formator(data)
        // })
    }

    let response = await fetch(url,requestConfig)
    response = await response.json();
    return response;
}

Class 的基本语法

类的由来

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

ES6 的类,完全可以看作构造函数的另一种写法。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

class Foo {
  constructor() {
    return Object.create(null);
  }
}

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'

类的实例

生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);
//定义类
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

私有方法和私有属性

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。

一种做法是在命名上加以区别

class Widget {

  // 公有方法
  foo (baz) {
    this._bar(baz);
  }

  // 私有方法
  _bar(baz) {
    return this.snaf = baz;
  }

  // ...
}

Class 的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多

class Point {
}

class ColorPoint extends Point {
}

上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

class Point { /* ... */ }

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError

上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

Module 的语法

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

// ES6模块
import { stat, exists, readFile } from 'fs';

export 命令

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

export的写法,除了像上面这样,还有另外一种。

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

export命令除了输出变量,还可以输出函数或类(class)。

export function multiply(x, y) {
  return x * y;
};

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

下面是一个circle.js文件,它输出两个方法area和circumference

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

现在,加载这个模块

// main.js

import { area, circumference } from './circle';

console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));

上面写法是逐一指定要加载的方法,整体加载的写法如下

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

export default 命令

从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}

上面代码是一个模块文件export-default.js,它的默认输出是一个函数。

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

你可能感兴趣的:(JavaScript,javascript,es6)