# 第一章 ES6
## 目標
- [阮一峰ES6入門](http://es6.ruanyifeng.com/)

- 重點:
# 1 簡介
- JavaScript 語言組成: ECMAScript +DOM + BOM

1. ECMA是(歐洲計算機制造商協會)它規定了js的語法標準
2. DOM(document object model),是文檔對象模型,規定了文檔的顯示結構,可以輕松地刪除、添加和替換節點
3. BOM(browser object document ) 是瀏覽器對象模型,就是瀏覽器自帶的一些功能樣式,如搜索框,設置,等學習瀏覽器窗口交互的對象
- ECMAScript 6.0(以下簡稱ES6)是JavaScript語言的下一代標準,已經在2015年6月正式發布了。它的目標,是使得JavaScript語言可以用來編寫復雜的大型應用程序,成為企業級開發語言。2015年推出的,也叫ES2015
- ECMA 發展圖譜

` 注意TypeScript是JS的超級,完全面向對象,適用于大型web開發`

- [檢測當前瀏覽器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'
````
- 繼承實現代碼復用

> 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最大的好處是在異步執行的流程中,把執行代碼和處理結果的代碼清晰地分離了:

- 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);
});
````

- 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!