生成器
基本概念
Generator 函数是Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
JAVASCRIPT
1234567 |
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
1234567 |
function * foo(x, y) { ··· }function *foo(x, y) { ··· }function* foo(x, y) { ··· }function*foo(x, y) { ··· } |
生成器函数解决回调地狱问题
要实现异步要不断的嵌套,如下。
JS
123456789 |
setTimeout(()=>{ console.log("1s")setTimeout(()=>{console.log("2s")setTimeout(()=>{console.log("3s") },3000) },2000)},1000) |
这就是所谓的callback hell
解决方法
JS
123456789101112131415161718192021222324252627 |
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
1234567891011121314151617181920212223242526272829 |
//模拟获取 用户数据 订单数据 商品数据 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
123456789101112131415161718 |
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
123456789101112131415161718 |
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
12345678910111213 |
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
123456789101112131415161718192021222324 |
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
12345 |
getJSON("/posts.json").then(function(json) {return json.post;}).then(function(post) {// ...}); |
上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
JAVASCRIPT
1234567 |
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
123456 |
getJSON("/post/1.json").then(post => getJSON(post.commentURL)).then(comments =>console.log("Resolved: ", comments),err =>console.log("Rejected: ", err)); |
读取多个文件
JS
12345678910111213141516171819202122232425262728293031 |
//引入 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
12345678910111213141516171819202122232425 |
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
12345 |
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
123456789101112131415161718192021222324252627282930313233 |
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
12345678910 |
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
123456789101112131415161718192021222324252627282930313233 |
//手机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);//classclassShouji{ //构造方法 名字不能修改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
12345678910111213141516171819202122232425262728293031323334353637383940414243 |
// 发送 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 测试 axiosasyncfunctionmain(){//发送 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(); |


