# 第一章 ES6
## 目标
- [阮一峰ES6入门](http://es6.ruanyifeng.com/)
![ES6目标](https://box.kancloud.cn/6f21223a6be48bc1e0fc81b4c22b60dc_883x586.png)
- 重点:
# 1 简介
- JavaScript 语言组成: ECMAScript +DOM + BOM
![JS组成](https://box.kancloud.cn/84172d82548f5ba30624774ed31b28d8_355x161.png)
1. ECMA是(欧洲计算机制造商协会)它规定了js的语法标准
2. DOM(document object model),是文档对象模型,规定了文档的显示结构,可以轻松地删除、添加和替换节点
3. BOM(browser object document ) 是浏览器对象模型,就是浏览器自带的一些功能样式,如搜索框,设置,等学习浏览器窗口交互的对象
- ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。2015年推出的,也叫ES2015
- ECMA 发展图谱
![ECMA发展图谱](https://box.kancloud.cn/4be24e28f12d901917dc4f32c219dbfe_408x410.png)
` 注意TypeScript是JS的超级,完全面向对象,适用于大型web开发`
![](https://box.kancloud.cn/e4ed538dbb618fcf9ff7d53e5df981d4_1092x461.png)
- [检测当前浏览器ES6支持情况](http://ruanyf.github.io/es-checker/index.cn.html)
# 2 ES6 新增语法特性
## (1) 块级作用域
> ES5变量的两种作用域:函数级作用域和全局作用域.
> ES6 新增了 `let` 和 `const` 命令声明变量和常量!. 语法也var类似, 只是在 `let` 和 'const' 命令所> 在代码块有效!
特性说明
1. let 和const 是块级作用域
2. 变量名不能重复
使用块级作用域优点
1. var,内层变量可能覆盖外出变量
2. 用来基数的循环变量泄漏为全局变量
- 'let` 变量只在当前代码块有效,
```
{
let a= 10;
var b= 1;
}
a // ReferenceError: a is not defined
b // 1
```
- let 变量名不能重复
```
//报错
function fn1(){
let a = 10;
let a = 1;
}
```
- let 不存在变量提升
`var` 会发生 "变量提升"现象, 既变量在声明之前调用时,值为 `underfined`,按照一般逻辑,变量应该在声明滞后才能使用. 为纠正这一现象`let`改变了语法行为,变量必须在声明后使用,否则报错!
```
//var 情况
console.log(num1); //输出 underfined
var num1 = 2;
// let 情况
console.log(bar); //报错 ReferenceError
let bar = 2;
```
`var`声明变量 `num1` ,发生变量提升,既脚本开始运行时,变量 `num1`已经存在,但是没有值,所以输出 `underfined`. `let`声明的变量 `bar` ,不发生变量提升,标示声明前 `bar` 不存在,使用时会抛出异常
- 为什么使用块级作用域
(1) 内存变量可能覆盖外层变量
```
var tmp = new Date();
function fn1(){
console.log(tem);
if(false){
var tmp = "hello word";
}
}
fn1(); // underfined ;
```
本应 `if` 外部 使用外层 `tmp` ,内部使用 内层`tmp`,但是调用后,结果为 `underfined`,原因在于变量提升,导致内层 `tmp` 覆盖了外层 `tmp`.
(2) 用来计数的循环变量泄漏为全局变量
```
var s =" hello";
for (var i=0; i <s.length;i++){
console.log(s[i]);
}
console.log(i); //5
```
`var`声明的 `i` 在全局范围有效,每次循环新`i`的值会覆盖原值!
常用在 tab切换中
````
var btn = document.querySelectorAll(".btn");
for(let i=0; i<btn,length;i++){
// let 定义的变量i在当前块有效,每次循环都产生一个新的变量
btn[i].click=function(){
console.log("您点的是:"+i);
}
}
````
(3) 暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响.
```
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
```
S6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
- 常量 const
`const` 声明一个只读的常量.一旦声明,常量的值就不能发生变化!
```
const PI = 3.1415;
//以下两行会报错
var PI = 3;
let PI = 3.1;
```
本质: `const` 实际保证的,并不是变量的值不改动, 而是变量指向的内存地址不得改动. 对于简单数据类型(数值,字符串,布尔值),值就保存在变量指向的那个内存地址,一次等同于常量.
但是符合数据类型(主要是对象和数组),变量指向的内存地址中保存的只是一个指针,`const`只能保证指针固定不变,它指向的数据结构哦是可变的!
` 常量数组或对象,地址不可变,属性或只是可变的!`
```
const FOO = {};
// 为FOO 添加一个属性, 可以成功
FOO.prop = 123;
console.log(prop); //123
// 将 foo 指向另一个对象,就会报错
foo = {}; //TypeScript : FOO is ready-only
const NUM= [];
NUMS[0] = 1; //可以
NUM[ = [3,4] ; //报错
```
## (2)解构(Eestructuring)
> 解析数据结构; ES6 允许按照一定模式,从数组或对象中取值,对变量进行复值,这被称为解构!
> 重点: 从json中解析数据!
案例:
- 解构数组
- 结构字符串
- 结构属性
- 解构对象
- 解构应用在函数中,返回多个值
- 两个数交换
- for..of 编列器
案例
```
// 以前变量赋值
let a = 1;
let b = 2;
let c = 3;
//案例1: 解构数组
//上面代码表示,可以从数组中提取值,按照对应位置,为变量赋值!
let [a,b,c] = [1,2,3] ; //左右一一对应
let [a2,b2] = [1,2,3] ; //不完全解构,只匹配部分右边数组
//变量解构不成功,值等于underfined
let [a3]= [];
let [a4,a5]= [1];
console.log(a);
//案例2: 结构字符串
let [s1,s2,s3] = "abc";
console.log(s3);
//案例3 结构属性
let {length : changdu}= "hello My test";
console.log(changdu);
//案例4: 结构对象, 用在解析 ajax 返回的网络数据
let xinwen = {id:1,title:"海南开发自由贸易区",desc:"各种利好"};
let [title,desc ] = xinwen;
console.log(title);
//案例5: 解构应用在函数中
function fn1(){
return [10,20,30];
}
let [x1,x2] = fn1(); //可不完全对称
// 案例6 数据交换面试题
let y1 = 10;
let y2 = 20;
[y1,y2] = [y2,y1];
console.log("y1值:"+ y1);
```
ES6为字符串添加了遍历器接口(Iterator) ,使字符串可以被 `for...of`循环遍历
```
for ( let s of "hello"){
conslole.log(s); // h e l l o
}
```
解构解析ajax数据: 聚合API 获取手机号归属地
```
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
//请求地址:聚合数据api获取的电话号码归属地
$.ajax({
type:"get",
data:"phone=13413678912&dtype=&key=7fedcd3e4bd65e5b2471da4468c02aba",
url:"http://apis.juhe.cn/mobile/get",
dataType:"jsonp",
success:function(res){
console.log(res.result);
//解构只获取 手机号码地区
//{province: "广东", city: "湛江", areacode: "0759", zip: "524000", company: "移动", …}
let {province,city} = res.result;
}
});
})
</script>
```
## (3)字符串的扩展
> ES6 对字符串String进行了扩展
>
- 模板字符串,简化字符串拼接
- 扩展方法
includes,startsWidth,endsWidth,repeat,padStrats,padEnd,
```
let lili = {name:"丽丽", sex:"女",age:20};
console.log("介绍:您好我的名字叫"+lili.name+",今年*"+lili.age+"*岁了");
//案例1: 模板字符串 反引号 ` ${变量} ` 简化拼接
console.log(`我的名字叫${lili.name},年龄:${lili.age}`);
//是否包含
let str = "abcdef";
console.log(str.includes('a')); // true
console.log(str.startsWidth('a')); // true
console.log(str.endsWidth('a')); // true
console.log(str.includes('a',2)); // 以上方法支持第二个参数,标示开始搜索位置
//重复n次,参数>0
console.log("x".repeat(3)) ; // xxx
console.log("hello".repeat(2)); // "hellohello"
console.log("na.repeat(0)); // ""
//补全
console.log( "x".padStart(5,"ab")); //ababx
console.log( "x".padEnd(5,"ab")); //xabab
```
## (4) 函数新增
1. 函数参数带默认值
2. rest语法
3. 箭头函数
- ES6之前不能直接为函数参数指定默认值,智能采用变通方法
````
function fn1( x , y ){
y = y || 'World' ;
console.log(x , y);
}
fn1('hello'); // Hello , World
fn1('Hello', 'China') ; // Hello China
fn1('Hello', ' '); // Hello World
````
上面代码检测 `fn1` 参数 `y` 是否赋值,如果没有赋值,指定默认为 `World` , 缺点: y的值为 `false`(空也是false) 该赋值不起作用!!!
为了解决 该问题,通常需要先判断一下是否被赋值, 如果没赋值再等于默认值
```
if(typeof y === 'underfined'){
y = 'World ;'
}
```
- ES6 允许为函数参数设置默认值:
```
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
```
以上ES6 写法比ES5 简介很多而且非常自然,再看一个案例:
```
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
````
注意: 参数变量是默认声明的,所以内部不能用 let 或const 再次声明!
优点: 简洁, 提到代码可读性(立马意识到哪些参数可省略), 有利于代码优化!
- rest 参数简化参数定义 函数名 (参数1,参数2,...变量名) 只能是最有一个参数
```
function fn1(...name){ // fn1(name1,name2,name3,name4)
return name;
}
console.log( fn1("张三","李四","王五", "赵六"));
//求和
function add(...values){
let sum= 0;
for(let i of values){
sum + = i;
}
return sum;
}
add(2,5,3); // 10
```
reset语法可以简化参数个数,调用时可以向该函数传入任意数据的参数!
案例:改写数组改写数组push方法
```
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
```
- 函数的name属性
函数的name属性返回该函数的名字
````
function foo() {}
foo.name // "foo"
//注意,如果讲一个匿名函数赋值给变量,ES5的name返回空
var f = function (){};
// ES5
f.name // ""
//ES6
f.name // f
````
变量 `f` 等价于一个匿名函数, ES5 和ES6 返回值不同!
- 箭头函数(arrow function )
ES6 允许使用“箭头”(=>)定义函数。
语法: (参1,参2....)=>{n行代码}
如果箭头函数不需要参数或需要多个参数,就使用`()`表示参数部分
如果尖头函数代码块多余一条语句,使用 `{}`括起来
```
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
```
## (5) Map
- why
- map存数据格式
- 属性和方法
- 遍历
- map 和 数组 转化
- map 和 对象 转化
- map 和 json 转化
> JS对象Object,本质上是键值对的集合(Hash解构),但是key只能为字符串类型,使用中带了很大限制
> ES6 解决了以上问题,提供了Map数据解构, 也是 key-value 对, 但是"键"的范围可以是各种类型的值(包括对象)也可以当做>key,也就是Object提供了 "字符串-值"的对应, Map结构提供 "值-值"对应,如果使用"键值对"的数据解构,Map比Object合适
```
// DOM节点作为data的键
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
```
由于对象key只能为字符串,所以e`lemenet`自动被转化为 字符串 `[object HEMLDivElement[]`. 解决可以用Map
> 生活中成对存在的数据可以用Map存储
> 比如:CN-->中国 USA--美国 JP----日本
> ES6 新增Map存key:value对, 通过自带属性和方法可以便捷操作数据!
> 常用方法: size属性, get,set, has,delete, keys,values, for..of 编历,
这里科普下ES6新增的遍历器接口Iterator, 不仅仅是数组,任何具有Iterator接口,且每个成员都是一个双元素的数组的数据解构都可以当做Map构造函数的参数,也就是说`Set` 和 `Map` 都可以用来生成新Map
```
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
//set
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
```
属性和方法介绍
```
//创建
const map = new Map();
//赋值 set(key,value) key重复会自动覆盖
map.set("foo",true);
map.set("bar",false).set(1,"a").set(2,"b");
//属性
console.log(map.size); // 4
//取值get(key)
console.log(get("foo")); // true
//是否包含 has(key)
console.log(map.has(1));
//删除 delete(key)
console.log(map.delete("foo"));
//清空 clear
map.clear();
console.log( map.size); //0
```
>遍历: Map结构原生提供三个遍历器生成函数和一个遍历方法
- `keys()` 返回键名编历器
- `values()` 返回值的遍历器
- `entries()` 返回所有成员/条目的遍历器
- `forEach()` 遍历Map的所有成员
```
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
//for ..of
for(let key for map.keys()){
console.log(key);
}
// F
// T
// values()
for( let v from map.values()){
console.log(v);
}
// no
// yes
for ( let item of map.entries()){
console.log(item[0], item[1]);
}
或者
for ( let [key,vlue] of map.entries()){
console.log(key, value);
}
等同于
for ( let [key,vlue] of map){
console.log(key, value);
}
// F no
// T yes
```
> Map 机构转为数组解构,比较快速的方法是使用扩展运算符 (`...`)
```
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
```
> Map 还有一个 `forEach`方法,与数组的 `forEach`方法类似,可以实现遍历
````
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
````
> map 和 数组转化
````
// map转数组扩展运算符
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
//数组转map,直接将数组传入Map构造函数
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
````
> Map 和 对象
```
// key都转化为字符串,再作为对象的key
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
console.log(strMapToObj(myMap));
// { yes: true, no: false }
//对象转map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
console.log(objToStrMap({yes: true, no: false}));
// Map {"yes" => true, "no" => false}
```
>
>Map 转JSON,情况1:Map的key都是字符串,可以转为json对象; 情况2:Map key有非字符串,可转为数组JSON
```
//情况1
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
console.log(strMapToJson(myMap));
// '{"yes":true,"no":false}'
//情况2
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
console.log(mapToArrayJson(myMap));
// '[[true,7],[{"foo":3},["abc"]]]'
```
> JSON转Map
```
//情况1: key都是字符串
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
//情况2: 整个JSON就是一数组嵌套数组
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
```
## (6) Set
- 保存唯一不重复数据
- 常用方法
- 遍历
> ES6 新数据解构Set,类似于数组,成员的值都是唯一,不重复的!
- 常用方法:
```
//创建
const set = new Set([1,2,3,4,4]);
console.log([...set]); // [1,2,3,4] 自动去重复
// size 大小
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log( items.size) ; // 5
//set 保存数组对象
const set = new Set(document.querySelectorAll('div'));
set.size // 56
// 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56
//add()
let s = new Set();
s.add(1).add(2).add(3);
//delte() 删除
// has() 是否包含
// clear() 清空
```
- Set 和 数组
```
//Array.form 把Set转数组
const items = new Set([1,2,3,4,5]);
const aray = Array.form(items);
//数组排重方法:
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
```
- 遍历
1. keys
2. values
3. entries
4. forEach
`注意:Set遍历顺序就是插入顺序`
这里科普下ES6新增的遍历器接口Iterator, 不仅仅是数组,任何具有Iterator接口,且每个成员都是一个双元素的数组的数据解构都可以当做Map构造函数的参数,也就是说`Set` 和 `Map` 都可以用来生成新Map, ,Set的key和Value值一样!
```
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
```
- Set与数组一样,也拥有forEach,用于对每个成员执行某种操作,但是无返回值
```
let set2 = new Set([1, 4, 9]);
set2.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
```
- 遍历应用
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构
```
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
```
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
````
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
````
而且,数组的map和filter方法也可以间接用于 Set 了。
```
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
```
Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
```
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
```
直接在遍历操作中改变原来的 Set 结构
```
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
```
## (7) OOP: 类,构造器,对象, 继承
>面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)
>OOP: 把生活中的物体/对象,用程序代码描述的过程!
>生活中物体/对象组成: 名字和形容词描述(属性) + 动作/行为
| 生活 | 程序 |
| --- | --- |
| 1类物体 | ES6用 1个Class 表示 |
| 属性 | 类中成员变量/属性 |
| 行为 | 类中方法/函数 |
| 某个物体 | 通过类new出来的某个对象 |
> JavaScript 语言中,生成实例对象的传统方法是通过构造函数
1. ES5 建立对象
2. 通过class 定义对象
3. constructor构造器/构造方法 和 this关键字
4. 继承复用代码
- ES5 同哦过构造函数定义对象
```
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);
p.toString();
```
[原型和面向对象编程请参考廖雪峰官网](https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344997235247b53be560ab041a7b10360a567422a78000)
以上写法跟传统面向对象语言(C++,Java)差异很大,容易让新学习JS语言的程序员感到困惑!
ES6 提供了更仅仅传统语言的写法, 引入Class(类)的概念,作为对象的模板! 通过`class` 关键字可以定义类!
> ES6 `class` 改写
语法:
class 类名{
construnctor(参数1,参数2,..){
this.属性1= 参数1;
this.属性2= 参数2;
....
}
函数名(){
方法体
}
...
}
- 通过class定义对象
```
//定义类
Class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
// 注意:定义类中方法时不需要加function关键字,方法之间也不需要用逗号隔开
toString(){
return '(' + this.x + ',' + ',' +this.y +')';
}
}
let p = new Point(1,2);
p.toString();
```
以上代码定义了一个"类", 包含一个`constructor`方法,这就是构造方法,;`this` 关键字则代表实例对象!
也就是说, ES6 的构造函数 `Point` , 对应ES6 的`Point` 类的构造方法!
ES6的类,完全可以看作构造函数的另一种写法!
```
class Point {
// ...
}
console.log( typeof(Point)) // "function"
console.log( Point === Point.prototype.constructor) // true
```
以上代码表明,类的数据类型就是 函数,类本身就是指向构造函数!,使用时,也是直接对类使用`new`命令,跟构造函数用法一致!
构造函数的`prototype`属性,在ES6"类"中继续存在事实上,类的所有方法都定义在类的`prototype`属性上面
```
class Point{
constructor(){ // ...}
toString(){ // ...}
toValue(){// ...}
}
//等价于
Point.prototype = {
constructor(){},
toString(){},
toValue(){}
}
````
在类的实例上调用方法,其实就是调用原型上的方法
```
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
````
- constructor 方法
>`constructor` 方法是类默认方法, 通过 `new` 关键字生成对象实例时, 自动调用该方法! 一个类必须有`constructor`,如果没有显示定义, 一个空的 `constructor` 方法将会被默认添加! 显示声明后,默认无参构造讲被覆盖! 带参构造方法用于完成对象属性赋值!
```
class Point {
}
// 等同于
class Point {
constructor() {} // 无参构造一般不写
}
```
注意: `constructor` 默认返回实例对象, (既this) ,当前页可以返回另一个对象
```
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
```
- Class 表达式
与函数一样,类也可以使用表达式的形式定义,
```
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
```
需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。
```
const MyClass = class { /* ... */ };
```
采用 Class 表达式,可以写出立即执行的 Class。
```
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
```
- 私有方法,ES6不提供,只能通过变通的方式模拟
一种做法是在命名上加以区别。
```
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
```
上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。
另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
```
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
```
上面代码中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。
- Class 的取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
````
class MyClass {
constructor() {
// ...
}
get uname() {
return 'getter';
}
set uname(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.uname = "zhangsan";
// setter: 123
inst.uname
// 'getter'
````
- 继承实现代码复用
![继承](https://box.kancloud.cn/4c49f3fa5f9a54327ac9a650c74d81d6_1352x720.png)
> Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
```
class Point {
}
// 子类 继承 父类
class ColorPoint extends Point {
}
```
继承实现复用父类代码, super调用父类!只能在第一行
```
/**
* 使用继承实现代码复用:,父类Animal,包含共有属性:名字,年龄, 共有行为:吃*/
class Animal{
//构造方法构建属性,完成赋值
constructor(n,a){
this.name = n;
this.age = a;
}
toString() {
return '名字:'+this.name+'年龄:'+this.age;
}
chi(str){
console.log(`我叫${this.name},${str}真好吃....`);
}
}
/**小狗Dog类实现代码复用,属性:名,年龄, 功能:吃,子类 继承 父类 */
class Dog extends Animal{
//构造: 颜色是特有的
constructor(name,age,color){
super(name,age); //调用父类构造!!!只能在第一行
this.color= color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
let lele = new Dog("乐乐",2,"黑色");
console.log("我家小狗名:"+lele.name);
lele.chi("骨头");
console.log(lele.toString())
```
## (8) Promise 承诺
[参考廖雪峰-Promise](https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000)
1. ES5 异步介绍
2. Promis 简单概念
3. Promis 执行简单异步任务
4. Promis 模拟执行异步任务
5. Promis 执行串联任务
6. Promis Ajax应用
7. Promis 执行选择任务
- ES5 异步介绍
在JavaScript 的世界中,所有代码都是单线程执行的.
由于这个"缺陷",导致所有JavaScript的所有网络操作,浏览器事件,都必须异步执行.异步执行可以用回调函数实现:
```
function callback(){ //回调函数名字可以任意哦
console.log("hello World 定時器");
}
console.log("Before setTimeout() ");
setTimeout(callback, 1000); //1s 后调用 callback函数
console.log("After setTimeout()");
//结果:
//Before setTimeout()
//After setTimeout()
//(等待1秒后)
//hello World 定時器
```
可见,异步操作会在将来的某个时间点触发一个函数调用.
Ajax就是典型的异步操作.案例:
```
var request = new ActiveXObject("Microsoft.XMLHTTP");
request.onreadystatechange=function(){
if(request.readyState == 4){
if(request.status == 200){
return successFn(request.readyState); //成功处理函数
}else{
return failFn(request.status); // 失败处理函数
}
}
}
```
把回调函数 ` successFn(request.readyState)` 和 `failFn(request.status)` 写到一个Ajax操作,不利于代码复用!
有没有更好的写法? 比如写成这样:
````
var ajax = ajaxGet("http://...");
ajax.ifSuccess(successFn)
.ifFail(failFn);
````
- Promis 简单概念
这种链式写法的好处在于, 先同意执行Ajax逻辑,不关心处理结果,然后根据结果成功或失败,在将来的某个时刻调用 `successFn` 函数或 `failFn` 函数.
这种"承诺将来会执行" 的对象,在ES6 中已经存在,叫 `Promise` 承诺对象
`Promise` 有各种开源实现, 在ES6中被统一规范,由浏览器支持. 先测试一下你的浏览器是否支持Promise对象
```
console.log(new Promise(function(){ } ));
//支持 打印 Promise 对象
```
- Promis 简单异步任务
Promise 简单案例: 生成0-2之间的随机数, 如果小于1,则等待一段时间后返回成功, 否则返回失败!
```
function test(resolve, reject) {
var timeout = Math.random() * 2;
console.log("等待" + timeout + "s");
setTimeout(function() {
if(timeout < 1) {
console.log("call resolve() 成功"); // 成功回调函数一般叫:resolve 肯定
resolve("200 ok ");
}else{
console.log("call reject() 失败");//失败回调函数一般叫:reject 拒绝
reject('fail ' + timeout + ' seconds.');
}
},timeout*1000);
}
```
`test()` 函数有2个参数,这两个参数都是函数,如果执行成功,将调用 `resolve('200 ok')` ,如果执行失败,讲调用 ` reject('fail timeout' + timeout + ' seconds.'))` . 核心在于 `test()` 函数只关心自身的逻辑,不关心成功`(resolve)`或失败`(reject)`将如何处理结果.
有了执行函数,我们就可以用一个`Promise`对象来执行它,并在将来的某个时刻获得成功或失败的结果:
```
//p1是Promise对象,负责执行test函数,test内部是异步的
var p1 = new Promise(test);
//如果成功执行这个函数
var p2 = p1.then(function(result){
console.log("成功:"+result);
});
//如果失败执行这个函数
var p3 = p2.catch(function(reason){
console.log("失败:"+reason);
});
简写:
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});
```
- Promis 模拟执行异步任务
实际测试一下,看promise是如何异步执行的:
页面部分
````
<div id="test-promise-log"></div>
````
JS部分
```
// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
logging.removeChild(logging.children[logging.children.length - 1]);
}
// 输出log到页面:
function log(s) {
var p = document.createElement('p');
p.innerHTML = s;
logging.appendChild(p);
}
new Promise(function (resolve, reject) {
log('开始 Promise...');
var timeOut = Math.random() * 2;
log('时间: ' + timeOut + ' s.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' s.');
}
}, timeOut * 1000);
}).then(function (res) {
log('Done: ' + res);
}).catch(function (reason) {
log('Failed: ' + reason);
});
```
页面结果参考:
![Promise页面结果](https://box.kancloud.cn/8d5cccae19c8893cbed296eafa7e7120_360x195.png)
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
![Promise流程](https://box.kancloud.cn/9b03684f98cc75bb874b69f830bd99bb_512x280.png)
- Promis 串联
`Promise` 还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败都不能继续并执行错误处理函数. 要串行执行这样的异步任务,不用Promise 需要写一层一层的嵌套代码. 有了Promise,只需要简单的写:
```
job1.then(job2).then(job3).catch(handleError);
```
其中 `job1` , `jbo2` 和 `job3` 都是Promise对象
下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:
页面
````
<div id="test-promise-log"></div>
`````
JS代码
````
// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
logging.removeChild(logging.children[logging.children.length - 1]);
}
// 输出log到页面:
function log(s) {
var p = document.createElement('p');
p.innerHTML = s;
logging.appendChild(p);
}
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
return new Promise(function (resolve, reject) {
log('0.5s后执行 ' + input + ' x ' + input + '...');
setTimeout(resolve, 500, input * input);
});
}
// 0.5秒后返回input+input的计算结果:
function add(input) {
return new Promise(function (resolve, reject) {
log('0.5s后执行 ' + input + ' + ' + input + '...');
setTimeout(resolve, 500, input + input);
});
}
var p = new Promise(function (resolve, reject) {
log('开始 new Promise...');
resolve(2);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
log('结果: ' + result);
});
````
![Promise串联](https://box.kancloud.cn/bf33823485cde89dae594d21ff76ef08_271x274.gif)
- Promis Ajax应用
`setTimeout`可以看成一个模拟网络等异步执行的函数。
现在,我们把AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:
页面
```
<div id="div-promise-ajax-result"></div>
```
JS代码:
````
// ajax函数将返回Promise对象:
function ajax(method,url,data){
var request = new XMLHttpRequest();
return new Promise(function(resolve,reject){
request.onratechange=function(){
if(request.readyState == 4 ){
if(request.status == 200){
resolve(request.responseText);
}else{
reject(request.status);
}
}
};
request.open(method,url);
request.send(data);
});
}
var log = document.getElementById("div-promise-ajax-result");
var p = ajax("get","https://api.github.com/users");
//如果ajax成功,得到响应内容
p.then(function(text){
log.innerText = text;
}).catch(function(status){
//失败 得到相应代码
log.innerText = "错误状态码:"+status;
});
````
可以看到数据哦
- Promis 执行并行任务
除了串行执行若干异步任务外,Promise还可以并行执行异步任务
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用`Promise.all()`实现如下
```
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
```
- Promis 执行比赛任务
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用`Promise.race()`实现
```
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
```
由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。
* * * * *
## Thaks OVER!