es6特性-第二部分
Promise
介绍和基本使用
Promise是ES6引入的异步编程的新解决方案,主要用来解决回调地狱问题。语法上 Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
-
Promise构造函数:new Promise()
-
Promise.prototype.then方法
-
Promise.prototype.catch方法
//创建实例
const p = new Promise(function (resolve, reject) {//模拟主体业务代码setTimeout(() => {//let data = "成功获取数据库中的数据";//如果获取数据成功,执行resolve()函数,并将返回数据作为参数传入//resolve(data);let data = "获取失败";//如果获取数据失败,执行reject()函数,并将返回数据作为参数传入reject(data);}, 1000);
});
//调用promise实例
p.then(function (value) {//执行resolve函数执行的方法//...
}, function (reason) {//执行reject()函数执行的方法//...
});
这种写法好处在于代码简洁,避免了回调地狱问题。
Promise.prototype.then
调用then方法,then方法的逐回结果是 Promise对象,对象状态由回调函数的执行结果决定。
- 如果回调函数中返回的结果是非promise 类型的属性,状态为成功,返回值为对象的成功的值.
- 如果回调函数中返回的结果是promise 类型的属性,状态根据then方法内部Promise返回的状态决定。
- 如果直接抛出错误,返回值也是promise类型的,值为抛出错误的值。
//创建实例
const p = new Promise(function (resolve, reject) {//模拟主体业务代码setTimeout(() => {//let data = "成功获取数据库中的数据";//resolve(data);let data = "获取失败";reject(data);}, 1000);
});
//调用promise实例
const result = p.then(function (value) {//1.非promise类型的属性return 'iloveyou';//2.是 promise对象return new Promise((resolve, reject) => {// resolve('ok');reject('error ');});//3.抛出错误throw new Error("出错啦!");
}, function (reason) {console.warn(reason);
});
console.log(result);
由于promise返回的是promise类型,所以可以进行链式调用
const fs = require('fs');const p = new Promise(function (resolve, reject) {fs.readFile("./source/为学.md", function (err, data) {if (err) reject(err);resolve(data);});
});p.then(value => {return new Promise((resolve, reject) => {fs.readFile("./source/为学1.md", function (err, data) {resolve([value, data]);});});
}).then(value => {return new Promise((resolve, reject) => {fs.readFile("./source/为学2.md", function (err, data) {value.push(data);return resolve(value);});});
}).then(value => {console.log(value.join('\r\n'));
});
Promise.prototype.catch
通过cache方法可以指定Promise发生错误时的回调。
promise-ajax
const p = new Promise(function (resolve, reject) {const xhr = new XMLHttpRequest();xhr.open('GET', 'https://layuion.com/static/json/table/user.json?page=1&limit=10');xhr.send();xhr.onreadystatechange = function () {if (xhr.readyState == 4) {if (xhr.status >= 200 && xhr <= 400) {resolve(xhr.response);} else {reject('获取失败');}}};
});
p.then(function (value) {console.log(value);
}, function (reason) {console.log(reason);
})
set
ES6提供了新的数据结构 set(集合)。它类似于数组,但成员的值都是唯一的(声明时即使有重复,也会去重)。集合实现了Iterator接口,所以可以使用扩展运算符和for ...of...
进行遍历。
集合的属性和方法
size
返回集合的元素个数
add
增加一个新元素,返回当前集合
delete
删除元素,返回boolean值
has
检测集合中是否包含某个元素,返回boolean值
//声明集合并赋值
let s1 = new Set(['猪', '狗', '牛', '羊', '狗']);
console.log(s1); //Set(4) {'猪', '狗', '牛', '羊'}
//长度
console.log(s1.size); //4
//添加
s1.add('猫');
console.log(s1); //Set(5) {'猪', '狗', '牛', '羊', '猫'}
//删除
s1.delete('牛');
console.log(s1); //Set(4) {'猪', '狗', '羊', '猫'}
//has
console.log(s1.has('猫')) //true
console.log(s1.has('鸡')) //false
//清空
s1.clear();
console.log(s1);Set(0) {size: 0}
//遍历
for (const v of s1) {console.log(v);
}
集合实践
//集合实践
let a1 = [1, 2, 3, 5, 5, 6, 7, 8, 6, 5];
console.log(a1); //[1, 2, 3, 5, 5, 6, 7, 8, 6, 5]
//去重
let unq = [...new Set(a1)];
console.log(unq);//[1, 2, 3, 5, 6, 7, 8]
//交集
let a2 = [4, 5, 6, 9, 6, 4, 3];
const result = [...new Set(a1)].filter(item => new Set(a2).has(item));
console.log(result); //[3, 5, 6]
//并集
console.log([...new Set([...a1, ...a2])]);
//差集-> a1 和 a2取差集意思是a1里面有,a2没有的数据; 也就是交集的取反
console.log([...new Set(a1)].filter(item => !new Set(a2).has(item))); //[1, 2, 7, 8]
map
ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用「扩展运算符』和「 for…of…』进行遍历。
Map的属性和方法:
size 返回Map的元素个数,
set 增加一个新元素,返回当前Map
get 返回键名对象的键值
has 检测Map 中是否包含某个元素,返回boolean值
clear清空集合,返回undefined
//声明map
let m1 = new Map();
//添加
m1.set('team', 'IG');
m1.set('jiaolian', 'ssss');
m1.set('lpl', function () {console.log('IG电子竞技!');
})
let boos = {'boos': '王思聪'
}
m1.set(boos, {'上单': 'the shy','打野': 'ning','中单': 'rookie','adc': 'jacklove','辅助': 'baolan'
});
//size
console.log(m1.size);
//删除
m1.delete('jiaolian');
//获取
console.log(m1.get('team'));
console.log(m1.get('lpl'));
console.log(m1.get(boos));
//清空
m1.clear();
WeakMap
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 GC 回收掉。
为什么使用weakmap?
在 JavaScript 里,map API 可以 通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。
但这样的实现会有两个很大的缺点:
-
首先赋值和搜索操作都是
O(n)
的时间复杂度(n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。简单来说就是赋值和搜索都会遍历整个数组,时间复杂度都是
O(n)
。 -
另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
相比之下,WeakMap
它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 GC 回收掉。WeakMap
提供的接口与Map
相同。
与Map
对象不同的是,WeakMap
的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。
class类
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
/*--------------------之前es5实现类的方法举例------------------------*/
//手机
function Phone(brand, price) {this.brand = brand;this.price = price;
}
//添加方法
Phone.prototype.call = function () {console.log("我可以打电话!!");
}
//实例化对象
let Huawei = new Phone('华为', 5999);
Huawei.call();
console.log(Huawei);
/*--------------------es6实现类------------------------*/
class shouji {//构造方法名字不能修改constructor(brand, price) {this.brand = brand;this.price = price;}call() {console.log("我可以打电话!!");}
}
let onePlus = new shouji('1+', 1999);
console.log(onePlus);
onePlus.call();
同一作用域的类名和函数名不能相同,例如上述代码的类名就不能命名为Phone。
类中构造方法不是必须要有。
静态成员
function Phone() { } //表示函数对象phone,相当于面向对象里面的类。
//给函数对象添加属性,它的name属性只属于Phone对象。相当于面向对象里面的静态属性
Phone.name = '手机';
//给函数对象添加方法。它的change方法只属于Phone对象相当于面向对象里面的静态方法
Phone.change = function () {console.log("我可以改变世界");
}
//访问函数对象属性和方法
console.log(Phone.name);
console.log(Phone.change);//可以通过函数对象的prototype属性给实例对象添加方法和属性
Phone.prototype.size = '5.5inch';
let nokia = new Phone(); //实例化一个对象
console.log(nokia.size); //5.5inch
//undefined 实例化对象和函数对象不相同。所以不能访问。
console.log(nokia.name);
console.log(nokia.change());//caught TypeError: nokia.change is not a function
es6中写法
class Phone{//静态属性static name ='手机';//只能通过类名访问static change(){ //只能通过类名访问console.log("我可以改变世界");}
}
let nokia = new Phone();
console.1og(nokia.name);
console.log(Phone.name);
继承
es5里实现继承
function Phone(brand, price) {this.brand = brand;this.price = price;
}
Phone.prototype.call = function () {console.log("我可以打电话");
}
//智能手机
function SmartPhone(brand, price, color, size) {Phone.call(this, brand, price);this.color = color;this.size = size;
}
//设置子级构造函数的原型
SmartPhone.prototype = new Phone;
SmartPhone.prototype.constructor = SmartPhone;//矫正,不加也可以
//声明子类的方法
SmartPhone.prototype.photo = function () {console.log("我可以拍照")
}
SmartPhone.prototype.playGame = function () {console.log("我可以玩游戏");
}
const chuizi = new SmartPhone('锤子', 2499, '黑色', '5.5inch ');
console.log(chuizi);
es6里面类的继承
class Phone {constructor(brand, price) {this.brand = brand;this.price = price;}call() {console.log('打电话')}
}class SmartPhone extends Phone {constructor(brand, price, color, size) {super(brand, price);this.color = color;this.brand = brand;}photo() {console.log("我可以拍照");}playGame() {console.log('我可以玩游戏');}
}
let xiaomi = new SmartPhone('小米', '799', '粉色', '70inch');
console.log(xiaomi);
es6中只允许继承一个类。
es6类中普通的方法中不允许调用super
方法
重写
子类中可以声明同父类名称相同的方法。
getter和setter
getter:可以对类属性绑定一个函数,当调用这个属性的时候,这个函数被执行,这个函数的返回值就是这个属性的值。通常对对象的动态属性进行封装。
setter:可以对类属性绑定一个函数,当设置这个属性的时候,这个函数被执行。可以对属性的合法性进行判断
class Phone {//这里的price为类Phone的属性,后边的分别是getter和setter绑定的方法get price() {console.log('get price')return 90;}set price(price) {console.log("set price");}
}
let p = new Phone();
console.log(p.price);//get price 90
p.price = 100;//set price
数值扩展
Number.EPSILON
Number.EPSILON
是JavaScript表示的最小精度。
EPSILON
属性的值接近于2.2204460492503130808472633361816E-16
。
通常用于两个数字进行比较,如果结果比Number.EPSILON小或等于这个数,就认为这两个数字相等。
function equal(a, b) {if (Math.abs(a - b) < Number.EPSILON) {return true;} else {return false;}
}
console.log(0.1 + 0.2 === 0.3); //false
console.log(equal(0.1 + 0.2, 0.3)) //true
二进制和八进制
let b = 0b1010; //二进制
let o = 0o777; //八进制
let d = 100; //十进制
let x= 0xff; //16进制
Number.isFinite()
检测一个数值是否为有限数
console.log(Number.isFinite(100)); //true
console.log(Number.isFinite(100/0)); //false
console.log(Number.isFinite(Infinity));//false
Number.isNaN()
Number.isNaN()
检测一个数值是否为NaN
Number.parseInt() Number.parseFloat()
字符串转整数
Number.isInteger()
判断一个数是否为整数
Math.trunc()
将数字的小数部分抹掉
Math.sign()
判断一个数到底为正数、负数还是零
对象的方法扩展
object.is()
判断两个值是否完全相等。和===
略有不同,判断NaN的时候不一样。
console.log(Object.is(120,120));//true
console.log(Object.is(NaN,NaN));//true
console.log(NaN === NaN);//false
Object.assign()
对象的合并。如果合并的对象中有相同的属性或方法,后边的会覆盖前边的属性或方法。
get或set原型对象
Object.setPrototype0f 设置原型对象
Object.getPrototypeof 获取原型对象
模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
模块化的好处
防止命名冲突
代码复用
高维护性
模块化规范产品
ES6 之前的模块化规范有:
CommonJs规范 => NodeJS、Browserify产品
AMD规范 => requireJs产品
CMD规范 => seaJs产品
ES6模块化语法
模块功能主要由两个命令构成: export和 import。
export命令用于规定模块的对外暴露接口。
/*---./js/m1.js---*/
//分别暴露
export let s = "测试模块";
export function change() {console.log('用心改变世界');
}
上边的使用export分别将s
和change
暴露。还有以下两种暴露方式:
-
统一暴露:在最底部写入一下代码
export {s, change}
; -
默认暴露:default里面可以跟任意数据,对象居多。这种方式使用的时候就要多加个default,例如:
m1.default.school
//默认暴露 export default {s: "测试模块',change: function(){console.log("我们可以改变你!!");} }
import命令用于输入其他模块提供的功能
import * as m1 from "./js/m1.js";
console.log(m1);
上边的代码使用的是通用导入方式,还有以下两种导入方式:
-
解构赋值形式
import {s, change} from "./js/m1.js"; import {s as guigu, change} from "./js/m2.js"; //与第一个s重名,可以使用as命名别名 import {default as m3} from " ./js/m3.js";
-
简便形式,只能针对默认暴路
import m3 from "./js/m3.js"; console.log(m3);
一般引入模块都是放在一个入口文件里面如:app.js
, 在html中使用时候引入这个文件就可以了
<script src="./src/js/app.js" type="module"></script>
注意:type="module"
.
在js文件中只要使用了import
语句,不管import
之间是否有javascript语句,都会按import
顺序先执行import的语句
es6模块化代码转换
由于es6语法对所有的浏览器都都不一定支持,所以考虑到兼容性,需要将es6模块代码转换成es5代码。
-
安装工具 babel-cli(babel客户端命令行) babel-preset-env(babel环境) browserify(项目中一般使用webpack打包)
-
使用命令
npx babel src/js -d dist/js --presets=babel-preset-env
,如果全局安装babel,可以直接使用babel命令:babel src/js -d dist/js --presets=babel-preset-env
. 命令的格式:[npx] babel 要转换文件路径 -d 转换完成后要存储的文件路径 --presets=babel-preset-env
-
打包:
npx browserify dist/js/app.js -o dist/bundle.js
。
打包完成后就可以在html引入了。每次更改都需要进行上述2、3步操作。
proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。proxy 是javascript的内置对象,在es6中被引入。
语法
const p = new Proxy(target, handler)
参数
target
: 要使用 Proxy
包装的目标对象(引用类型:对象,数组,函数,map, set, 甚至另一个代理)。
handler
: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为。
let person = {name: "John",age: 21,
};const p = new Proxy(person, {/*** 拦截读取属性* @param target 目标对象* @param property 被获取的属性名。* @param receiver Proxy 或者继承 Proxy 的对象*/get(target, property, receiver) {},/*** 拦截赋值属性* @param target 目标对象* @param property 被设置的属性名。* @param value 属性值* @param receiver Proxy 或者继承 Proxy 的对象* @returns boolean*/set(target, property, value, receiver) {return true;},/*** 拦截方法调用* @param target 目标对象(函数)* @param thisArg 被调用时的上下文对象* @param argumentsList 被调用时的参数数组。* apply方法可以返回任何值。*/apply(target, thisArg, argumentsList) {},/*** 用于拦截 new 操作符* @param target 目标对象* @param argumentsList constructor 的参数列表* @param newTarget 最初被调用的构造函数* construct 方法必须返回一个对象。*/construct(target, argumentsList, newTarget) {return {};},/*** 用于拦截 in 操作符* @param target 目标对象。* @param prop 需要检查是否存在的属性。* has 方法返回一个 boolean 属性的值。*/has(target, prop) {return true;},/*** 法用于拦截 Reflect.ownKeys()* @param target 目标对象*/ownKeys(target) {return Reflect.ownKeys(target);},/*** 用于拦截对对象属性的 delete 操作。* @param target 目标对象。* @param property 待删除的属性名。* @returns boolean*/deleteProperty(target, property) {return true;},
});
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。反射,就是将代理的内容反射出去。
与大多数全局对象不同 Reflect
并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect
对象作为一个函数来调用。Reflect
的所有属性和方法都是静态的。
注意:Reflect的静态方法是直接使用的
const p = new Proxy(person, {/*** 拦截读取属性* @param target 目标对象* @param property 被获取的属性名。* @param receiver Proxy 或者继承 Proxy 的对象*/get(target, property, receiver) {if (target.age <= 15) {return Reflect.get(target, property, receiver); //直接使用和对象操作一样} else {return "John成年了!!!";}},
静态方法
Reflect.apply(target, thisArgument, argumentsList)
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。Reflect.construct(target, argumentsList[, newTarget])
对构造函数进行 new 操作,相当于执行 new target(...args)。Reflect.deleteProperty(target, propertyKey)
作为函数的delete操作符,相当于执行 delete target[name]。Reflect.get(target, propertyKey[, receiver])
receiver可以理解为上下文this对象
获取对象身上某个属性的值,类似于 target[name]。Reflect.getPrototypeOf(target)
类似于 Object.getPrototypeOf()。Reflect.has(target, propertyKey)
判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。Reflect.isExtensible(target)
类似于 Object.isExtensible().Reflect.ownKeys(target)
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable 影响).Reflect.preventExtensions(target)
类似于 Object.preventExtensions()。返回一个Boolean。Reflect.set(target, propertyKey, value[, receiver])
将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
在面向对象中,反射的三种用法:1、查看元数据。2、动态创建对象。3、动态调用方法。
示例
模拟mobx
const list: Set<Function> = new Set();
const autorun = (cb: Function) => {list.add(cb);
};
const observable = <T extends object>(target) => {return new Proxy(target, {set(target, p, newValue, receiver) {const result = Reflect.set(target, p, newValue, receiver);list.forEach((fn) => fn());return result;},});
};
const dog = observable({ name: "阿黄", age: 1 });
autorun(() => {console.log("change");
});dog.name = "阿绿"; //change
dog.age = 2; //change