Part 1: ES6 中的类
基本类的声明
- ES5 中仿类的构成:
创建一个构造器,然后将方法指派到该构造器的原型上。
这被称为“创建自定义类型”。
![](https://img.haomeiwen.com/i4131163/dcfd9242cb4f20a8.png)
![](https://img.haomeiwen.com/i4131163/32438a745ff7edc3.png)
👆sayName 方法被指派到原型上,接下来用
new
创建了一个 PersonType 的一个实例 person,如此,这个对象是通过原型继承了 PersonType 与 Object 的实例。
-
ES6 通过
class
关键字创建一个类:
Paste_Image.png
ES6 的类中,允许使用一个特殊方法constructor
直接定义一个构造器👆
constructor
之外的方法名则由我们随意定义。 -
ES6 中的
class
,实际上是一个语法糖。
它实际上创建了并实现了一个和constructor
一样的函数
Paste_Image.png
对其使用typeof
返回function
👆
Paste_Image.png
sayName
实际上也是 PersonClass.prototype 上的一个函数👆
也正是这样的特性允许我们将 “类” 和 “自定义类型” 混合使用。
仍有值得主要的不同之处
- 尽管特别相似,但 “类” 和 “自定义类型” 仍有一些不同之处。
- 类声明不会被提升,这与函数声明有不同,类声明更像是
let/const
声明,在其声明之前它都存在于 “暂时性死区” 中。 - 类声明中的代码运行于严格模式中,并且没有退出严格模式的可能。
- 不使用
new
调用类会报错
Paste_Image.png
类的表达式
- 类也有类似函数表达式一样的书写方法
这个表达式类声明与前面类声明的例子其实没有什么区别👇
Paste_Image.png
表达式类声明,同样不会被提升。
所以,使用哪种声明纯属代码风格问题。
个人喜欢class MyClass{}
这样的书写方法,因为这和其它主流编程语言(Java、PHP、Python)更像。
需计算的成员名
类方法也可以使用可计算的名称,用 []
包裹名称👇
![](https://img.haomeiwen.com/i4131163/cbadf2449d8e96be.png)
生成器方法
可以为对象定义一个生成器方法。
使用 *
配合方法名称,书写方法和对象字面量中定义生成器是一样的👇
![](https://img.haomeiwen.com/i4131163/0a8e5d5830de58b2.png)
当对象需要存些值并要求可做些简单的迭代时,生成器方法很有效。
于是,我们可以用
Symbol.iterator
来写一个默认迭代器,迭代更方便👇![](https://img.haomeiwen.com/i4131163/a93c4c7a2d9811ba.png)
静态成员
- 如果想让方法只存在于类自身,无需实例化,这时我们需要的是“静态成员”
- 在 ES5 中,将方法添加在构造器上而不是构造器的原型上,是模拟静态成员的普遍做法👇
Paste_Image.png
ES6 中添加静态方法,使用static
关键字👇
Paste_Image.png
派生类
-
继承了其它类的类称为 “派生类”。
ES6 使用extends
创建派生类👇
Paste_Image.png
👆如果派生类使用了构造器,则必须在构造器中使用super()
来访问基类的构造器,否则会报错。
如果不使用构造器,派生类则会自动调用super()
👇
Paste_Image.png
-
只能在派生类(使用了
extend
)中使用super()
。 -
屏蔽基类方法
派生类的方法总是会屏蔽基类中的同名方法。
这个特性使我们可以在派生类中重新定义它的功能👇
Paste_Image.png
-
从表达式中派生类
ES6 派生类的强大之处在于可以从表达式中派生类。
只要表达式能够返回一个具有[[consturctor]]
属性以及原型(prototype)的函数👇
Paste_Image.png
Rectangle 是一个 ES5 风格的构造器,而 Square 是一个 ES6 的类,由于 Rectangle 具有[[constructor]]
属性和原型,所以 Square 可以直接继承 Rectangle。
Part 2: Promise
Promise 基础
使用 Promise 对象构造一个实例👇
![](https://img.haomeiwen.com/i4131163/b801cc24a8fe9bdb.png)
👆接受一个被称为执行器的函数作为参数,该函数接受两个函数
resolve
和 reject
作为参数。实例化后,使用
then()
方法指定异步操作结果的回调函数。
Promise 生命周期
Promise 的生命周期初始为 pending state
,此时异步操作尚未结束。一旦操作结束,状态转为 resolved
(成功结束) 或 rejected
(未成功结束)。
内部的 [[PromiseState]]
属性在这个过程中被设置为 pending
后转换为 resolved
或 rejected
) 以反映 Promise 的状态。
这个属性无法手动判断,但我们可以通过 then()
方法来指定状态改变时可以做的一些事情。
then()
方法和 catch()
方法
then()
方法存在于所有 Promise 实例上,接受两个参数,第一个参数用来指定 Promise 状态由 pending
转为 resolved
时做的事(异步完成了);第二个参数用来指定 Promise 状态由 pending
转为 rejected
时做的事(异步被拒绝了)。
传递给 then()
参数是可选的👇
// 处理完成和拒绝
pro.then(function(content){
console.log(content);
}, function(error){
console.log(error);
});
// 只处理完成
pro.then(function(content){
console.log(content);
});
// 只处理拒绝
pro.then(null, function(error){
console.log(error);
});
Promise 还有一个 catch()
方法,它的行为和只处理拒绝的例子是一样的👇
pro.catch(function(error){
console.log(error);
})
// 等同于
pro.then(null, function(error){
console.log(error);
});
如果被拒绝了,但没有指定如何处理,拒绝还是会静默发生👇
![](https://img.haomeiwen.com/i4131163/be9513c82b98b8e5.png)
建议始终附加一个拒绝处理函数, 即使该处理程序只是用于打印错误日志 —— Nicholas
catch()
方法还可以这样使用,在 Promise 的执行器中抛出一个错误,外部的拒绝处理函数会被调用👇
![](https://img.haomeiwen.com/i4131163/a623b71f7627a4d9.png)
串联 Promise
对 then()
或 catch()
的调用实际上创建并返回了另一个 Promise。
当前一个 Promise 完成或被拒绝时,后一个 Promise 开始工作。
这个特性允许我们使用一组串联起来的 Promise 。
![](https://img.haomeiwen.com/i4131163/de004c5d2bae35c0.png)
串联中的 Promise 也可以捕获上一个Promise 中抛出的错误👇
![](https://img.haomeiwen.com/i4131163/e54ba30414223e2d.png)
若是拒绝处理函数抛出的错误,同样可以在接下来捕获👆
在 Promise 链中返回值
执行器中传递给 resolve 函数的参数会被传递给对应的完成处理函数👇
![](https://img.haomeiwen.com/i4131163/b1b014547b406a87.png)
可以指定完成函数的返回值,这就可以沿着 Promise 链继续传递数据👇
![](https://img.haomeiwen.com/i4131163/96dcff681472d1b8.png)
如果是拒绝处理函数也可以完成同样的事情👇
![](https://img.haomeiwen.com/i4131163/1acb535ab6c0e073.png)
💡 如此看,若有必要,可以用失败的 Promise 传递参数来恢复整个 Promise 继续运行。
响应多个 Promise
-
Promise.all()
Promise.all()
接受一个可迭代对象(比如数组)作为参数,并返回一个 Promise 。
可迭代对象的元素中的 Promise 全部完成后,所返回的 Promise 才执行👇
![](https://img.haomeiwen.com/i4131163/07d66c5e1302cfb9.png)
-
Promise.race()
Promise.race()
同样接受一个可迭代对象作为参数,返回一个 Promise 。
不同之处是,可迭代对象中有一个 Promise 完成,返回的 Promise 就会执行👇
![](https://img.haomeiwen.com/i4131163/492b89bf4f1beed3.png)
Part 3:模块封装代码
在 ES6 之前,一个应用的每个 JS 文件所 定义的所有内容都由全局作用域共享。
ES6 的设计目标之一就是 要解决作用域问题。 —— Nicholas
模块特点s
- 模块中的代码自动运行在严格模式下,并无方法可以退出严格模式。
- 对于模块外部代码访问的内容,模块必须导出它们。
基本的导出
使用 export
关键字公开功能给其它模块👇
// 导出变量
export let name = 'Pacino';
export const country = 'Italy';
export var address = 'New York';
// 导出类
export class Rectangle{
constructor(length, width){
this.length = length;
this.width = width;
}
}
// 导出函数
export function sum(num1, num2){
return sum = num1 + num2;
}
// 声明的函数,不做导出,外部无法访问
function sum(num1, num2){
return sum = num1 - num2;
}
// 导出一个函数
function foo(){
return true;
}
// 稍后导出它
export { foo };
👆除了 export
关键字以外,每个声明与平时没什么不同。
💡 无法用这种方法导出匿名函数或匿名类,除非使用 default
(稍后再说)
基本的导入
使用 import
关键字引入模块。
// 导入单个绑定
import { sum } form './test.js';
// 导入多个绑定
import { name, country, address } from './test.js';
// 完全导入
// test.js 中暴露出的模块如今都可以使用了
import * as test from './test.js';
重命名的导出和导入
可以使用 as
关键字来为导出和导入的模块重命名👇
// foo 函数被作为 myFunc 导出
function foo(){
return true;
}
export { foo as myFunc };
👆而在“接收端”(👈我自己起的名字),导入这个函数时则是要接收 myFunc
这个函数而不是 foo
函数了。
在“接收端”,同样可以重命名后导入👇
import { foo as myFunc} from './test.js';
模块默认值
使用 default
关键字指定模块中的默认导出👇
// 这个函数成为了模块中的默认导出内容
export default function foo(){
return true;
}
// 另一种默认导出的写法
function foo(){
return true;
}
export { foo as default };
💡 若模块中有多个导出,并需要默认导出,第二种写法是很好的方法。
导入默认内容,去掉 {}
👇
import app from './test.js';
无绑定的导入
之前介绍的导入,都是有指定的内容的,或是默认的内容的,也就是有绑定的导入。
当然我们还可以做无绑定的导入👇
import './test.js';
语法非常简单。
写在导出模块中的内容也不需要添加 export
关键字了。
下面是一个书中的例子:
// 没有导出与导入的模块
Array.prototype.pushAll = function(items) {
// items 必须是一个数组
if (!Array.isArray(items)) {
throw new TypeError("Argument must be an array.");
}
// 使用内置的 push() 与扩展运算符
return this.push(...items);
};
导出模块中的方法,直接拿来用👇
import "./example.js";
let colors = ["red", "green", "blue"];
let items = [];
items.pushAll(colors);
本节完
Understanding ECMAScript6(上)
Understanding ECMAScript6(中)
网友评论