美文网首页
ES6 学习教程与demo-case总结

ES6 学习教程与demo-case总结

作者: 云顶天宫写代码 | 来源:发表于2019-10-18 11:16 被阅读0次

学习序

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)

相关文章

网友评论

      本文标题:ES6 学习教程与demo-case总结

      本文链接:https://www.haomeiwen.com/subject/xelimctx.html