生成器
基本概念
Generator 函数是Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
JAVASCRIPT
1 2 3 4 5 6 7 |
function* helloWorldGenerator() { yield'hello'; yield'world'; return'ending'; } var hw = helloWorldGenerator(); |
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
ES6没有规定,function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
JAVASCRIPT
1 2 3 4 5 6 7 |
function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· } |
生成器函数解决回调地狱问题
要实现异步要不断的嵌套,如下。
JS
1 2 3 4 5 6 7 8 9 |
setTimeout(()=>{ console.log("1s") setTimeout(()=>{ console.log("2s") setTimeout(()=>{ console.log("3s") },3000) },2000) },1000) |
这就是所谓的callback hell
解决方法
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
functionone(){ setTimeout(()=>{ console.log("1s") iterator.next() },1000) } functiontwo(){ setTimeout(()=>{ console.log("2s") iterator.next() },2000) } functionthree(){ setTimeout(()=>{ console.log("3s") },3000) } function *gen(){ yield one(); yield two(); yield three(); } var iterator =gen() iterator.next() |
实例2
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//模拟获取 用户数据 订单数据 商品数据 functiongetUsers(){ setTimeout(()=>{ let data = '用户数据'; //调用 next 方法, 并且将数据传入 iterator.next(data); }, 1000); } functiongetOrders(){ setTimeout(()=>{ let data = '订单数据'; iterator.next(data); }, 1000) } functiongetGoods(){ setTimeout(()=>{ let data = '商品数据'; iterator.next(data); }, 1000) } function * gen(){ let users = yield getUsers(); let orders = yield getOrders(); let goods = yield getGoods(); } //调用生成器函数 let iterator = gen(); iterator.next(); |
promise对象
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise
对象。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:Pending
(进行中)、Resolved
(已完成,又称Fulfilled)和Rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从Pending
变为Resolved
和从Pending
变为Rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于Pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise
更好的选择。
基本使用
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const p =newPromise(function(resolve,reject) { setTimeout(()=>{ let data ='数据返回成功' resolve(data) // let err ='数据返回失败' // reject(err) },2000) } ) p.then(function(value){ console.log(value) },function(reason){ console.log(reason) }) |
读取文件
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const fs = require('fs') // fs.readFile('aa.txt',(err,data)=>{ // if(err) throw err; // console.log(data.toString()) // }) const p =newPromise(function(resolve,reject){ fs.readFile('aa.txt',(err,data)=>{ if (err) reject(err) resolve(data) }) }) p.then(function(value){ console.log(value.toString()) },function(reason){ console.log("读取失败!!") }) |
封装ajax
原生ajax请求
就直接往本站发请求了,看看拿不拿的到响应结果。
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const xhr = new XMLHttpRequest() xhr.open("GET",'https://www.jnylife.com') xhr.send() xhr.onreadystatechange= function (){ if(xhr.readyState ===4){ if(xhr.status>=200 && xhr.status<= 300) //成功 console.log(xhr.response) else{ console.error(xhr.status) } } } |
成功拿到响应结果,如图:
promise封装ajax
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const p = newPromise((resolve,reject)=>{ const xhr = new XMLHttpRequest() xhr.open("GET",'https://www.jnylife.com') xhr.send() xhr.onreadystatechange= function (){ if(xhr.readyState ===4){ if(xhr.status>=200 && xhr.status<= 300) //成功 resolve(xhr.response) else{ reject(xhr.status) } } } }) p.then(function(value){ console.log(value) },function(reason){ console.log(reason) }) |
Promise.prototype.then()
Promise实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
then
方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
JAVASCRIPT
1 2 3 4 5 |
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... }); |
上面的代码使用then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
JAVASCRIPT
1 2 3 4 5 6 7 |
getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(functionfuncA(comments) { console.log("Resolved: ", comments); }, functionfuncB(err){ console.log("Rejected: ", err); }); |
上面代码中,第一个then
方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用funcA
,如果状态变为Rejected,就调用funcB
。
如果采用箭头函数,上面的代码可以写得更简洁。
JAVASCRIPT
1 2 3 4 5 6 |
getJSON("/post/1.json").then( post => getJSON(post.commentURL) ).then( comments =>console.log("Resolved: ", comments), err =>console.log("Rejected: ", err) ); |
读取多个文件
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//引入 fs 模块 const fs = require("fs"); //使用 promise 实现 const p = newPromise((resolve, reject) => { fs.readFile("aa.txt", (err, data) => { resolve([data]); }); }); p.then(value => { returnnewPromise((resolve, reject) => { fs.readFile("aa.txt", (err, data) => { value.push(data) resolve(value) }); }); }).then(value => { returnnewPromise((resolve, reject) => { fs.readFile("aa.txt", (err, data) => { //压入 value.push(data); resolve(value); }); }) }).then(value => { console.log(value.toString()); }); |
Set
Set本质就是集合,元素唯一。
Set结构的实例有以下属性。
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
add(value)
:添加某个值,返回Set结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
let arr = [1,2,3,4,5,4,3,2,1]; //1. 数组去重 // let result = [...new Set(arr)]; // console.log(result); //2. 交集 let arr2 = [4,5,6,5,6]; // let result = [...new Set(arr)].filter(item => { // let s2 = new Set(arr2);// 4 5 6 // if(s2.has(item)){ // return true; // }else{ // return false; // } // }); // let result = [...new Set(arr)].filter(item => new Set(arr2).has(item)); // console.log(result); //3. 并集 // let union = [...new Set([...arr, ...arr2])]; // console.log(union); //4. 差集 let diff = [...new Set(arr)].filter(item => !(newSet(arr2).has(item))); console.log(diff); |
MAP
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
JAVASCRIPT
1 2 3 4 5 |
var data = {}; var element = document.getElementById('myDiv'); data[element] = 'metadata'; data['[object HTMLDivElement]'] // "metadata" |
上面代码原意是将一个DOM节点作为对象data
的键,但是由于对象只接受字符串作为键名,所以element
被自动转为字符串[object HTMLDivElement]
。
为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
let m = newMap(); //添加元素 m.set('name','chenhao'); m.set('change', function(){ console.log("chenhao is cool"); }); console.log(m) let key = { school : 'ynnubs' }; m.set(key, ['北京','上海','深圳']); //size // console.log(m.size); //删除 // m.delete('name'); //获取 // console.log(m.get('change')); // console.log(m.get(key)); //清空 // m.clear(); //遍历 for(let v of m){ console.log(v); } // console.log(m); |
class
JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。
JAVASCRIPT
1 2 3 4 5 6 7 8 9 10 |
functionPoint(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
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//手机 functionPhone(brand, price){ this.brand = brand; this.price = price; } //添加方法 Phone.prototype.call = function(){ console.log("我可以打电话!!"); } //实例化对象 let Huawei = new Phone('华为', 5999); Huawei.call(); console.log(Huawei); //class classShouji{ //构造方法 名字不能修改 constructor(brand, price){ this.brand = brand; this.price = price; } //方法必须使用该语法, 不能使用 ES5 的对象完整形式 call(){ console.log("我可以打电话!!"); } } let onePlus = new Shouji("1+", 1999); console.log(onePlus); |
利用async和await结合发送ajax请求
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// 发送 AJAX 请求, 返回的结果是 Promise 对象 functionsendAJAX(url) { returnnewPromise((resolve, reject) => { //1. 创建对象 const x = new XMLHttpRequest(); //2. 初始化 x.open('GET', url); //3. 发送 x.send(); //4. 事件绑定 x.onreadystatechange = function () { if (x.readyState === 4) { if (x.status >= 200 && x.status < 300) { //成功啦 resolve(x.response); }else{ //如果失败 reject(x.status); } } } }) } //promise then 方法测试 // sendAJAX("https://api.apiopen.top/getJoke").then(value=>{ // console.log(value); // }, reason=>{}) // async 与 await 测试 axios asyncfunctionmain(){ //发送 AJAX 请求 let result = await sendAJAX("https://api.apiopen.top/getJoke"); //再次测试 let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P') console.log(tianqi); } main(); |