探索flutter框架开发的app在移动应用市场的潜力与挑战
1006
2022-11-25
JS与ES6高级编程学习笔记(五)——ECMAScript6 代码组织
一、概述
ES6不仅在语法上有很大的改进,在代码的组织结构上也有重大升级,ES6中新增加了像Set、WeakSet、Map、WeakMap、ArrayBuffer、TypedArray和DataView等数组结构;原生的模块化解决了复用、依赖、冲突、代码组织混乱的问题,让开发复杂的前端项目变得更加容易;类(class)的加入使JavaScript面向对象更加易于理解。
ES6除了弥补了传统语言特性的不足外,在许多方面也增强了JavaScript动态语言的特性,可以说是扬长避短。在元编程中增加了Reflect反射对象与Proxy代理构造器,元编程是对编程语言进行编程,元编程的目标使代码更具描述性、拥有更强的表现力和灵活性。异步流程控制可以更加优雅、方便的编写异步程序,给用户带来更好的体验与性能。
二、集合
ES6中新增加了多种数据结构,Set可以存放任意不重复的值,Map弥补了对象类型存放key-value对的不足,而WeakSet与WeakMap则解决了Set与Map在GC回收垃圾时存在内存泄漏的风险, ArrayBuffer、TypedArray和DataView的引入是为了更加方便操作底层二进制数据的视图。
2.1、Set
在ES6中新增加了Set这种数据结构,通常称为集合,Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用,Set中的成员不允许重复。
//创建一个Set对象,使用数组初始化集合,注意2重复了
let numbers=new Set([1,2,2,3,4,5]);
//向Set中添加成员
numbers.add(5);
numbers.add(6);
//输出集合的大小
console.log("size:"+numbers.size);
//遍历集合
for(let n of numbers){
console.log(n);
}
输出结果如图5-1所示:
图5-1 Set示例输出结果
示例中共添加了8个元素,但size的值为6是因为有两个重复的元素。这里使用数组初始了一个新的Set对象,也可以是实现了iterable 接口的其他数据结构,当然如果不指定此参数或其值为null,则新的Set为空。
(1)、Set对象的常用操作
size属性:返回Set对象的值的个数,属性的默认值为0。
add(value)方法:在Set对象尾部添加一个元素。返回该Set对象。
has(value)方法:返回一个布尔值,表示该值在Set中存在与否。
delete(value)方法:移除Set的中与这个值相等的元素,返回has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。has(value)在此后会返回false。
clear()方法:移除Set对象内的所有元素。
//创建一个空的Set对象
var numbers=new Set();
//添加
numbers.add("hello");
numbers.add("hello");
numbers.add({name:"tom"});
numbers.add({name:"tom"}); //注意对象总是不重复的
//输出Set的元素个数
console.log("size:"+numbers.size);
//测试元素是否存在
console.log("hello在集合中吗?"+numbers.has("hello"));
console.log("对象{name:\"tom\"}在集合中吗?"+numbers.has({name:"tom"}));
//删除元素
numbers.delete("hello");
numbers.delete({name:"tom"});
//输出Set的元素个数
console.log("删除后 size:"+numbers.size);
//清空元素
numbers.clear();
console.log("清空后 size:"+numbers.size);
//创建一个set对象,初始化特殊的重复对象
let set=new Set([NaN,NaN,undefined,undefined,null,null,{},{}]);
//使用...运算展开(spread)集合
var array=[...set];
console.log(array);
输出结果如图5-2所示:
图5-2 Set示例输出结果
示例中需要特别注意的是因为Set中的值总是唯一的,所以需要判断两个值是否相等,可以参考===操作符的使用;NaN与NaN相等,undefined与undefined相等;对象(含空对象)总是不相等的。
(2)、Set对象的遍历
keys()方法:返回键名的遍历器
values()方法:返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
entries():返回键值对的遍历器
forEach(callbackFn[,thisArg])方法:按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。
var numbers=new Set([1,2,3]);
//遍历所有的键
for(let n of numbers.keys()){
console.log(n); //输出1,2,3
}
//遍历所有的值
for(let n of numbers.values()){
console.log(n); //输出1,2,3
}
//遍历所有的键值对
for(let obj of numbers.entries()){
console.log(obj); //[1, 1] [2, 2] [3, 3]
}
//调用对象的forEach方法
numbers.forEach((value,key)=>console.log(value,key)); //输出1 1 2 2 3 3
//给回调函数指定参数
numbers.forEach(function(n){
console.log(n+this); //输出101 102 103
},100);
输出结果如图5-3所示:
图5-3 Set示例输出结果
从输出结果可以看出因为Set对象并没有区分键与值所以输出的结果是相同的,另外需要注意的是forEach中的回调函数带参数时不能使用箭头函数,因为此时箭头函数的this指向Window对象。
(3)、Set的使用技巧
使用Set可以方便的处理数组中的数据去重复、对多个数组进行集合运算操作:
//1、去除数组中的重复元素
var array=[1,1,2,2,3,"3","3","4","5"];
//定义Set对象,清除重复元素
var set=new Set(array);
//将set展开获得元素唯一的数组
var unique=[...set];
console.log(unique);
var x=new Set([100,200,300]);
var y=new Set([300,400,500]);
//2、并集(合并去重)
var set1=new Set([...x,...y]);
console.log(...set1.values());
//3、交集(共有元素)
var set2=new Set([...x].filter(n=>y.has(n)));
console.log(...set2.values());
//4、补集(x中存在而y中不存在的元素)
var set3=new Set([...x].filter(n=>!y.has(n)));
console.log(...set3.values());
输出结果如图5-4所示:
图5-4 Set示例输出结果
filter是Array对象中的一个过滤方法,语法如下:
var newArray = array.filter(callback(element[,index[,array]])[,thisArg])
callback:筛选数组中每个元素的函数。返回true表示该元素保留,false则不保留。
element:数组中当前正在处理的元素。
index可选参数,正在处理的元素在数组中的索引。
array可选参数,数组本身。
2.2、WeakSet
ES6中新增加的WeakSet对象的作用是可以将弱引用对象保存在集合中,该对象的使用方法与Set基本一样,但有如下几点不同:
(1)、WeakSet只允许添加对象类型,不允许添加原生类型值,因为没有引用,而Set都可以。
(2)、WeakSet对象中存储的对象值都是被弱引用的,如果没有其他的变量或属性引用这个对象值,则这个对象值会被当成垃圾回收掉.正因为这样,WeakSet对象是无法被枚举的,没有办法拿到它包含的所有元素,而Set则不然。
(3)、WeakSet比Set更适合(和执行)跟踪对象引用,尤其是在涉及大量对象时,可以避免一些性能问题,如内存泄漏。
下面是Stack Overflow中的一段脚本,可以用于更好的理解WeakSet:
const requests = new WeakSet(); class ApiRequest { constructor() { requests.add(this); } makeRequest() { if (!request.has(this)) throw new Error("Invalid access"); // do work } }
从上面的代码中可以看出这里集合并不想控制对象的生命周期但又需要判断对象是否存在使用WeakSet比Set要更加合适。
用于存储DOM节点,而不用担心这些节点从文档移除时会引发内存泄露,即可以用来避免内存泄露的情况。
const foos = new WeakSet() class Foo { constructor() { foos.add(this) } method() { if(!foos.has(this)) { throw new TypeError("Foo.prototype..method 只能在Foo的实例上调用") } }}// 这段代码的作用是保证了Foo 的实例方法只能在Foo的实例上调用。// 这里用WeakSet的好处:数组foos对实例的引用不会被计入内存回收机制,所以删除实例时无需考虑foos, 也不会出现内存泄露
2.3、Map
键值对集合是非常常用的散列数据结构(Hash),ES6之前常常使用Object当作键值对集合使用,但Object只能是String与Symbol作为键,而ES6中新增加的Map的键可以是任意值,包括函数、对象或任意基本类型;Map中的key是有序的。
//定义用户对象
var jack={name:"jack"};
var mark={name:"mark"};
//定义一个Object字面量对象,当着Key-value集合使用
var objectMap={};
objectMap[jack]=jack.name; //向对象中添加元素,使用对象作为key
objectMap[mark]=mark.name;
console.log(objectMap[jack],objectMap[mark]);
console.log(objectMap["[object Object]"]);
输出结果如图5-5所示:
图5-5 Object作Map使用示例输出结果
当使用对象类型作为键向对象中添加成员时会自动转换为字符串,这里的jack与mark都转换成了" [object Object]",所以看到的输出结果都是mark,这并没有达到我们的预期,使用Map可以做到。
//定义用户对象
var jack={name:"jack"};
var mark={name:"mark"};
//创建Map对象
var map=new Map();
//向集合中添加key为jack对象,值为字符类型的key-value对
map.set(jack,jack.name);
map.set(mark,mark.name);
console.log(map.get(jack),map.get(mark));
输出结果如图5-6所示:
图5-6 Map示例输出结果
(1)、Map对象的常用操作
set(key,value)方法:向Map对象中设置键为key的值。
size属性:获得Map对象的键值对总数。
get(key)方法:获取键对应的值,如果不存在,则获取undefined。
has(key)方法:获取一个布尔值,表示Map实例是否包含键对应的值。
delete(key)方法:根据key删除集合中的对象,成功删除返回true,否则返回false。
clear()方法:移除Map对象的所有键/值对。
//定义一个空的Map对象
let users=new Map();
//设置成员
users.set("mark",{name:"mark",height:195});
//添加键为jack,值为{name:"jack",height:173}对象
users.set("jack",{name:"jack",height:173});
users.set("rose",{name:"玫瑰",height:188});
users.set("rose",{name:"rose",height:168}); //重复添加key为rose的对象
//获得成员个数
console.log("size:"+users.size);
//获取成员
console.log(users.get("rose"));
console.log(users.get("tom"));
//删除对象
users.delete("jack"); //返回true
users.delete("jack"); //返回false
//判断成员是否存在
console.log("jack是否存在:"+users.has("jack"));
//删除所有成员
users.clear();
console.log("size:"+users.size);
输出结果如图5-7所示:
图5-7 Map示例输出结果
示例中有几处需要注意的地方:重复添加key为rose的对象会覆盖原有对象,类似修改;删除成功时回返回true,如果key不存在则删除失败,返回false。
(2)、Map对象的遍历
keys()方法:获取迭代(Iterator)对象,含每个元素的key的数组。
values()方法:获取迭代(Iterator)对象,含每个元素的value的数组。
entries()方法:获取迭代(Iterator)对象,含每个元素的 [key, value] 数组。
forEach(callbackFn[, thisArg])方法:遍历集合,如果为forEach提供了thisArg,它将在每次回调中作为this值。
//定义一个Map对象,使用数组初始化
let users=new Map([
["mark",{name:"mark",height:195}],
["jack",{name:"jack",height:173}],
["rose",{name:"rose",height:168}]
]);
//1、使用for同时获取键与值,等同于users.entries()
for(let [key,value] of users){
console.log(key,value);
}
//2、获取所有的键
for(var key of users.keys()){
console.log(key);
}
//3、获取所有的值
for(var value of users.values()){
console.log(value);
}
//4、获取所有的键值对,等同于直接users
for(var entity of users.entries()){
console.log(entity[0],entity[1]);
}
//5、使用forEach遍历,注意value与key的顺序
users.forEach((value,key)=>console.log(value,key));
//带参数
users.forEach(function (v,k) {
console.log(this+k,v);
},'user:');
输出结果如图5-8所示:
图5-8 Map示例输出结果
(3)、Map与其它对象的转换
Map可以与数组、对象、JSON等其它类型进行相互转换,部分转换示例如下:
//1、数组转map
var array1=[[1,'a'],[2,'b']];
var map1=new Map(array1);
console.log(map1);
//2、map转数组
var array21=[...map1.entries()]; //[[1,'a'],[2,'b']];
var array22=[...map1]; //[[1,'a'],[2,'b']];
var array23=[...map1.values()]; //['a','b'];
console.log(array21,array22,array23);
//3、对象转成map
var user={name:"mark",height:195};
console.log(Object.entries(user)); //[["name", "mark"],["height", 195]]
var map3=new Map(Object.entries(user));
输出结果如图5-9所示:
图5-9 Map示例输出结果
注意展开运算符"…"的使用,Object.entries()的作用是获取对象自身可枚举属性的键值对数组。
2.4、WeakMap
ES6中新增加的WeakMap与WeakSet类似也是一个弱引用的数据结构,使用方法也与Map基本相同但两者的区别主要是内存分配与回收。
Map可能会导致内存泄漏因为Map内部数组会一直引用着每个键和值(强引用),如果在使用Map时只想引用对象而不想管理其生命周期则可以考虑使用WeakMap,注意只有key是弱引用。
var div1=document.querySelector("#div1");
var div2=document.querySelector("#div2");
let elements=[
[div1,"文章管理"],
[div2,"商品管理"]
];
var map=new Map(elements);
示例中Map使用div1与div2作为key,map对这两个对象是强引用的,如果不再需要使用则需要手动释放,否则可能会引起内存泄漏。
elements[0]=null;
elements[1]=null;
当然如果将上面的代码修改为WeakMap则不需要手动来管理对象的释放了。
WeakMap只接受对象作为键名,不支持clear方法,不支持遍历,也就没有了keys、values、entries、forEach这4个方法,也没有属性size;WeakMap 键名中的引用类型是弱引使用,假如这个引使用类型的值被垃圾机制回收了,WeakMap实例中的对应键值对也会消失;WeakMap中的key不计入垃圾回收,即若只有WeakMap中的key对某个对象有引用,那么此时执行垃圾回收时就会回收该对象。
WeakMap对象只允许使用对象作为key而Map可以是任意类型。而这些作为键的对象是弱引用的,值非弱引用,如果作为key的对象被GC回收则WeakMap中对应的对象也将被删除,因为不能确保key是否存在,所以key不可以枚举。
在我们的开发过程中,如果我们想要让垃圾回收器回收某一对象,就将对象的引用直接设置为 null
var a = {}; // {} 可访问,a 是其引用a = null; // 引用设置为 null// {} 将会被从内存里清理出去
但如果一个对象被多次引用时,例如作为另一对象的键、值或子元素时,将该对象引用设置为 null 时,该对象是不会被回收的,依然存在
var a = {};var arr = [a];a = null;console.log(arr) // [{}]
如果作为 Map 的键:
var a = {};var map = new Map();map.set(a, 'hello map')a = null;console.log(map.keys()) // MapIterator {{}}console.log(map.values()) // MapIterator {"hello map"}
如果想让 a 置为 null 时,该对象被回收,该怎么做?
ES6 考虑到了这一点,推出了: WeakMap 。它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)。
Map 相对于 WeakMap :
Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的Map 可以被遍历, WeakMap 不能被遍历
下面以 WeakMap 为例,看看它是怎么上面问题的:
var a = {};var map = new WeakMap();map.set(a, 'hello map')map.get(a)a = null;
2.5、ArrayBuffer、TypedArray和DataView
ES6中引入了ArrayBuffer、TypedArray和DataView,方便操作底层二进制数据的视图,如在Canvas、Fetch API、File API、WebSockets、XMLHttpRequest等对象的API操作中会使用到。
(1)ArrayBuffer操作内存中的一段原始二进制数据。
(2)TypedArray共有 9 种类型的视图:
Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
基本构成是"类型+位数+Array",U表示无符号,如Uint16Array()表示无符16位整数视图。用来读写简单类型的二进制数据。
(3)DataView可以自定义复合格式的视图,用来读写复杂类型的二进制数据。
new DataView(buffer [,byteOffset[,byteLength]])
buffer:一个已经存在的ArrayBuffer或SharedArrayBuffer对象,DataView对象的数据源。
byteOffset:第一个字节在 buffer 中的字节偏移,默认从第1个字节开始。
byteLength:此DataView对象的字节长度。
//1、定义一个长度为16个字节的buffer
var buffer=new ArrayBuffer(16);
console.log(buffer,buffer.byteLength);
//2、定义可以存放2个16位的整型数据视图
var int16 = new Int16Array(2);
int16[0] = 13;
console.log(int16[0]); // 13
//3、定义一个DataView,从第10个字节开始的3个长度
var dataview1=new DataView(buffer,10,3);
console.log(dataview1,dataview1.byteLength);
输出结果如图5-10所示:
图5-10 二进制数组示例输出结果
2.6、Iterator 迭代器 -
Iterator(迭代器)是一个接口,实现该接口的对象拥有可迭代的功能,迭代器对象可以通过重复调用next()方法迭代。常见可迭代的内置对象有Array、String、Map、Set、TypedArray、Generator等,使用for…of循环可以直接迭代一个符合规范的iterator迭代器。
获得内置对象的迭代器对象。
var greeting="Hi World!";
for(let c of greeting){
console.log(c);
}
//获取字符串对象上的迭代器对象
var itor=greeting[Symbol.iterator]();
console.log(itor.next()); //输出:{value: "H", done: false}
console.log(itor.next()); //输出:{value: "i", done: false}
输出结果如图5-18所示:
图5-18 Iterator示例输出结果
当然,除了可以获取内置对象的迭代器之外也可以自定义迭代器,自定义迭代器需要遵循接口约束。
var MyIterator = {
content: "MyIterator",
[Symbol.iterator]() {
const that = this;
let currentIndex = 0;
return {
next() {
if (currentIndex < that.content.length) {
return {done: false, value: that.content[currentIndex++]}
} else {
return {done: true, value: undefined}
}
}
}
}
}
for(let c of MyIterator){
console.log(c); //输出:MyIterator
}
方法[Symbol.iterator]()在被调用时返回带有next()和return()方法的迭代器对象。
三、模块(module)
3.1、第一个模块
3.2、ES6中模块的特点
3.3、export导出
3.4、import导入
3.5、默认导出与导入
每个模块允许默认导出一个成员,导入时可以自定义对象名称,而不需要使用者过多关注导入模块的细节,解决了命名对象导出时使用该模块必须清楚的知道每个导出成员的名称的问题,简单说默认导出使模块的使用更加方便。//定义math对象let math={add(m,n){ //加法方法console.log(m+'+'+n+'=',m+n);},sub(m,n){ //减法方法console.log(m+'-'+n+'=',m-n);}};//默认导出math对象export default math;导入上面定义的模块://导入module10模块,注意这里没有使用{}import calculator from './js/module10.js';//调用calculator对象中的方法calculator.add(200,100); //输出:200+100= 300calculator.sub(200,100); //输出:200-100= 100默认导出允许使用匿名对象、匿名函数或匿名变量。//匿名对象export default {price:100};//匿名函数export default function () {}//匿名变量export default 900;默认导出可以与命名导出混合使用。export let math={};export var i=100;var j=200;var k=300;//j作为默认导出成员,k为命名导出成员export {j as default,k};导入时同样可以将命名与默认成员混合导入。//导出模块名的成员,默认导出成员重命名为jimport {default as j,i,k} from './js/module12.js';console.log(j,i,k); //输出:200 100 300导入其它模块时允许将导入的内容再次导出。//导入模块module12的成员,重命名后导出export {i as n1,k} from './js/module12.js';//导入模块module12的所有成员并重新导出export * from './js/module12.js';通过上面的方法可以实现模块间的"继承"。
四、类(class)
面向对象编程中class是非常重要的,如果你熟悉像Java、C#、C++这样的面向对象编程语言,你想用其中的面向对象思维来理解JavaScript是非常难的,因为JavaScript并非真正的面向对象语言,所以这给开发者带来了较大的障碍,ES6中增加了类(class),这样可以让JavaScript更加接近传统面向对象语言。
4.1、第一个类
假定我们现在要定义一个"形状(方、圆、五角形…)"类,该类拥有"颜色"属性,与"显示"颜色的方法。
传统定义如下:
//定义形状类(构造器)
function Shape(color) {
this.color=color;
}
//在构造器的原型对象中添加show方法
Shape.prototype.show=function () {
console.log("形状的颜色:"+this.color);
}
//创建对象,并调用show方法
var shape=new Shape("蓝色");
shape.show();
控制台输出结果:形状的颜色:蓝色
ES6定义如下:
//定义Shape类
class Shape{
//带参构造函数
constructor(color){
this.color=color;
}
//show方法
show(){
console.log("形状的颜色:"+this.color);
}
}
//创建对象,并调用show方法
let shape=new Shape("蓝色");
shape.show();
控制台输出结果:形状的颜色:蓝色
可以看出输出结果是完全一样的,但ES6定义类的方法明显更加接近传统OOP的方式。
4.2、ES6中类的特点
(1)、class只是语法糖,class定义的类本质还是一个构造函数,但这种写法更加清晰,更加接近经典面向对象的写法。
(2)、类的所有实例方法定义在类的prototype属性中,类中定义的方法默认为原型中所有对象共享的方法,但ES5中定义在构造器中的方法属于对象或构造器,如图5-13所示:
图5-13 ES6 class示例输出结果
(5)、在类中定义的方法不可枚举。
(6)、类默认都拥有Constructor内部方法。
4.3、字段
类中可以定义多种成员,包含字段、构造方法、属性、公共实例方法、静态方法。
//定义Shape类
class Shape{
//公有实例字段
size=0;
name={};
width;
}
let rect1=new Shape();
rect1.width=100;
let rect2=new Shape();
console.log(rect1.name,rect1.size,rect1.width); //输出:{} 0 100
console.log(rect2.name,rect2.size,rect2.width); //输出:{} 0 undefined
console.log(rect1.name===rect2.name); //输出:false
class Shape{
//定义静态字段
static width=100;
}
let s1=new Shape();
console.log(s1.width); //输出:undefined
console.log(Shape.width); //输出:100
因为静态字段属于类,访问时只能用类名访问,所以s1中并没有width字段而需要使用Shape访问。静态字段可用于存放缓存数据、固定结构数据或者其他你不想在所有实例都复制一份的数据。
4.4、方法
方法也可以分为实例方法、静态方法与构造方法。实例方法属于实例,通过实例名访问;静态方法通过类名访问;在实例方法中可以通过类名访问静态字段,但是在静态方法中不能直接通过this访问实例成员。
class Shape {
//实例字段
width="100";
//静态字段
static PI=3.14;
//实例方法
getWidth(){
console.log("宽:"+this.width);
}
//静态方法
static getPI(){
console.log("PI:"+Shape.PI);
}
}
var shape=new Shape();
shape.getWidth(); //输出:宽:100
Shape.getPI(); //输出:PI:3.14
构造方法是通过new关键字创建对象时调用的特殊方法,ES6中class的构造方法具有如下特性:
(1)、方法名为constructor,这与经典的面向对象为类名的区别较大;
(2)、每个类都有一个默认的空构造方法;
(3)、构造方法默认会返回this,不建议指定返回对象;
(4)、一个类只能定义一个构造方法,没有重载;
class Shape {
constructor(width){ //构造方法
this.width=width;
}
}
4.5、属性
class Shape {
//获取宽度
get width(){
return this._width;
}
//设置宽度
set width(value){
//约束属性值
if(value>=0) {
this._width = value;
}else{
throw "宽度必须大于等于0";
}
}
}
let shape=new Shape();
shape.width=100; //设置正确的值
console.log(shape.width); //获取值
shape.width=-100; //设置不合理的值
输出结果如图5-14所示:
图5-14 ES6 class示例输出结果
结合闭包和Symbol实现私有属性:
var Shape=function(){ let name=Symbol("name"); class Shape{ constructor(n){ this[name]=n; } sayName(){ console.log(this[name]); } } return Shape; }(); let shape=new Shape("正方形"); shape.sayName(); console.log(shape[Symbol("name")]);
运行结果:
可见name是只读的,外部通过实例访问不到。
升级版:
//使用IIFE+闭包+class+Symbol实现私有成员 var Shape=function(){ let widthname=Symbol("width"); class Shape{ constructor(w){ this[widthname]=w; //使用Symbol作为属性名,定义宽度 } get width(){ return this[widthname]; } set width(value){ if(value>0){ this[widthname]=value; }else{ throw "宽度必须大于0"; } } } return Shape; }(); let s1=new Shape(); s1.width=100; console.log(s1.width); s1.width=-100;
View Code
使用#号实现私有成员:
class Shape{ #a; constructor(color,size){ this.color=color; this.#a=size; } getSize(){ console.log(this.#a); } } let shape=new Shape("red",100); shape.getSize(); //Private field '#a' must be declared in an enclosing class console.log(shape.#a);
运行结果:
完整示例:
View Code
4.6、继承
(1)、extends与super。继承是面向对象最重要的特性之一,ES5中的继承相对麻烦,在ES6中使用关键字extends可以很方便的实现类之间的继承,但本质上还是基于原型链实现的。通过super可以访问父类成员。
class Shape{ constructor(width){ this.width=width; } draw(){ console.log("宽:"+this.width); } } class Circle extends Shape{ constructor(width,height){ super(width); this.height=height; } draw(){ super.draw(); console.log("高:"+this.height); } } let circle=new Circle(100,200); circle.draw(); console.log(circle instanceof Shape);
输出结果如图5-15所示:
图5-15 ES6 class示例输出结果
从输出结果可以看出circle对象是Shape、Object类型的实例。
(2)、构造方法与this。子类必须调用父类的构造方法,如果不显式调用将自动调用,只有调用super后,才允许用this关键字,否则将出错,因为子类实例是基于父类实例的,子类实例在获得父类实例后再新增自己的方法与属性。super调用父类构造方法时this指向的是子类实例。
class Shape{ constructor(){ this.width=100; } draw(){ console.log("宽:"+this.width); } } class Circle extends Shape{ constructor(width,height){ super.width=width; this.height=height; } draw(){ super.draw(); console.log("高:"+this.height); } }
这样会报错:Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorat new Circle
只有调用super后,才允许用this关键字。在super.width=width;前加上super();就解决问题了。
//形状,父类
class Shape {
constructor(type="形状"){ //构造方法
this.type=type;
console.log("调用父类构造方法");
}
draw(){
console.log("这是一个"+this.type);
}
}
//圆,继承形状
class Circle extends Shape{
constructor(radius){ //子类构造方法
super("圆形"); //调用父类构造函数
this.radius=radius;
}
draw() {
super.draw(); //调用父类中的draw()方法,该方法在原型中
console.log(this.type+"的半径是"+this.radius);
}
}
let circle=new Circle(75); //输出:调用父类构造方法
circle.draw();
输出结果如图5-16所示:
图5-16 ES6 class示例输出结果
在构造函数中定义的属性和方法相当于定义在父类实例上,而不是原型对象上。super作为对象时,在实例方法中,指向父类的原型对象;在静态方法中,指向父类。
(3)、静态成员继承。父类的静态成员也将被子类继承,这可能与经典的面向对象有些区别。
//形状,父类
class Shape {
static width = 100; //静态字段
static show() { //静态方法
console.log("宽度:" + Shape.width);
}
}
//圆,继承形状
class Circle extends Shape {
}
Circle.show(); //输出:宽度:100
console.log(Circle.width); //输出:100
(4)、扩展原生类。使用继承不仅可以扩展自定的类,也可以扩展系统中内置的类型,如:Boolean、Number、String、Array、Date、Function、RegExp、Error、Object等。
//定义类ArrayPro,继承自内置类型Array
class ArrayPro extends Array{
getData(index){ //自定义获得数据的方法
return this[index];
}
get size(){ //自定义属性,获得数组长度
return this.length;
}
get last(){ //自定义属性,获得最后一个元素
return this[this.size-1];
}
}
let arraypro=new ArrayPro(1,2,3,4,5,6);
console.log(arraypro.getData(1)); //输出:2
console.log(arraypro.size); //输出:6
console.log(arraypro.last); //输出:6
当然ES5也可以扩展内置类型,但方法相对复杂且并不支持真正array的性质,ES6可以非常自然的完成内置类型的扩展功能。
五、元编程 -
5.1、Reflect 反射
Reflect是ES6中新增加的一个对象,并非构造器,该对象中含有多个可完成"元编程(对编程语言进行编程)"功能的静态函数,能方便的对对象进行操作,也可以结合Proxy实现拦截功能,共计13个函数,TypeScript定义如下:
apply(target: Function, thisArgument: any, argumentsList: ArrayLike
construct(target: Function, argumentsList: ArrayLike
defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
deleteProperty(target: object, propertyKey: PropertyKey): boolean;
get(target: object, propertyKey: PropertyKey, receiver?: any): any;
getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor | undefined;
getPrototypeOf(target: object): object;
has(target: object, propertyKey: PropertyKey): boolean;
isExtensible(target: object): boolean;
ownKeys(target: object): PropertyKey[];
preventExtensions(target: object): boolean;
set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
setPrototypeOf(target: object, proto: any): boolean;
Reflect 是为了操作对象而提供的新 APIReflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect不是一个函数对象,因此它是不可构造的。所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的
这里演示一下get与set方法的使用:
//要反射的对象
var shape = {
width: 100, height: 200, get area() {
return this.width * this.height;
}
};
//获取shape对象中的width属性值
console.log(Reflect.get(shape,"width")); //输出:100
//设置shape对象中的height属性值为300
Reflect.set(shape,"height",300); //true
console.log(Reflect.get(shape,"area")); //输出:30000
//获取shape对象中area的属性值,area中的this使用指定的对象替代
console.log(Reflect.get(shape,"area",{width:200,height:300}));//输出:60000
这里需要注意的是get与set方法的最后一个参数receiver是可选参数,默认为当前操作对象,如果指定后则this将指向该对象。
1. 取值:Reflect.get()Reflect.get() 方法:获取对象身上某个属性的值(类似于 target[name])/* 参数说明 */1. target: 需要取值的目标对象2. propertyKey: 需要获取的值的键值3. receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。/* 语法 */Reflect.get(target, propertyKey[, receiver])/* 返回值 */1. 如果含有该属性,返回对应属性值2. 如果没有该属性,则返回 undefined/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; },}Reflect.get(myObject, 'foofoo') // undefinedReflect.get(myObject, 'bar') // 2Reflect.get(myObject, 'baz') // 3// Objectvar obj = { x: 1, y: 2 };Reflect.get(obj, "x"); // 1// Arrayvar arr = ["zero", "one"];Reflect.get(arr, 1); // "one"// 带有get处理程序的代理var x = {p: 1};var obj = new Proxy(x, { get(t, k, r) { return k + "bar"; }});Reflect.get(obj, "foo"); // "foobar"// 非对象Reflect.get(1, 'foo') // 报错Reflect.get(false, 'foo') // 报错如果 propertyKey 属性部署了读取函数(getter),则读取函数的this绑定receiver。var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; },};var myReceiverObject = { foo: 4, bar: 4,};Reflect.get(myObject, 'baz', myReceiverObject) // 82. 设值:Reflect.set()Reflect.set():以函数的方式给属性赋值。/* 参数说明 */1. target: 设置属性的目标对象。2. propertyKey: 设置的属性的名称。3. value: 设置的值。4. receiver: 如果遇到 setter,receiver则为setter调用时的this值。/* 语法 */Reflect.set(target, propertyKey, value[, receiver])/* 返回值 */1. 如果设置成功,返回 true2. 如果设置失败,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:var myObject = { foo: 1, set bar(value) { return this.foo = value; },}myObject.foo // 1Reflect.set(myObject, 'foo', 2);myObject.foo // 2Reflect.set(myObject, 'bar', 3)myObject.foo // 3// Objectvar obj = {};Reflect.set(obj, "prop", "value"); // trueobj.prop; // "value"// Arrayvar arr = ["duck", "duck", "duck"];Reflect.set(arr, 2, "goose"); // truearr[2]; // "goose"// 它可以截断数组.Reflect.set(arr, "length", 1); // truearr; // ["duck"];// With just one argument, propertyKey and value are "undefined".var obj = {};Reflect.set(obj); // trueReflect.getOwnPropertyDescriptor(obj, "undefined");// { value: undefined, writable: true, enumerable: true, configurable: true }// 非对象类型Reflect.set(1, 'foo', {}) // 报错Reflect.set(false, 'foo', {}) // 报错如果propertyKey属性设置了赋值函数,则赋值函数的this绑定receiver。var myObject = { foo: 4, set bar(value) { return this.foo = value; },};var myReceiverObject = { foo: 0,};Reflect.set(myObject, 'bar', 1, myReceiverObject);myObject.foo // 4myReceiverObject.foo // 13. 判断:Reflect.has()Reflect.has(): 判断一个对象是否存在某个属性(等同于 in 运算符)/* 参数说明 */1. target: 目标对象.2. propertyKey: 属性名,需要检查目标对象是否存在此属性。/* 语法 */Reflect.has(target, propertyKey)/* 返回值 */1. 如果该属性存在,返回 true2. 如果该属性不存在,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:var myObject = { foo: 1,};// 旧写法'foo' in myObject // true// 新写法Reflect.has(myObject, 'foo') // trueReflect.has({x: 0}, "x"); // trueReflect.has({x: 0}, "y"); // false// 如果该属性存在于原型链中,返回trueReflect.has({x: 0}, "toString");// Proxy 对象的 .has() 句柄方法obj = new Proxy({}, { has(t, k) { return k.startsWith("door"); }});Reflect.has(obj, "doorbell"); // trueReflect.has(obj, "dormitory"); // false4. 删除:Reflect.deleteProperty()Reflect.deleteProperty():用于删除对象的属性(等同于delete obj[name])/* 参数说明 */1. target: 删除属性的目标对象。2. propertyKey: 需要删除的属性的名称。/* 语法 */Reflect.get(target, propertyKey)/* 返回值 */1. 如果该属性不存在 或者 删除成功,返回 true2. 如果该属性删除失败,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:const myObj = { foo: 'bar' };// 旧写法delete myObj.foo;// 新写法Reflect.deleteProperty(myObj, 'foo');var obj = { x: 1, y: 2 };Reflect.deleteProperty(obj, "x"); // trueobj; // { y: 2 }var arr = [1, 2, 3, 4, 5];Reflect.deleteProperty(arr, "3"); // truearr; // [1, 2, 3, , 5]// 如果属性不存在,返回 trueReflect.deleteProperty({}, "foo"); // true// 如果属性不可配置,返回 falseReflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false5. 定义:Reflect.defineProperty()Reflect.defineProperty():用来为对象定义属性(等同于 Object.defineProperty)/* 参数说明 */1. target: 目标对象。2. propertyKey: 要定义或修改的属性的名称。3. attributes: 要定义或修改的属性的描述。/* 语法 */Reflect.defineProperty(target, propertyKey, attributes)/* 返回值 */1. 如果该定义成功,返回 true2. 如果该定义失败,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:function MyDate() { /*…*/}// 旧写法Object.defineProperty(MyDate, 'now', { value: () => Date.now()});// 新写法Reflect.defineProperty(MyDate, 'now', { value: () => Date.now()});let obj = {}Reflect.defineProperty(obj, 'x', {value: 7}) // trueobj.x // 76. 原型:Reflect.getPrototypeOf()Reflect.getPrototypeOf():用于读取对象的__proto__属性(等同于 Object.getPrototypeOf(obj))/* 参数说明 */ target: 获取原型的目标对象。/* 语法 */Reflect.getPrototypeOf(target)/* 返回值 */1. 给定对象的原型。2. 如果给定对象没有继承的属性,则返回 null。/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:const myObj = new FancyThing();// 旧写法Object.getPrototypeOf(myObj) === FancyThing.prototype;// 新写法Reflect.getPrototypeOf(myObj) === FancyThing.prototype;Reflect.getPrototypeOf({}); // Object.prototypeReflect.getPrototypeOf(Object.prototype); // nullReflect.getPrototypeOf(Object.create(null)); // null与Object.getPrototypeOf的区别是:// 如果参数为 Object,返回结果相同Object.getPrototypeOf({}) // Object.prototypeReflect.getPrototypeOf({}) // Object.prototype// 在 ES5 规范下,对于非 Object,抛异常Object.getPrototypeOf('foo') // Throws TypeErrorReflect.getPrototypeOf('foo') // Throws TypeError// 在 ES2015 规范下,Reflect 抛异常, Object 强制转换非 ObjectObject.getPrototypeOf('foo') // String.prototypeReflect.getPrototypeOf('foo') // Throws TypeError7. 原型:Reflect.setPrototypeOf()Reflect.setPrototypeOf():方法用于设置目标对象的原型(等同于 Object.setPrototypeOf())/* 参数说明 */1. target: 设置原型的目标对象。2. propertyKey: 对象的新原型(一个对象或 null)。/* 语法 */Reflect.setPrototypeOf(target, prototype)/* 返回值 */1. 原型设置成功,返回 true。2. 原型设置失败,则返回 false。/* 异常 */1. 如果目标值类型不是 Object ,抛出一个 TypeError。2. prototype 既不是对象也不是 null,抛出一个 TypeError。示例:const myObj = {};// 旧写法Object.setPrototypeOf(myObj, Array.prototype);// 新写法Reflect.setPrototypeOf(myObj, Array.prototype);myObj.length // 0Reflect.setPrototypeOf({}, Object.prototype); // true// 它可以改变一个对象的 [[Prototype]] 为 null.Reflect.setPrototypeOf({}, null); // true// 如果目标不是可扩展的,则返回falseReflect.setPrototypeOf(Object.freeze({}), null); // false// 如果它导致原型链循环,则返回false。var target = {};var proto = Object.create(target);Reflect.setPrototypeOf(target, proto); // false第一个参数情况:/* 第一个参数非对象 */Object.setPrototypeOf(1, {})// 1Reflect.setPrototypeOf(1, {})// TypeError: Reflect.setPrototypeOf called on non-object/* 第一个参数是undefined或null */Object.setPrototypeOf(null, {})// TypeError: Object.setPrototypeOf called on null or undefinedReflect.setPrototypeOf(null, {})// TypeError: Reflect.setPrototypeOf called on non-object8. 绑定:Reflect.apply()Reflect.apply():用于绑定this对象后执行给定函数(等同于 Function.prototype.apply.call())/* 参数说明 */1. target: 目标函数。2. thisArgument: target函数调用时绑定的this对象。3. argumentsList: target函数调用时传入的实参列表,该参数应该是一个类数组的对象。值。/* 语法 */Reflect.apply(target, thisArgument, argumentsList)/* 返回值 */1. 返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。/* 异常 */1. 如果 target 对象不可调用,抛出 TypeError。示例:Reflect.apply(Math.floor, undefined, [1.75]);// 1;Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);// "hello"Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;// 4Reflect.apply("".charAt, "ponies", [3]);// "i"一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args)采用Reflect对象可以简化这种操作。const ages = [11, 33, 12, 54, 18, 96];// 旧写法const youngest = Math.min.apply(Math, ages);const oldest = Math.max.apply(Math, ages);const type = Object.prototype.toString.call(youngest);// 新写法const youngest = Reflect.apply(Math.min, Math, ages);const oldest = Reflect.apply(Math.max, Math, ages);const type = Reflect.apply(Object.prototype.toString, youngest, []);9. new:Reflect.construct()Reflect.construct():调用构造函数的方法(等同于 new target(...args))/* 参数说明 */1. target: 被运行的目标构造函数2. argumentsList: 类数组,目标构造函数调用时的参数。3. newTarget: 作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target。/* 语法 */Reflect.construct(target, argumentsList[, newTarget])/* 返回值 */1. 以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。/* 异常 */1. 如果target或者newTarget不是构造函数,抛出TypeError,异常。示例:function Greeting(name) { this.name = name;}// new 的写法const instance = new Greeting('张三');// Reflect.construct 的写法const instance = Reflect.construct(Greeting, ['张三']);unction OneClass() { this.name = 'one';}function OtherClass() { this.name = 'other';}// 创建一个对象:var obj1 = Reflect.construct(OneClass, args, OtherClass);// 与上述方法等效:var obj2 = Object.create(OtherClass.prototype);OneClass.apply(obj2, args);console.log(obj1.name); // 'one'console.log(obj2.name); // 'one'console.log(obj1 instanceof OneClass); // falseconsole.log(obj2 instanceof OneClass); // falseconsole.log(obj1 instanceof OtherClass); // trueconsole.log(obj2 instanceof OtherClass); // true10. 扩展:Reflect.isExtensible ()Reflect.isExtensible():判断当前对象是否可扩展(等同于 Object.isExtensible)/* 参数说明 */1. target: 检查是否可扩展的目标对象。/* 语法 */Reflect.isExtensible(target)/* 返回值 */1. 如果当前对象可扩展,返回 true2. 如果当前对象不可扩展,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:const myObject = {};// 旧写法Object.isExtensible(myObject) // true// 新写法Reflect.isExtensible(myObject) // true// 新对象是可扩展的。var empty = {};Reflect.isExtensible(empty); // === true// 但这是可以改变的.Reflect.preventExtensions(empty);Reflect.isExtensible(empty); // === false// 根据定义,密封对象是不可扩展的.var sealed = Object.seal({});Reflect.isExtensible(sealed); // === false// 根据定义,冻结对象也是不可扩展的.var frozen = Object.freeze({});Reflect.isExtensible(frozen); // === false如果参数不是对象,Object.isExtensible会返回false,因为非对象本来就是不可扩展的,而Reflect.isExtensible会报错。Object.isExtensible(1) // falseReflect.isExtensible(1) // 报错11. 禁扩:Reflect.preventExtensions()Reflect.preventExtensions():阻止新属性添加到对象(等同于 Object.preventExtensions)/* 参数说明 */1. target: 需要取值的目标对象阻止扩展的目标对象。/* 语法 */Reflect.preventExtensions(target)/* 返回值 */1. 如果设置成功,返回 true2. 如果设置失败,则返回 false/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:var myObject = {};// 旧写法Object.preventExtensions(myObject) // Object {}// 新写法Reflect.preventExtensions(myObject) // true// 默认情况下,对象是可扩展的var empty = {};Reflect.isExtensible(empty); // === true// 但这是可以改变的Reflect.preventExtensions(empty);Reflect.isExtensible(empty); // === false与Object.preventExtensions的区别// ES5 环境Object.preventExtensions(1) // 报错// ES6 环境Object.preventExtensions(1) // 1// ES5、ES6 环境Reflect.preventExtensions(1) // 报错12. 属性:Reflect.ownKeys()Reflect.ownKeys():返回对象的所有属性(等同于 Object.getOwnPropertyNames+Object.getOwnPropertySymbols)/* 参数说明 */1. target: 获取自身属性键的目标对象。/* 语法 */Reflect.ownKeys(target)/* 返回值 */1. 由目标对象的自身属性键组成的 Array。/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:var myObject = { foo: 1, bar: 2, [Symbol.for('baz')]: 3, [Symbol.for('bing')]: 4,};// 旧写法Object.getOwnPropertyNames(myObject)// ['foo', 'bar']Object.getOwnPropertySymbols(myObject)//[Symbol(baz), Symbol(bing)]// 新写法Reflect.ownKeys(myObject)// ['foo', 'bar', Symbol(baz), Symbol(bing)]13. 描述:Reflect.getOwnPropertyDescriptor()Reflect.getOwnPropertyDescriptor():获取指定属性的描述对象(等同于 Object.getOwnPropertyDescriptor)/* 参数说明 */1. target: 需要寻找属性的目标对象。2. propertyKey: 获取自己的属性描述符的属性的名称。/* 语法 */Reflect.getOwnPropertyDescriptor(target, propertyKey)/* 返回值 */1. 如果属性存在于给定的目标对象中,则返回属性描述符;2. 否则,返回 undefined。/* 异常 */1. 如果目标值类型不是 Object,则抛出一个 TypeError。示例:Reflect.getOwnPropertyDescriptor({x: "hello"}, "x");// {value: "hello", writable: true, enumerable: true, configurable: true}Reflect.getOwnPropertyDescriptor({x: "hello"}, "y");// undefined与Object.getOwnPropertyDescriptor的区别// 第一个参数不是对象,抛出错误,表示参数非法Reflect.getOwnPropertyDescriptor("foo", 0);// TypeError: "foo" is not non-null object// 第一个参数不是对象,不报错,返回 undefined,Object.getOwnPropertyDescriptor("foo", 0);// { value: "f", writable: false, enumerable: tru
5.2、Proxy 代理
Proxy是ES6中新增加的"元编程(对编程语言进行编程)"内容,使用Proxy可以对被代理的对象进行拦截,当被代理对象被访问时可以实现统一的处理。
//定义被代理的对象
var shape={width:100};
//定义代理代理
let proxy=new Proxy(shape,{
get:function (target, key, receiver) {
//输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
console.log(target, key, receiver);
//使用get方法从目标对象中获取值,把取得的值加100
return Reflect.get(target, key, receiver)+100;
},
set:function (target, key, value, receiver) {
//输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
console.log(target, key, value, receiver);
//在目标对象上设置属性值,设置值时将值加100
return Reflect.set(target, key, value+100, receiver);
}
});
proxy.width=101;
console.log(proxy.width);
输出结果如图5-19所示:
图5-19 Proxy示例输出结果
示例中我们的被代理对象是shape,当对该对象执行读取操作时将自动执行get方法,拦截后将值增加了100,当对该对象执行设置值操作时将自动执行set方法,拦截后将值也增加了100,所以最后输出301。
六、异步编程 -
6.1、Generator 生成器
Generator生成器是一种带"*"号的特殊函数,是ES6中提供的一种异步编程解决方案。一个Generator可以在运行期间暂停,可以立即或稍后再继续执行。
function *SendDataGenerator() {
yield "建立连接"; //产出一个状态,暂停点
console.log("1");
yield "传输数据";
console.log("2");
return "断开连接"; //完成
}
//调用生成器创建一个生成器实例
var sender=SendDataGenerator();
console.log(sender.next()); //输出:{value: "建立连接",done: false}
console.log(sender.next()); //输出:{value: "传输数据",done: false}
console.log(sender.next()); //输出:{value: "断开连接",done: true}
console.log(sender.next()); //输出:{value: undefined,done: true}
输出结果如图5-17所示:
图5-17 ES6 生成器示例输出结果
从输出结果可以看出函数并没有一次执行完成,每当调用一次next方法后获得一个状态,向下执行一步,直到return后完成的状态值为true。
6.2、Promise 异步控制流
Promise提供一种异步编程解决方案,比传统的回调函数和事件解决方案更合理、强大、简洁。让回调函数变成了链式调用,避免了层层嵌套,使程序流程变得清晰,并为一个或者多个回调函数抛出的错误通过catch方法进行统一处理。
//复杂计算
function complexCompute(millseconds) {
//返回一个Promise对象
return new Promise(function (resolve, reject) {
if(millseconds<0){
throw new Error("毫秒数必须大于0"); //异常
}else if(millseconds<1000){
reject("毫秒数必须大于1000"); //失败时回调
}
//在指定的毫秒数millseconds结束后返回一个随机数
setTimeout(() => {
resolve(Math.random() * 1000); //成功时回调
}, millseconds);
});
}
//延迟时间为3000毫秒,执行成功,指定成功时的处理函数
complexCompute(3000).then(v => console.log(v))
//延迟时间为200毫秒,执行失败,指定成功与失败时的处理方法
complexCompute(200).then(v => console.log(v),r=>console.log(r));
//延迟时间为-10毫秒,抛出异常,指定成功与异常时的处理函数
complexCompute(-10).then(v => console.log(v)).catch(r=>console.log(r));
输出结果如图5-20所示:
图5-20 Promise示例输出结果
6.3、async-await函数
async-await是promise和generator的语法糖,使用async-await,搭配promise,可以通过编写形似同步的代码实现异步编程,提高代码的可读性,且使代码变得更加简洁。
//定义异步函数
async function getAge(age) {
if(age>0){
return age; //成功 resolve
}
else{
throw "年龄必须大于0"; //失败 reject
}
}
//执行getAge获得promise,指定成功时的处理方法
getAge(28).then(v=>console.log(v));
//执行getAge获得promise,指定失败时的处理方法
getAge(-10).catch(r=>console.log(r));
console.log("异步函数getAge后的代码"); //先输出
输出结果如图5-21所示:
图5-21 asnyc示例输出结果
从输出结果可以看出来"异步函数getAge后的代码"这一句虽然在最后但是是先输出的,而两次调用getAge虽然在前面但是后输出结果的,可以看出getAge是异步的。
而await用于等待一个异步方法执行完成,await必须定义在异步方法中。
//定义函数
function getAge(age) {
//1秒后返回结果
return new Promise((resolve, reject) =>{
setTimeout(()=>resolve(age),1000);
});
}
//定义异步函数
async function client() {
//等待getAge执行成功后返回结果,未返回结果前不向下执行
let age=await getAge(18);
console.log(age);
}
//执行
console.log(client());
1-1000毫秒时输出结果如图5-22所示:
图5-22 1-1000毫秒时await示例输出结果
因为client是异步方法,所以先输出了一个promise对象,而此时没有值,所有结果为undefined,当1000毫秒后输出的结果如图5-23所示:
图5-23 1000毫秒后await示例输出结果
七、课后作业
7.1、上机任务一(90分钟内完成)
上机目的
1、掌握ES6中集合Set与Map的应用。
2、巩固DOM操作。
上机要求
1、定义一个app对象,在该对象中封装好产品管理的业务逻辑,完成产品管理功能,如图5-24所示:
图5-24 产品管理原型
2、使用Set集合封装所有的数据。
3、完成产品的展示、添加、编辑、删除功能,删除时需要提示用户是否删除,添加时需要校验字段是否为空,尝试添加重复数据到Set集合中。
4、先用Set完成所有功能,复制页面后将Set替换成Map,实现相同的功能,试比较两者的区别。
推荐实现步骤
步骤1:创建好app对象,根据业务设计出对象的结构,参考结构如下,可以根据自己的思路调整。
var app = {
data: new Set([{...}, {...}, {...}...]),
current:null,
init() {
//初始化
},
query() {
//搜索与展示
},
delete() {
//删除
},
findById(id) {
//根据编号获得产品对象
},
edit() {
//编辑
},
save() {
//保存
}
};
步骤2:根据不同的方法完成相应的功能,先不需要考虑界面,在控制台完成所有的方法测试,通过后再根据需要渲染界面,完成其它功能。
步骤3:反复测试运行效果,优化代码,关键位置书写注释,必要位置进行异常处理。
7.2、上机任务二(90分钟内完成)
上机目的
1、掌握ES6中模块的定义、导入与导出。
2、掌握ES6中模块间的引用与应用。
上机要求
1、使用模块改进本章上机任务一,完成一个升级版本的产品管理功能,效果如图5-24所示:
2、定义5个模块,模块间的依赖关系与基本功能如图5-25所示,模块中的成员仅供参考,可以根据自己的实现思路进行调整。
图5-25 产品管理模块间依赖关系
3、页面最终只允许使用app.js主模块与utils.js工具模块。
4、所有功能要求请参照本章的上机任务一。
5、必须使用到import、export、默认导入与导出技术。
推荐实现步骤
步骤1:根据依赖关系逐个创建好每个模块,先创建没有依赖任何模块的模块,控制台测试各模块功能。
步骤2:保证模块的正确性后按要求完成每个功能。
步骤3:反复测试运行效果,优化代码,关键位置书写注释,必要位置进行异常处理。
7.3、上机任务三(60分钟内完成)
上机目的
1、掌握ES6中模块与类的定义。
2、掌握类的继承。
3、了解Canvas绘画技术。
上机要求
定义好一个模块shapeModule.js,该模块向外暴露3个类。如图5-26所示创建3个class(类),定义好属性与方法,父类中draw方法向控制台输出当前形状的基本信息,不需要实现绘图功能,area方法计算形状的面积,PI是静态字段。
图5-26 继承关系图
2、实现形状间的继承关系,构造方法要求可以初始化所有参数,子类构造方法要求调用父类构造方法,如图5-26所示。
3、分别创建不同类型的测试对象,定义对象时传入参数,调用对象中的方法。
4、重写draw方法,通过Canvas实现绘图功能,参考代码如下所示:
var c=document.getElementById("canvas1");
var cxt=c.getContext("2d");
cxt.fillStyle="dodgerblue";
//fillRect(x: number, y: number, w: number, h: number): void;
cxt.fillRect(200,200,100,200);
cxt.beginPath();
//arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;
cxt.arc(100,100,100,0,Math.PI*2,true);
cxt.closePath();
cxt.fillStyle="orangered";
cxt.fill();
图5-27 Canvas绘图参考示例
5、定义一个drawHandler方法,接受不同的形状实例,调用绘图方法,在页面上绘出不同的图形,请使用多态的方式。
推荐实现步骤
步骤1:创建模块与页面,按要求定义好三个类并export,并实现其继承关系,测试效果。
步骤2:学会HTML5中使用Canvas绘画的基本技巧后,重写draw方法。
步骤3:在页面中导入模块,创建测试对象,调用方法实现绘图功能。
步骤4:反复测试运行效果,优化代码,关键位置书写注释,必要位置进行异常处理。
7.4、代码题
1、使用XMLHttpRequest第2版XHR2从服务器获取任意一张图片的二进制数据,显示在页面中如图5-28所示。
图5-28 AJAX获得图片数据显示在页面中
2、在第1题的基础上将请求到的图片进行水平翻转,如下图5-29所示。
图5-29 客户端翻转图片效果
八、源代码
https://gitee.com/zhangguo5/JS_ES6Demos.git
九、教学视频
https://bilibili.com/video/BV1bY411u7ky?share_source=copy_web
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~