从零开始学 Web 之 ES6(五)ES6基础语法三
2018-08-31 17:25
686 查看
大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新......
github:https://github.com/Daotin/Web
微信公众号:Web前端之巅
博客园:http://www.cnblogs.com/lvonve/
CSDN:https://blog.csdn.net/lvonve/
在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧!
一、Generator
Generator 函数是 ES6 提供的一种异步编程解决方案。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。
一是,
function关键字与函数名之间有一个星号;
二是,函数体内部使用
yield表达式,定义不同的内部状态(
yield在英语里的意思就是“产出”)。
function* myGenerator() { yield 'hello'; yield 'world'; return 'ending'; } // 返回值是一个遍历器对象 var hw = myGenerator();
上面代码定义了一个 Generator 函数
helloWorldGenerator,它内部有两个
yield表达式(
hello和
world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的
next方法,使得指针移向下一个状态。也就是说,每次调用
next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个
yield表达式(或
return语句)为止。换言之,Generator 函数是分段执行的,
yield表达式是暂停执行的标记,而
next方法可以恢复执行。
hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的
next方法,就会返回一个有着
value和
done两个属性的对象。
value属性表示当前的内部状态的值,是
yield表达式后面那个表达式的值;
done属性是一个布尔值,表示是否遍历结束。
1、yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
yield表达式就是暂停标志。
遍历器对象的
next方法的运行逻辑如下:
(1)遇到
yield表达式,就暂停执行后面的操作,并将紧跟在
yield后面的那个表达式的值,作为返回的对象的
value属性值。
(2)下一次调用
next方法时,再继续往下执行,直到遇到下一个
yield表达式。
(3)如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到
return语句为止,并将
return语句后面的表达式的值,作为返回的对象的
value属性值。
(4)如果该函数没有
return语句,则返回的对象的
value属性值为
undefined。
需要注意的是,
yield表达式后面的表达式,只有当调用
next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
yield表达式与
return语句区别:
相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到
yield,函数暂停执行,下一次再从该位置继续向后执行,而
return语句不具备位置记忆的功能。
一个函数里面,只能执行一次(或者说一个)
return语句,但是可以执行多次(或者说多个)
yield表达式。
正常函数只能返回一个值,因为只能执行一次
return;Generator 函数可以返回一系列的值,因为可以有任意多个
yield。
2、与 Iterator 接口的关系
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的
Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
上面代码中,Generator 函数赋值给
Symbol.iterator属性,从而使得
myIterable对象具有了 Iterator 接口,可以被
...运算符遍历了。
3、next 方法的参数
yield表达式本身没有返回值,或者说总是返回
undefined。
next方法可以带一个参数,该参数就会被当作上一个
yield表达式的返回值。
function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
上面代码先定义了一个可以无限运行的 Generator 函数
f,如果
next方法没有参数,每次运行到
yield表达式,变量
reset的值总是
undefined。当
next方法带一个参数
true时,变量
reset就被重置为这个参数(即
true),因此
i会等于
-1,下一轮循环就会从
-1开始递增。
4、for...of 循环
for...of循环可以自动遍历 Generator 函数时生成的
Iterator对象,且此时不再需要调用
next方法。
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
上面代码使用
for...of循环,依次显示 5 个
yield表达式的值。这里需要注意,一旦
next方法的返回对象的
done属性为
true,
for...of循环就会中止,且不包含该返回对象,所以上面代码的
return语句返回的
6,不包括在
for...of循环之中。
下面是一个利用 Generator 函数和
for...of循环,实现斐波那契数列的例子。
function* foo() { let [prev, current] = [0, 1]; for (;;) { yield current; [prev, current] = [current, prev + current]; } } for (let n of foo()) { if (n > 1000) break; console.log(n); }
Generator小案例
需求:
1、发送Ajax请求获取新闻内容
2、新闻内容获取成功再次发送请求获取对应的新闻评论内容
3、新闻内容获取失败则不需要再次发送请求。
function getNews(url) { $.get(url, function (data) { console.log(data); let urls = "http://localhost:3000" + data.commentUrl; // urls可以作为第一个yield的返回值 // 执行第二条yeild语句,发送请求新闻评论 // 获取的评论地址如何传入到 yield getNews(urls);靠的是第二次 // 发送next时传入的参数,就是评论地址 sx.next(urls); }); } function* sendXml() { // 发送请求新闻内容 let urls = yield getNews("http://localhost:3000/news?id=2"); // 请求新闻评论内容 yield getNews(urls); } let sx = sendXml(); // 执行第一条yeild语句,发送请求新闻 sx.next();
二、async
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。
语法:
async function foo(){ await 异步操作; await 异步操作; }
特点:
1、不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行
2、返回的总是Promise对象,可以用then方法进行下一步操作
3、async取代Generator函数的星号*,await取代Generator的yield
4、语意上更为明确,使用简单,经临床验证,暂时没有任何副作用
举个栗子:
async function timeout(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }) } async function asyncPrint(value, ms) { console.log('函数执行', new Date().toTimeString()); await timeout(ms); console.log('延时时间', new Date().toTimeString()); console.log(value); } console.log(asyncPrint('hello async', 2000));
asyncPrint 执行的时候,先打印的是“函数执行”,之后进入到 timeout 函数,由于是异步执行,但是timeout未执行完成,所以 await 在等待,相当于挂起。而这一边 asyncPrint会立即返回一个 Promise对象。之后另一边timeout、执行完成,打印出“延时时间”,之后打印“hello async”。
async函数内部
return语句返回的值,会成为
then方法回调函数的参数。下面代码中,函数
f内部
return命令返回的值,会被
then方法回调函数接收到。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
1、await 命令
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即
resolve的 Promise 对象。
而 resolve参数就是await的返回值。
async function f() { return await 123; } f().then(v => console.log(v)) // 123
await命令后面的 Promise 对象如果变为
reject状态,则
reject的参数会被
catch方法的回调函数接收到。
async function f() { await Promise.reject('出错了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了
2、案例:获取新闻和评论内容
async function sendXml(url) { return new Promise((resolve, reject) => { $.ajax({ url, type: 'GET', success: data => resolve(data), error: error => reject(error) }) }) } async function getNews(url) { let result = await sendXml(url); let result2 = await sendXml(url); console.log(result, result2); } getNews('http://localhost:3000/news?id=2')
三、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关键字,可以定义类。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
1、constructor 方法
constructor方法是类的默认方法,通过
new命令生成对象实例时,自动调用该方法。一个类必须有
constructor方法,如果没有显式定义,一个空的
constructor方法会被默认添加。
Class 的继承
Class 可以通过
extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point { } // ColorPoint 继承 Point class ColorPoint extends Point { }
上面代码定义了一个
ColorPoint类,该类通过
extends关键字,继承了
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对象。
相关文章推荐
- 从零开始学 Web 之 ES6(六)ES6基础语法四
- 从零开始学 Web 之 ES6(四)ES6基础语法二
- 从零开始学 Web 之 Ajax(二)PHP基础语法
- 【Java.Web】JSP —— 语法基础 —— 总结
- [JavaWeb基础] 028.CSS简介和基础语法
- ES6基础语法(六) —— Class的基本运用
- [Web基础开发-JSP基础语法]JSP注释与Scriptlet_01
- Web端 es6(基础四) 数值扩展 Number
- 【Java.Web】JSP —— 语法基础 —— JSP异常处理
- React-redux-webpack项目总结之用到的Es6基本语法
- ES6基础语法(四) —— 数据集合-map
- 从零开始学 Web 之 ES6(二)ES5的一些扩展
- [Web基础开发-JSP基础语法]Page指令_02
- [Wondgirl]从零开始学React Native之ES5 ES6语法对比(七)
- Web端 es6(基础十六) 模块化
- 【Java.Web】JSP —— 语法基础 —— 选择在web.xml配置访问JSP URL
- ES6语法基础
- ES6基础语法(五) —— Symbol
- [Web基础开发-JSP基础语法]跳转指令_04