ES6 學習教程與demo-case總結

學習序

1.前端技術

十年前說前端,一定是Flash、Html、Js、Css,這是我工作的時候,前端接觸最普遍的技術,Jquery可以說是很新的技術了。

但發展到今天的前端是一個范圍很廣的技術領域,發展迭代之快,衍生框架之多,可以說其他語言是不能媲美的,這也說明了前端技術是一個活躍的、標準難制定的領域。

這也直接推動了前端技術棧的發展,包括JS的UI框架、模塊化的MVVM框架及構建工具、JavaScript 編譯器、CSS的預處理器、HTML5技術。

常用的JS相關框架:
Ext.js、Jquery、Jquery MiniUI、Jquery EasyUI (解決瀏覽器兼容)
React、Angular 2、 Vue(MVVM框架)
RequireJS、 SeaJS、Webpack
Babel、CoffeeScript、 TypeScript
Less、Saas

2.移動開發技術

App(Application 應用程序)一般是指手機端的軟件。手機端的操作系統有iOS、Android、Windows Phone、Fuchsia(Google下一代手機操作系統)
各系統的開發語言有:
iOS --- Object-C、Swift
Android ----Java、Kotlin
Fuchsia ---- Dart
上面都可以稱為原生開發,移動端技術發展也是提高生產力的過程,出現了跨平臺開發的框架,一套代碼多端運行,現在常見的開發框架有H5、小程序、uni-app(VUE跨平臺框架)、Vue-Native、weex、React-Native、Flutter

3.學習基礎的重要性

學習好這些基礎是寫出合理代碼的重要支撐。

實際工作中可能受限于工期和領導的壓力,我們在基礎的學習上都是蜻蜓點水,急急忙忙的做出效果,導致代碼頻出bug,這點我是親身經歷過的痛。

去年由原生開發轉Native的同事,es6看了一禮拜,告訴我已經學的差不多了,然后讓他加入了項目組開始開發,一天的折騰后,臨下班前說界面輸入的文本,在傳參的時候獲取不到,找不到問題所在,然后加入log輸入參數

我和他一起看打印出的參數,他義正言辭的說沒問題,我看了知道是結構賦值的問題。讓他重新看了看解構賦值,多想想以前的json取值

結果再過兩天又犯了同樣的錯誤,抱怨半天。其實還是基礎沒理解好,這會很影響開發效率。

廢話不多說,祝君學習順利 !


1. 環境搭建

Node.js是運行在服務端的JavaScript環境,基于Google的V8引擎,下載地址Node.js 安裝包及源碼下載地址為:https://nodejs.org/en/download/

Node.js 歷史版本下載地址:https://nodejs.org/dist/

具體的安裝教程:https://www.runoob.com/nodejs/nodejs-install-setup.html
安裝完成以后,以windows為例,打開cmd窗口 執行node -v查看,輸出版本號說明安裝成功。
成功之后就可以使用 npm 命令來管理安裝的依賴包了。也可使用 yarn

npm install -g yarn

由于某些安裝包有可能安裝失敗的風險,做React Native的同學應該深有體會,原因當然是天朝網絡的問題了,所以我們需要安使用國內源,cmd窗口執行

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

或

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

好多人推薦使用 yarn,原因是npm的版本管理有個安裝不一致的問題,比如 ~1.0.3 指的是 1.0.X的最新版,^1.0.3是指1.X.X的最新版,很容易導致一個月前項目跑起來好好的,來一個新同事,項目跑起來有問題,我相信入坑多年的都有這種經歷。yarn的出現也解決了這個痛點(

其實npm 已經用package-lock.json解決了這個問題。用哪個看你喜好
這是常用的操作命令

npm yarn
npm install yarn
npm install react --save yarn add react
npm uninstall react --save yarn remove react
npm install react --save-dev yarn add react --dev
npm update --save yarn upgrade

2. let 與 const

代碼塊有效

  var PI = "a";
  {
     console.log(PI) // 報錯
     let a = 0;
     var b = 1;
 }
 console.log(b)

let 只能聲明一次 var 可以聲明多次

 let a = 1;
 let a = 2;   //  報錯
 var b = 3;
 var b = 4;
 console.log(b)

const 是聲明常量,相當于java中final聲明

const PI = "3.1415926";
PI = "dadada" // 報錯
console.log(PI)

3. 解構賦值

  • 數組模型的解構(Array)
    情景1:
    let [a, b, c] = [1, 2, 3];
    // a = 1
    // b = 2
    // c = 3
    
    情景2:
    let [a, [\[b], c]] = [1, [\[2], 3]];
    // a = 1
    // b = 2
    // c = 3
    
    情景3:
    let [a, ...b] = [1, 2, 3]
    //a = 1
    //b = [2, 3]
    
    情景4:
    let [a, b, c] = [1, 2, 3];
    // a = 1
    // b = 2
    // c = 3
    
  • 對象模型的解構(Object)
    情景1:
    let { football, basketball } = { football: 'CSL', basketball: 'NBA' }
    // football = 'CSL'
    // basketball = 'NBA'
    
    情景2:
    const {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
    // a = 10
    // b = 20
    // rest = {c: 30, d: 40}
    

4. Symbol

這是一種新的基礎數據類型,和Object、 Number 、 String 、 Boolean同級

  • 基本使用

    let sy = Symbol("zk");
    console.log(sy);    // Symbol(zk)
    typeof(sy);     // "symbol"
    let sy1 = Symbol("zk");     // 相同參數 Symbol() 返回的值不相等
    sy === sy1;     // false
    
  • 適用場景1
    使用symbol作為對象屬性名(key)

    const param3 = Symbol()
    let obj = {
       param1: 100, 
       "param2": "egg",
       [param3]:"我是params33"
    }
    obj["param1"]   // 100
    obj["param2"]  // 'egg'
    const param4 = Symbol()
    obj[param4] = "我是param444"
    

    Symbol可同樣用于對象屬性的定義和訪問:

    let obj = {
        [Symbol('name')]: '老師',
        age: 43,
        dept: '軟件學院'
    }
    Object.keys(obj)    // ['age', 'title'] 
    
  • 適用場景2
    注冊和獲取全局Symbol,Symbol.for() 類似單例模式

    let instance = Symbol("Instance");
    let instance1 = Symbol.for("Instance");
    instance === instance1;      // false
     
    let instance2 = Symbol.for("Instance");
    instance1 === instance2;     // true
    

5. Map 與 Set

Map 的鍵值可以是任意值,開發通用是String 字符串

var myMap = new Map();
var keyString = "a string"; 
 
myMap.set(keyString, "和鍵'a string'關聯的值");
  
myMap.get(keyString);    // "和鍵'a string'關聯的值"
myMap.get("a string");   // "和鍵'a string'關聯的值"
  • Map 迭代
    for...of

    var myMap = new Map();
    myMap.set(0, "zero");
    myMap.set(1, "one");
    
    // 將會顯示兩個 log。 一個是 "0 = zero" 另一個是 "1 = one"
    for (var [key, value] of myMap) {
    console.log(key + " = " + value);
    }
    
    // 將會顯示兩個log。 一個是 "0" 另一個是 "1"
    for (var key of myMap.keys()) {
    console.log(key);
    }
    /* 這個 keys 方法返回一個新的 Iterator 對象, 它按插入順序包含了 Map 對象中每個元素的鍵。 */
    
    // 將會顯示兩個log。 一個是 "zero" 另一個是 "one"
    for (var value of myMap.values()) {
    console.log(value);
    }
    /* 這個 values 方法返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每個元素的值。 */
    

    forEach()

    var myMap = new Map();
    myMap.set(0, "zero");
    myMap.set(1, "one");
    
    // 將會顯示兩個 logs。 一個是 "0 = zero" 另一個是 "1 = one"
    myMap.forEach(function(value, key) {
        console.log(key + " = " + value);
    }, myMap)
    
  • Map 與 Array的轉換

    var kvArray = \[["key1", "value1"], ["key2", "value2"]];
    
    // Map 構造函數可以將一個 二維 鍵值對數組轉換成一個 Map 對象
    var newMap = new Map(kvArray);
    
    // 使用 Array.from 函數可以將一個 Map 對象轉換成一個二維鍵值對數組
    var outArray = Array.from(newMap);
    
  • Set
    Set 對象允許你存儲任何類型的唯一值,無論是原始值或者是對象引用。

    +0 與 -0 在存儲判斷唯一性的時候是恒等的,所以不重復;
    undefined 與 undefined 是恒等的,所以不重復;

      let mySet = new Set();
     
      mySet.add(1); // Set(1) {1}
      mySet.add(5); // Set(2) {1, 5}
      mySet.add(5); // Set(2) {1, 5} 這里體現了值的唯一性
      mySet.add("some text");     // Set(3) {1, 5, "some text"} 這里體現了類型的多樣性
     
      var o = {a: 1, b: 2}; 
      mySet.add(o);
      mySet.add({a: 1, b: 2}); 
      // Object即使一樣,所以mySet的結構是 Set(5) {1, 5, "some text", {…}, {…}} 
    
  • Set 與 Array

    // Array 轉 Set
    var mySet = new Set(["value1", "value2", "value3"]);
    // 用...操作符,將 Set 轉 Array
    var myArray = [...mySet];
    
    // String 轉 Set
    var mySet = new Set('hello');  // Set(4) {"h", "e", "l", "o"}
    

    數組去重的使用

    var mySet = new Set([1, 2, 3, 4, 4]);
    [...mySet]; // [1, 2, 3, 4]
    

6. 字符串

ES6 之前判斷字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的識別方法。

includes():返回布爾值,判斷是否找到參數字符串。
startsWith():返回布爾值,判斷參數字符串是否在原字符串的頭部。
endsWith():返回布爾值,判斷參數字符串是否在原字符串的尾部。

let string = "apple,banana,orange";
string.includes("banana");   // true
string.startsWith("apple");   // true
string.endsWith("apple");    // false
string.startsWith("banana",6) // true

這三個方法只返回布爾值,如果需要知道子串的位置,還是得用 indexOf 和 lastIndexOf 。

  • 模板字符串
    模板字符串相當于加強版的字符串,用反引號 `,除了作為普通字符串,還可以用來定義多行字符串,還可以在字符串中加入變量和表達式。

    let name = 'zk';
    let age = 18;
    let info =\`我的名字是 \${name},我今年 \${age+1} 歲了\`;
    

7. 對象

ES6允許對象的屬性直接寫變量,這時候屬性名是變量名,屬性值是變量值。

const age = 12;
const name = "Amy";
const person = {age, name};
//等同于
const person = {"age": age, "name": name}
  • 方法名也可以簡寫

    const person = {
    sayHi(){
        console.log("Hi");
    }
    }
    person.sayHi();  //"Hi"
    //等同于
    const person = {
        sayHi:function(){
            console.log("Hi");
        }
    }
    person.sayHi();//"Hi"
    
  • 拓展運算符(...)用于取出參數對象所有可遍歷屬性然后拷貝到當前對象

    let person = {name: "Amy", age: 15};
    let someone = { ...person };
    someone;  //{name: "Amy", age: 15}
    

    如果是相同的屬性,后加入的屬性覆蓋前面的

    let person = {name: "Amy", age: 15};
    let someone = {name: "Mike", age: 17, ...person};
    someone;  //{name: "Amy", age: 15}
    

    let person = {name: "Amy", age: 15};
    let someone = {...person,name: "Mike", age: 17};
    someone;  //{name: "Mike", age: 17}
    
  • Object.assign()方法使用
    assign的方法定義是 Object.assign(target, source1,source2, ···)
    用于將源對象的所有可枚舉屬性復制到target對象中。

    let sourceObj = { a: { b: 1}};
    let targetObj = {c: 3};
    Object.assign(targetObj, sourceObj);
    
    targetObj.a.b = 2;
    sourceObj.a.b;  // 2
    // 這里體現的是assign的淺拷貝
    

8. 數組

  • 1. 數組數據的基本使用

    const arr = ["人民幣","美元"] // 初始化數組
    arr.push("歐元","英鎊") 
    arr//  ["人民幣","美元","歐元","英鎊"]
    arr[2] = "港元" ;
    arr[4] = "盧布" ;
    arr//  ["人民幣","美元","港元","英鎊",'盧布']
    
  • 2. 轉換可迭代對象

    轉換 map

    let map = new Map();
    map.set('key0', 'value0');
    map.set('key1', 'value1');
    console.log(Array.from(map));// \[['key0', 'value0'],['key1',
    // 'value1']]
    

    轉換 set

    let arr = [1, 2, 3];
    let set = new Set(arr);
    console.log(Array.from(set)); // [1, 2, 3]
    

    轉換字符串

    let str = 'abc';
    console.log(Array.from(str)); // ["a", "b", "c"]
    
  • 3. 數組元素的增加與刪除

    array.push(e1, e2, ...eN) 將一個或多個元素添加到數組的末尾,并返回新數組的長度。

    const array = [1, 2, 3];
    const length = array.push(4, 5);
    

    array.unshift(e1, e2, ...eN) 將一個或多個元素添加到數組的開頭,并返回新數組的長度。

    const array = [1, 2, 3];
    const length = array.unshift(4, 5);
    

    array.pop() 從數組中刪除最后一個元素,并返回最后一個元素的值,數組為空時返回undefined。

    const array = [1, 2, 3];
    const poped = array.pop();  
    // array: [1, 2]; poped: 3
    

    array.shift() 刪除數組的第一個元素,并返回第一個元素,原數組的第一個元素被刪除。數組為空時返回undefined。

    const array = [1, 2, 3];
    const shifted = array.shift();  
    // array: [2, 3]; shifted: 1
    

    array.splice(start[, deleteCount, item1, item2, ...]) 從數組中添加/刪除元素,返回值是由被刪除的元素組成的一個新的數組,如果只刪除了一個元素,則返回只包含一個元素的數組。如果沒有刪除元素,則返回空數組。

    const array = [1, 2, 3, 4, 5];
    const deleted = array.splice(2, 0, 6);*// 在索引為2的位置插入6*
    *// array 變為 [1, 2, 6, 3, 4, 5]; deleted為[]*
    
  • 4. 數組的截取與合并

      1. 數組的截取 - array.slice(start, end) 方法,start (必填), end(選填)
        slice()通過索引位置,從數組中返回start下標開始,直到end下標結束(不包括)的新數組,該方法不會修改原數組,只是返回一個新的子數組。
        end參數如果不填寫,默認到數組最后
        // 獲取僅包含最后一個元素的子數組
        let array = [1,2,3,4,5];
        array.slice(-1); // [5]
        
        // 獲取不包含最后一個元素的子數組
        let array2 = [1,2,3,4,5];
        array2.slice(0, -1); // [1,2,3,4]
        

        注意:該方法并不會修改數組,而是返回一個子數組,如果想刪除數組中的一段元素,應該使用方法 array.splice()。

      1. 數組的合并 - array.concat([item1[, item2[, . . . [,itemN]]]])方法
        conact()是將多個數組(也可以是字符串,或者是數組和字符串的混合)連接為一個數組,返回連接好的新的數組。
      const array = [1,2].concat(['a', 'b'], ['name']);
      // [1, 2, "a", "b", "name"]
      
  • 5. 數組元素的排序

      1. array.sort()方法
        按照數值大小進行排序-升序
      [1, 8, 5].sort((a, b) => {
          return a-b; // 從小到大排序
      });
      // [1, 5, 8]
      
      按照數值大小進行排序-降序
      [1, 8, 5].sort((a, b) => {
          return b-a; // 從大到小排序
      });
      // [8, 5, 1]
      
      1. array.reverse()方法
        reverse() 方法將數組中元素的位置顛倒
      let arr = [1,2,3,4,5]
      console.log(arr.reverse())    // [5,4,3,2,1]
      console.log(arr)    // [5,4,3,2,1]
      
  • 6. 數組的遍歷與迭代

      1. array.filter(callback, thisArg)方法使用指定的函數測試所有元素,并創建一個包含所有通過測試的元素的新數組。
    // callback定義如下,三個參數: element:當前元素值;index:當前元素下標; array:當前數組
    
    function callback(element, index, array) {
        // callback函數必須返回true或者false,返回true保留該元素,false則不保留。
        return true || false;
    }
    
    const filtered = [1, 2, 3].filter(element => element > 1);
    // filtered: [2, 3];
    
      1. array.map(callback[, thisArg])方法返回一個由原數組中的每個元素調用callback函數后的返回值組成的新數組。
    let a = [1, 2, 3, 4, 5];
    let bbb = a.map((item) => {
        const key = `key${item}`
        const object = {}
        object[key] =item
        return object
    });
    console.log(bbb);  
    
      1. array.forEach(callbak)為數組的每個元素執行對應的方法。
    let a = [1, 2, 3, 4, 5];
    
    let b = [];
    // item:當前元素值;index:當前元素下標; array:當前數組
    a.forEach((item,index,array) => {
        b.push(item + 1);
    });
    console.log(b); // [2,3,4,5,6]
    
      1. array.reduce(callback[, initialValue])方法返回針對數組每項調用callback函數后產生的累積值。
    const total = [0, 1, 2, 3].reduce((sum,currentValue, currentIndex, array) => {
     return sum + currentValue;
    }, 0);
    // total is 6
    
    const flattened = [[0, 1], [2, 3], [4, 5]].reduce((a, b) => {
    return a.concat(b);
    }, []);
    // flattened is [0, 1, 2, 3, 4, 5]
    

9. 函數

  • 默認參數

    function fn(name,age=17){
        console.log(name+","+age);
    }
    fn("Amy",18);   // Amy,18
    fn("Amy","");   // Amy,
    fn("Amy");      // Amy,17
    

    只有在未傳遞參數,或者參數為 undefined 時,才會使用默認參數,null 值被認為是有效的值傳遞。

    fn("Amy",null); // Amy,null
    
  • 不定參數或可變參數

      function f(...values){
          console.log(values.length);
      }
      f(1,2);      //2
      f(1,2,3,4);  //4
    
  • 箭頭函數
    基本語法是: 參數 => 函數體

    var f = v => v;
      //等價于
      var f = function(a){
      return a;
    }
    f(100); //100
    

    如果箭頭函數返回的對象,必須使用 () 括起來

    var f = (id,name) => ({id, name});
    f(6,2);  // {id: 6, name: 2}
    

    箭頭函數體中的 this 對象,是定義函數時的對象,而不是使用函數時的對象。

    function fn(){
    setTimeout(()=>{
        // 定義時,this 綁定的是 fn 中的 this 對象
        console.log(this.a);
      },0)
    }
    
    fna = ()=>{
        console.log("我是fna 函數");
    }
    
    var a = 20;
    // fn 的 this 對象為 {a: 20}
    fn.call({a: 18});  // 18
    

    上面代碼中的call是替換this的對象,大白話就是可以使用箭頭函數外層的變量,包括方法,實際的應用場景中 ...展開運算符可以替代這個方法。
    箭頭函數常用的一個場景是:回調函數需要使用外層的this,改變變量值,或使用方法**

10. Class 類

class (類)作為對象的模板被引入,可以通過 class 關鍵字定義類。

class 的本質是 function。

它可以看作一個語法糖,使用起來更像面向對象編程的語法。

  • 類聲明
class Example {
    constructor(a) {
        this.a = a;
    }
}

Example 只能聲明一次,名字不能重復聲明

  • 實例化對象
class Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Example');
    }
    sum() {
        return this.a + this.b;
    }
}

let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);

console.log(exam1.sum(),exam2.sum()); // 3, 4

exam1.__proto__.sub = function() {
    return this.a - this.b;
}

console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2

使用實例的proto屬性改寫原型,必須相當謹慎,不推薦使用,因為這會改變“類”的原始定義,影響到所有實例。

  • decorator
    decorator 是一個函數,用來修改類的行為,在代碼編譯時產生作用。
    @testable
    export class Example2 {}
    
    // target就是class本身的實例
    function testable(target) {
        target.isTestable = true;
    }
    
    
    // 多參數修飾
    function makeSure(isOk,isCancle) {
        return function(target) {
            target.isOk=isOk;
            target.prototype.isCancle = isCancle
        }
    }
    
    @makeSure(true,false)
    export class Example3 {}
    
    上面是類修飾,以下是方法修飾,定義修飾函數有3個參數:target(類的原型對象)、name(修飾的屬性名)、descriptor(該屬性的描述對象)。
    class Math {
      @log
      add(a, b) {
        return a + b;
      }
    }
    
    function log(target, name, descriptor) {
      var oldValue = descriptor.value;
      descriptor.value = function() {
        console.log(`Calling ${name} with`,target, descriptor,arguments);
        return oldValue.apply(this, arguments);
      };
      return descriptor;
    }
    
    const math = new Math();
    
    // passed parameters should get logged now
    math.add(2, 4);
    
    修飾器執行順序:由外向內進入,由內向外執行。
    class Example {
      @logMethod(1)
      @logMethod(2)
      sum(a, b){
        return a + b;
      }
    }
    function logMethod(id) {
        console.log('先執行 logMethod'+id);
        return (target, name, desctiptor) => console.log('后執行 logMethod '+id);
    }
    
  • 構造函數與繼承
class Example{
    constructor(a, b) {
        this.a = a; // 實例化時調用 set 方法
        this.b = b;
    }
    get a(){
        console.log('getter');
        return this.a;
    }
    set a(a){
        console.log('setter');
        this._a = a; // 正確定義
        <!-- this.a = a; // 自身遞歸調用 -->
    }
}

extends 通過 extends 實現類的繼承。getter和setter 必須同時出現

class Father1 {
    constructor(){}
    // 或者都放在子類中
    get a() {
        return this._a;
    }
    set a(a) {
        this._a = a;
    }
}
class Child1 extends Father1 {
    constructor(){
        super(); // 必須放在第一行,后面是相應的代碼
        this.child = {name:"child"}
    }
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); // 2

11. 模塊的導入導出

ES6 的模塊化分為導出(export) @與導入(import)兩個模塊。模塊中可以導入和導出各種類型的變量,如函數,對象,字符串,數字,布爾值,類等。

  • export 與 import

    //   demo10.js
    
    let myName = "Tom";
    let myAge = 20;
    let myfn = function(){
        return "我的名字叫" + myName + "! 我今年" + myAge + "歲了"
    }
    let MyClass =  class MyClass {
        static a = "hello word!";
    }
    export { myName, myAge, myfn, MyClass }
    

    使用的時候有幾種導入方式

    import { myName, myAge, myfn, MyClass } from "./demo10.js";
    console.log(myfn());// 我的名字是 Tom! 我今年 20 歲了.
    console.log(myAge);// 20
    console.log(myName);// Tom
    console.log(MyClass.a );// hello world!
    
    或
    import * as all from "./demo10.js";
    console.log(all.myfn());// 我的名字是 Tom! 我今年 20 歲了.
    console.log(all.myAge);// 20
    console.log(all.myName);// Tom
    console.log(all.MyClass.a );// hello world!
    

    注意點:import 是靜態執行,所以不能使用表達式和變量。

  • export default 命令

    • 在一個文件或模塊中,export、import 可以有多個,export default 僅有一個。
    • export default 中的 default 是對應的導出接口變量。
    • 通過 export 方式導出,在導入時要加{ },export default 則不需要。
    • export default 向外暴露的成員,可以使用任意變量來接收。
    // export-default.js
    
    export default function () {
        console.log('foo');
    }
    

    上面代碼是一個模塊文件export-default.js,它的默認輸出是一個函數。

    其他模塊加載該模塊時,import命令可以為該匿名函數指定任意名字。

    // import-default.js
    
    import customName from './export-default';
    customName(); // 'foo'
    

    export default命令的本質是將后面的值,賦給default變量

12. Promise 對象

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。

  • Promise 狀態
    狀態的特點
    Promise 異步操作有三種狀態:pending(進行中)、fulfilled(已成功)和 rejected(已拒絕或已失敗)。除了異步操作的結果,任何其他操作都無法改變這個狀態。
    Promise 對象只有:從 pending 變為 fulfilled 和從 pending 變為 rejected 的狀態改變。只要處于 fulfilled 和 rejected ,狀態就不會再變了即 resolved(已定型或已完成)。

  • then 方法
    then 方法接收兩個函數作為參數,第一個參數是 Promise 執行成功時的回調,第二個參數是 Promise 執行失敗時的回調,兩個函數只會有一個被調用。
    我們首先創建一個Promise實例

    const promise = new Promise(function(resolve, reject) {
        // ... 業務代碼
    
        if (/* 異步操作成功 */){
            resolve(value);
        } else {
            reject(error);
        }
    });
    

    Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。

    Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。定義方式是這樣 then(function1,function2)

    promise.then(function(value) {
        // 這是定義的 resolve
    }, function(error) {
        // 這是定義的 rejected
    });
    

    下面我們用js中的延遲函數setTimeout模擬異步操作

    function timeout(ms) {
        return new Promise((resolve, reject) => {
            setTimeout(resolve, ms, '我延遲了兩秒');
        });
    }
    
    timeout(2000).then((value) => {
        console.log(value);
    });
    

    如果調用resolve函數和reject函數時帶有參數,那么它們的參數會被傳遞給回調函數。reject函數的參數通常是Error對象的實例,表示拋出的錯誤;resolve函數的參數除了正常的值以外,還可能是另一個 Promise 實例,比如像下面代碼:

    const p1 = new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('fail')), 3000)
    })
    
    const p2 = new Promise(function (resolve, reject) {
        setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => console.log(result))
    .catch(error => console.log(error))
    

    上述代碼中p2的resolve方法的參數是p1的Promise的實例,這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態,p1的狀態是resolved或者rejected時,p2的回調函數執行
    ?

  • Promise 的常規用法
    promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
    這是最常用的方式, 我們通過一個例子來輸出看一下執行的順序

    const promise = new Promise((resolve, reject)=> {
        console.log('我第一個執行')
        console.log(' 這里是寫邏輯代碼的地方1')// 這里是邏輯代碼
        resolve({key:"我是返回的數據1"});
    });
    
    promise.then(result => {
        console.log(result)
        return {key:"我是then之后,返回的數據2"}
    }).then(result => {
        console.log(' 這里是寫邏輯代碼的地方2')// 這里是邏輯代碼
        console.log(result)
    })
    .catch(error => {
        console.log(error)
    })
    
  • Promise.all
    Promise.all方法接受一個數組作為參數,Promise.all([p1, p2, p3])。
    p1、p2、p3都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。

    const p1 = new Promise((resolve, reject) => {
        resolve('hello1');
    })
    .then(result => result)
    .catch(e => e);
    
    const p2 = new Promise((resolve, reject) => {
        <!-- throw new Error('報錯了'); -->
        resolve('hello2');
    })
    .then(result => result)
    .catch(e => e);
    
    Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));
    

13. Generator 函數

Generator 函數可以通過 yield 關鍵字,把函數的執行流掛起,為改變執行流程提供了可能,從而為異步編程提供解決方案。

其中 * 用來表示函數為 Generator 函數,yield 用來定義函數內部的狀態。

  • 基本使用

我們來定義一個示例函數

function* test(){
    console.log("one");
    yield '1';
    console.log("two");
    yield '2'; 
    console.log("three");
    return '3';
}

let funResult = test()
f.next();
// one
// {value: "1", done: false}
 
f.next();
// two
// {value: "2", done: false}
 
f.next();
// three
// {value: "3", done: true}
 
f.next();
// {value: undefined, done: true}

這段代碼的執行順序:
第一次調用 next 方法時,從 Generator 函數的頭部開始執行,先是打印了 one ,執行到 yield 就停下來,并將yield 后邊表達式的值 '1',作為返回對象的 value 屬性值,此時函數還沒有執行完, 返回對象的 done 屬性值是 false。

第二次調用 next 方法時,同上步 。

第三次調用 next 方法時,先是打印了 three ,然后執行了函數的返回操作,并將 return 后面的表達式的值,作為返回對象的 value 屬性值,此時函數已經結束,多以 done 屬性值為true 。

第四次調用 next 方法時, 此時函數已經執行完了,所以返回 value 屬性值是 undefined ,done 屬性值是 true 。如果執行第三步時,沒有 return 語句的話,就直接返回 {value: undefined, done: true}。

  • for...of循環

for...of循環可以自動遍歷 Generator 函數運行時生成的Iterator對象,且此時不再需要調用next方法。

function print(){
    console.log('執行了打印')
    return "我才是打印"
}
function* sendParameter(){
    console.log("start");
    var x = yield print();
    console.log("one:" + x);
    var y = yield '繼續打印';
    console.log("two:" + y);
    console.log("final:" + (x + y));
}

 // 使用 for... of 相當于無參數自動執行該方法
var sendp1 = sendParameter();
for (let v of sendp1) { // v 是迭代對象 也就是yield 后面的數字或字符串或function的返回值
    console.log('for...of',v);
}


<!-- 執行的打印結果為:
start
執行了打印
for...of 我才是打印
one:undefined
for...of 繼續打印
two:undefined
final:NaN    // undefined 參與了加法計算,所以值成了NaN 
-->
  • next()傳參
function getData(){
    console.log('從網絡get獲取數據')
    setTimeout(() =>{sendp2.next({data:"我是get返回的數據"})}, 1000)
}
function postData(){
    console.log('從網絡post獲取數據')
    setTimeout(() =>{sendp2.next({data:"我是post返回的數據"})}, 1000)
}
function* requestDataFromServer(){
    console.log("start");
    var x = yield getData();
    console.log("get到的數據:",x);
    var y = yield postData();
    console.log("post到的數據:",y);
}

var sendp2 = requestDataFromServer();
sendp2.next();


<!--打印結果為:
start
從網絡get獲取數據
get到的數據: { data: '我是get返回的數據' }
從網絡post獲取數據
post到的數據: { data: '我是post返回的數據' }
-->
  • 打斷Generator函數

結束Generator函數有兩種方式,return 和 throw

return 方法返回給定值:

  • 方法提供參數時,返回該參數;
  • 不提供參數時,返回 undefined

throw 是通用關鍵字,不單純用在Generator函數,也可以放在普通的方法里做打斷或者驗證的功能

用法如下:

function* foo(){
    try{
        yield 1;
        yield 2;
        yield 3;
    }catch (e) {
        console.log('catch inner', e);
    }
}
var f = foo();
f.next();
//返回的值是 {value: 1, done: false}

f.return("foo");
//返回的值 {value: "foo", done: true} , done為true,說明迭代完成

try {
  f.throw('a');
  f.throw('b');
} catch (e) {
  console.log('catch outside', e);
}

<!-- 
執行結果為:
catch outside a
 -->

實際是怎么樣應用的,考慮以下這樣的場景:
打開淘寶登錄后,我們看到的了什么信息?

  1. 我的個人信息
  2. 我的訂單信息
  3. 我關注的商家自動為我推薦的商品

也就是說我們在登錄的時候需要獲取到這三種信息,這三種信息被淘寶封裝成了三個獲取接口,我們以 url1,url2,url3分別代表三個接口地址
登錄方式通過Token的方式,方法命名為 login()
獲取三個信息的接口分別為 getUserInfo(),getOrderInfo(),getGoods()

function getToken(token){
    console.log('獲取Token')
     setTimeout(() =>{loginVal.next("token122222222")}, 500)
}
function getUserInfo(token){
    console.log(`使用${token}獲取用戶信息`)
    setTimeout(() =>{loginVal.next({userName:'coding'})}, 500)
}

function getOrderInfo(){
    console.log(`使用${token}獲取訂單信息`)
    setTimeout(() =>{loginVal.next({orderName:'玩具槍'})}, 500)
}

function getGoods(){
    console.log(`使用${token}獲取商品信息`)
    setTimeout(() =>{loginVal.next({goodsName:'嬰兒玩具'})}, 500)
}

//為了保證登錄后信息的完整性,需要將三種數據都獲取到,我們會這樣寫

function* login(){
    try {
        const token = yield getToken();
        console.log(token);
        const userInfo = yield getUserInfo(token);
        const orderInfo = yield getOrderInfo(token);
        const goodsInfo = yield getGoods(token);
        console.log(userInfo,orderInfo,goodsInfo);

    } catch (e) {
        // 處理這個過程遇到的錯誤
    }
}

var loginVal;
// 點擊按鈕時執行的方法
function submitClick(){ 
    loginVal = login();
    loginVal.next();
}

14. async 函數

async 是 ES7 才有的與異步操作有關的關鍵字,和 Promise , Generator 有很大關聯的。

  • 基本使用
// 這是一個沒有參數的例子
async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})
  • await

await 操作符用于等待一個 Promise 對象, 它只能在異步函數 async function 內部使用。
下面是一個實現紅綠燈變換的例子

let time = 8
let intervalId
let newColor ='red 紅燈'

function changeColor(lightColor) {
    return new Promise(resolve => {
      if(intervalId)
        clearInterval(intervalId)
      intervalId = setInterval(() => {
          console.log('倒計時:'+time+'秒')
          time -= 1
          if(time<1&&lightColor){
            if(lightColor.includes('red'))
              newColor = "green 綠燈"
            else
              newColor = "red 紅燈"
            resolve(newColor);
          }
      }, 1000);
    });
}

async function changeLightAsync() {
    var x = await changeColor(newColor);
    console.log(x); 
}

changeLightAsync()

setInterval(() => {
    time = 8
    changeLightAsync();
},9000)

15. Proxy 與 Reflect

  • Proxy

一個 Proxy 對象由兩個部分組成: target 、 handler 。

var proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。
其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定制攔截行為。
看一個簡單的攔截例子:

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 666;
  }
});

proxy.gender // 666
proxy.name // 666
proxy.age // 666

當然y一個攔截器函數可以有多個攔截操作

let target = {name:'張三',age:18}
var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
      console.log('執行了apply',thisBinding)
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

target = function(x, y) {
  return x + y;
}

var fproxy = new Proxy(target, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

下面是所有的攔截操作,一共13項:

  • get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.foo和proxy['foo']。

  • set(target, propKey, value, receiver):攔截對象屬性的設置,比如proxy.foo = v或proxy['foo'] = v,返回一個布爾值。

  • has(target, propKey):攔截propKey in proxy的操作,返回一個布爾值。

  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值。

  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。

  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。

  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。

  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。

  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。

  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標對象是函數,那么還有兩種額外操作可以攔截。

  • apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

  • construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)。

上面的基本操作我們可以暫時有一個印象,我們基本上代理的都是對象的get,set,deleteProperty,construct方法等
下面我們看一些適用的場景:

  • Form表單驗證做成插件;
function createValidator(target, validator) {  
    return new Proxy(target, {
        _validator: validator,
        set(target, key, value, proxy) {
            if (target.hasOwnProperty(key)) {
                let validator = this._validator[key];
                if (!!validator(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    throw Error(`Cannot set ${key} to ${value}. Invalid.`);
                }
            } else {
                throw Error(`${key} is not a valid property`)
            }
        }
    });
}

const personValidators = {  
    name(val) {
        return typeof val === 'string';
    },
    age(val) {
        return typeof age === 'number' && age > 18;
    }
}
class Person {  
    constructor(name, age) {
        this.name = name;
        this.age = age;
        return createValidator(this, personValidators);
    }
}

const bill = new Person('Bill', 25);

// 以下操作都會報錯,我們在賦值時可以快速的用try...catch 捕捉錯誤做出響應
bill.name = 0;  
bill.age = 'Bill';  
bill.age = 15;  
  • 私有屬性
    JavaScript 或其他語言中,大家會約定俗成地在變量名之前添加下劃線 _ 來表明這是一個私有屬性(并不是真正的私有),但我們無法保證真的沒人會去訪問或修改它。
let api = {  
    _apiCase1: 'http://182.190.5.88', // 實例1
    _apiCase2: 'http://182.190.5.89', // 實例2

    /* 測試數據時使用 this._apiCase1 或者 this._apiCase2 */

    getUsers: function(){}, 
    getUser: function(userId){}, 
    setUser: function(userId, config){}
};


// 我們定義私有變量的初衷是為了不修改,但是下面依然可以執行
api._apiCase1 = 'http://182.190.5.90';

很顯然,約定俗成是沒有束縛力的,使用 ES6 Proxy 我們就可以實現真實的私有變量了

const privateKes = ['_apiCase1','_apiCase2'];
api = new Proxy(api, {  
    get(target, key, proxy) {
        if(privateKes.indexOf(key) > -1) {
            throw Error(`${key} 不存在.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(privateKes.indexOf(key) > -1) {
            throw Error(`無法為 ${key} 賦值,因為它是私有變量`);
        }
        return Reflect.get(target, key, value, proxy);
    }
});

// 以下操作都會拋出錯誤
console.log(api._apiCase1);
api._apiCase2 = '987654321'; 
  • 預警過時方法和攔截刪除操作
let dataStore = {  
    noDelete: 1235,
    oldMethod: function() {/*...*/ },
    doNotChange: "tried and true"
};

const NODELETE = ['noDelete'];  
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];  

dataStore = new Proxy(dataStore, {  
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error! ${key} 是只讀屬性.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error! ${key} 是不可刪除屬性`);
        }
        return Reflect.deleteProperty(target, key);

    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning! ${key} 已過時,請謹慎使用.`);
        }
        var val = target[key];

        return typeof val === 'function' ?
            function(...args) {
                Reflect.apply(target[key], target, args);
            } :
            val;
    }
});

// 這三個都會報錯
dataStore.doNotChange = "foo";  
delete dataStore.noDelete;  
dataStore.oldMethod();

  • Proxy.revocable()

Proxy.revocable 返回一個可取消的Proxy實例

let target2 = {};
let handler2 = {};

let {proxy:newProxy, revoke} = Proxy.revocable(target2, handler2);

newProxy.foo = 123;
newProxy.foo // 123

revoke();
newProxy.foo // Cannot perform 'get' on a proxy that has been revoked

Proxy.revocable的一個使用場景是,目標對象不允許直接訪問,必須通過代理訪問,一旦訪問結束,就收回代理權,不允許再次訪問。
實際開發中有這樣的需求:
比如 登錄時候需要判斷輸入密碼次數,如果輸入錯誤密碼超過5次,密碼輸入框變為不可用


  • Reflect

了操作對象而提供的新 API*
Reflect對象其實就是為了取代Object對象。取代原因有一下幾點:

  1. Object對象的一些內部方法放在了Reflect上面,比如:Object.defineProperty。主要是優化了語言內部的方法。

  2. 修改Object方法的返回,例如:Object.definePropery(obj,name,desc)無法定義屬性時報錯,而Reflect.definedProperty(obj,name,desc)則會返回false。

  3. 讓Object變成函數的行為,以前的:name in obj和delete obj[name],可以讓Reflect.has(name)和Reflect.deleteProperty(obj,name)替代。

  4. Reflect方法和Proxy方法一一對應。主要就是為了實現本體和代理的接口一致性,方便用戶通過代理操作本體。

下面我們寫一個比較復雜的例子,用Proxy和Reflect實現觀察者模式

// 定義一個訂閱者集合
const queuedObservers = new Set();
// 添加訂閱者
const observe = fn => queuedObservers.add(fn);
// 給對象添加代理對象,代理的set方法中進行遍歷訂閱者列表
const observable = obj => new Proxy(obj,{set});

function set(target,key,value,receiver){
        const result = Reflect.set(target,key,value,receiver);
        //遍歷訂閱者集合,依次觸發訂閱者方法
        queuedObservers.forEach(fn => fn());
        //返回訂閱者
        return result;
}

//使用Proxy實現觀察者模式[發布-訂閱者模式]
const person = observable({name:'張三',age:30});

function print(){
    console.log(`${person.name},${person.age}`);
}
function anotherPrint(){
    console.log(`你想的很對`)
}

//訂閱者集合里面加入print訂閱者
observe(print);
observe(anotherPrint)

person.name = 'miya'

Reflect對象一共有 13 個靜態方法 與 Proxy 的方法一一對應
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374