ES6知识点总结

let、const和var的作用和区别

var

  • 声明变量 ,声明的变量可以重复声明,同一作用域下,后声明的会覆盖前声明的,
  • 有变量提升的特点

let

  • 没有变量提升,必须先声明再使用,

  • 同一作用域下不能重复声明同一个变量名,会报错。可以重新赋值。

  • let声明的变量产生一个块级作用域。

    • 块级作用域:在js中成每个花括号之间的叫代码块 –{ 代码块 },{}的区域叫块级作用域,在每一个{}内let声明出来的变量只能在这个{}中访问到。
  • 暂时性死区

    • 简而言之:在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
    • 当程序的控制流程在新的作用域进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来。但因这时还未进行词法绑定,所以是不能访问的,如果访问就会报错。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时性死区
    1
    2
    3
    4
    5
    let a = 10;
    if (true) {
    console.log(a) //会报错,因为该代码块内有一个let,所以使该块区形成了一个封闭的作用域,a在使用let声明前不能被调用
    let a = 30;
    }

const

  • 没有变量提升,必须声明在使用
  • 用于声明常量,初始化时一定要赋值,声明之后不能再重复声明也不能再赋值。
    • 注意用const声明引用类型时(对象、数组):对象和数组内部的项可以修改。也不能直接给对象、数组的变量名重新赋值 –不能 obj= 10
  • 会产生块级作用域,暂时性死区。

拓展

  • 局部作用域又叫(函数作用域),全局作用域和局部作用域都是相对函数内外而定义的。

  • console.time() ……… console.timeEnd()可以控制台打印中间代码执行的时间。

  • 循环变量时循环内部的处理函数,拿到的都是循环结束之后的最终变量值

    • 解决:
      • 闭包:可以将闭包内部变量不会直接垃圾回收,而会存储在内存中,这样使用的时候就是上次存的变量值,而不是循环结束之后最终的变量值。
      • let:而let本身就 有块级作用域的特点,所以相当于闭包,在块级作用域使用的时候也会把值存到内存中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //let的应用场景

    for (var i = 0; i < 5; i++) {
    setTimeout(function() {
    console.log(i) //打印5个5
    })
    }

    for (let i = 0; i < 5; i++) {
    setTimeout(function() {
    console.log(i) // 0-4
    })
    }
    //注:若用var声明循环变量,还可用匿名函数自执行的方式解决
    for(var i = 0; i < 5; i++){
    ;(function(i){
    setTimeout(function(){
    console.log(i) // 0-4
    })
    })(i)//将i值传到匿名函数中
    }

变量的解构赋值

概念

  • ES6 允许按照一定模式,从数组对象中提取值,对变量进行赋值,这被称为解构—–要求*模式匹配

数组解构赋值

有序— 按位置对应

特点

  • 完全解构:值与变量对应

    1
    let [a,b,c] = [1,2,3];
  • 不完全解构:值比变量多

    1
    2
    3
    let [a]=[1,2,3]  --- a = 1
    //缺省
    let [,,a]= [1,2,3] --- a=3
  • 解构失败:变量比值多

    1
    2
    let [a,b,c] = [1] 
    返回值:a=1,b=undefind,c=undefind
  • 拓展默认值

    • —解构失败的情况下,变量如果有默认值的情况下就显示默认值,有值的时候显示值
    • 赋值为undefined时默认值生效,赋值为null时默认值不生效
    1
    2
    3
    4
    5
    6
    7
    let [a=1,b=2,c=3] = [10]// 返回 a=10,b=2,c=3;

    let [a=1,b=2,c=3] = [10,20,30] //返回a=10,b=20,c=30;

    let [a=1,b=2,c=3]=[10,undefined,undefined] //返回a=10,b=2,c=3

    let [a=1,b=2,c=3]=[10,null,null] //返回a=10,b=null,c=null
    • 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
    1
    2
    3
    4
    5
    let [x = 1, y = x] = []; // x=1;y=1
    let [x = 1, y = x] = [2]; //x=2;y=2
    let [x = 1, y = x] = [1,2]; //x=1;y=2
    let [x = y, y = 1] = [];//ReferenceError: y is not defined
    //最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明

适用场景

  • 数组的解构赋值,可以做数据交换

    1
    2
    3
    let a = 10 ;
    let b = 20 ;
    [a,b] = [b ,a]

对象解构赋值

无序 – 匹配属性名

特点

  • 无序集合,必须按照属性 名匹配 ,所以解构中也没有缺省的情况。
  • 完全解构,不完全解构、默认值和数组一样

作用

  • 对象解构的作用:适用于取值,和函数的参数适用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     let data = {
    username: "xiaoming",
    age: 18,
    address: "长沙",
    sex: "man"
    };
    let {
    username,
    age
    } = data;

    console.log(username, age) //xiaoming 18

注意点

  • 如果要将一个已经声明的变量用于解构赋值,必须非常小心

    1
    2
    3
    4
    5
    // 错误的写法
    let x;
    {x} = {x: 1};
    // SyntaxError: syntax error

    报错的原因:JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JS将其解释为代码块,才能解决这个问题

    1
    2
    let x;
    ({x} = {x: 1})

拓展 函数参数的解构赋值

  • 函数的参数也可以适用解构赋值

    1
    2
    3
    4
    function add([x,y]){//
    return x + y;
    }
    add([1,2]);//传入参数的那一刻,数组参数就被解构成x和y。对于函数内部的代码来说,参数就是x和y

模板字符串

语法

``两个反 引号,变量用${ 变量 }包裹 ,

优点:

  • 换行不需要使用换行符,可以直接写html结构,
  • 不需要考虑单双引号嵌套问题
  • ${}里面也可以自己写js语句;
  • 方便简洁不容易出错

对象的扩展

对象的简写

对象属性的简写

  • 属性名和属性值的变量名相同的情况下

    1
    2
    3
    4
    5
    6
    7
    let username = "xiaoming";
    let age = 18;
    let obj = {
    username,
    age
    }
    console.log(obj)

对象方法的简写

  • 省略冒号和function

    1
    2
    3
    4
    5
    6
    7
    8
    let obj = {
    eat() {
    console.log(this)
    console.log("红烧肉")
    }
    }
    obj.eat()

对象表达式

  • 一般情况下:[]使用的是变量,表达式,属性为数字时也使用[]

  • obj[]—[]里面放的是变量不是属性名,如果使用[]要获取属性名,[]里就要加字符串,也就是变量需要加引号,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let username = "hello"
    let obj = {
    username: "a",
    [username]: "b",
    10: "world"
    }
    console.log(obj) // {10: "world", username: "a", hello: "b"}
    console.log(obj.username) //a
    console.log(obj['username']) //a
    console.log(obj[username]) //b
    obj["a" + "b"] = "ab"; //添加了一个属性名为ab的属性
    console.log(obj) // {10: "world", username: "a", hello: "b", ab: "ab"}
    console.log(obj[10]) //world

对象的方法

  • 数组的拼接:cancat()

  • 对象拼接Object.assign(): 可以实现对象的浅拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 第一个参数是目标对象,从第二个参数开始就要拼接的对象
    let o1 = {
    a: 10
    };
    let o2 = {
    b: 20
    };
    Object.assign(o1, o2, {
    c: 30
    });
    console.log(o1)//{a: 10, b: 20, c: 30}
  • Object.keys() 返回的是一个数组,数组里面是对象所有的属性名

  • Object.values() 返回的是一个数组,数组里面是对象所有的值

    1
    2
    3
    4
    5
    6
    let o = {
    a: 10,
    b: 20
    }
    console.log(Object.keys(o)) //["a", "b"]
    console.log(Object.values(o)) //[10, 20]

对象属性的设置

  • 获取对象中某个属性的描述对象;

  • 设置对象中的某一个属性的特性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 数据劫持 vue
    const obj = {
    a: 10,
    b: 20
    }

    // 读取对象中的某个属性的设置
    console.log(Object.getOwnPropertyDescriptor(obj, "a"))



    Object.defineProperty(obj, "a", {
    writable: false, // 值是否可以重写
    enumerable: false, // 是否可以循环遍历
    value: "world",
    configurable: false // 是否可以重新修改属性的设置
    })

    obj.a = "hello";
    console.log(obj)

    for (let key in obj) {
    console.log(key)
    }

函数拓展

函数默认值

  • 直接在形参上进行赋值

    1
    2
    3
    4
    5
    6
    function fn(x = "hello") {
    console.log(x)
    }

    fn(10)

  • 特点

    • 不传实参,默认值生效
    • 传递undefined,默认值生效
    • 传递null,默认值不生效
  • 注意参数的位置

    在参数传递过程中,要注意位置问题;

    如果是最后一个参数的话,可以忽略不传递实参;

    但是其他位置的话,要用undefined;

    • 解决方法1:有默认值的参数放到最后
    • 解决方法2:形参和实参都以对象的形式(对象解构赋值)

rest剩余参数

  • 语法:…变量名

    • 返回的是一个数组,接收的是没有形参接收的值
    • 剩余参数必须写在形参的最后。
    1
    2
    3
    4
    5
    6
     function fn(x, y, ...rest) {
    console.log(rest) //[3,4,5]
    console.log(Array.isArray(rest)) //true
    }

    fn(1, 2, 3, 4, 5)

箭头函数*

语法:

  • 箭头左边(参数)

    • 一个参数的情况()括号可以省略
  • 箭头右边(函数的执行代码)

    • 执行代码只有一句话的时候{}和return可以省略,但是对象要注意,加上()避免代码歧义。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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;
    };


    //由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
    // 报错
    let getTempItem = id => { id: id, name: "Temp" };

    // 不报错
    let getTempItem = id => ({ id: id, name: "Temp" });

特点:

  • this指向的是定义this所在的对象,并且call、apply、bind是无法改变this的指向,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let obj = {
    fn() {
    console.log(this)
    },
    f1: () => {
    console.log(this) // window
    }
    }
    obj.fn() //this
    obj.f1() // window
    • this是固定的=>有利于封装回调函数;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //将DOM事件的回调函数封装在一个对象里
    var handler = {
    id: '123456',

    init: function() {
    document.addEventListener('click',
    event => this.doSomething(event.type), false);
    },

    doSomething: function(type) {
    console.log('Handling ' + type + ' for ' + this.id);
    }
    };
    //使用了箭头函数,导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象
  • 箭头函数不能当做构造函数使用,不能使用new命令

  • 箭头函数没有prototype显示原型;

  • 箭头函数不能使用arguments,在遇到不定参的情况使用剩余参数代替(…变量名);

不适用场景

  • 对象中方法的简写不要使用箭头函数

  • 事件绑定函数,要谨慎使用箭头函数

严格模式

  • 关键字 use strict ; 必须放在第一行

  • 规则

    • 变量先声明再使用
    • 不允许指向全局对象,返回的是undefined
    • 对arguments的限制:
      • 不允许重新赋值
      • 不再追踪参数的变化
      • arguments.callee返回的是函数的本身,但是在严格模式下,不允许使用;
    1
    2
    3
    4
    5
    6
    7
    8
    9
     function fn(num){
    // arguments=10;
    num++;
    console.log(num)
    console.log(arguments)
    }

    fn(1,2,3)

  • 对于只读属性重新赋值是会报错的

数组拓展

拓展运算符…

  • 拓展运算符是三个点(…)

  • 剩余参数的逆运用,将数组转换为参数序列

数组运用

  • 数组去重

  • 拉平多维数组(推荐使用flat())

  • 实现浅拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //数组去重
    let arr = [1,1,2,2,2,3,4,4,4];
    console.log([...new Set(arr)])

    //拉平多维数组
    let arr = [
    [1, 2, 3],
    [4, 5]
    ];
    let newArr = []
    arr.forEach(function(item) {
    newArr.push(...item)
    })
    console.log(newArr)

    //实现浅拷贝
    let arr = [1, 2, 3];
    let arr2 = [...arr];
    arr[0] = "hello";
    console.log(arr, arr2)

对象运用

  • 对象浅拷贝

    1
    2
    3
    4
    5
    6
    7
     let obj = {
    a: 10,
    b: 20
    }

    let obj2 = {...obj
    }
  • 模拟vuex

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    let mapState = {
    userInfo: {
    username: "xiaoming",
    age: 18
    },
    admin: {
    username: "guanliyuan"
    }
    }

    let mapMutations = {
    run() {
    console.log("xiaoming 正在跑")
    },
    eat() {
    console.log("排骨")
    }
    }

    let vm = {
    data: {
    ...mapState //浅拷贝mapState,这样在mapState中可以随需求添加数据
    },
    methods: {
    ...mapMutations
    }
    }

    console.log(vm)

数组方法的扩展

  • Array.of() 将参数内的数据转为数组,弥补new Array()的不足

  • find()和findIndex()

    • find 返回符合条件的第一个成员
    • findIndex 返回符合条件的第一个成员的下标索引
  • Includes() 判断是否含有某一个成员(在字符串中也可以使用);返回的是一个布尔值;

  • Array.from() 伪数组转真数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let arr = new Array(5);
    console.log(arr) //(5) [empty × 5]

    let arr2 = Array.of(5);
    console.log(arr2) //[5]

    let arr3 = Array.of(true);
    console.log(arr3) //[true]

    let arr4 = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
    let index = arr4.findIndex((item, index, obj) => {
    return item > 3
    })
    console.log(index) //3 (第一个满足条件的下标是3)

    let x = arr4.find((item, index, obj) => {
    return item > 3
    })
    console.log(x) //4 (第一个满足条件的是4)


    let arr5 = ["a", "b", "c"];
    console.log(arr5.includes("hello")) //false

Symbol()

概念和作用

  • ES6新增了Symbol数据类型(是基础数据类型),它用来生成一个独一无二的值

  • Symbol数据常用来给对象属性赋值,让对象属性具备唯一性,不容易被覆盖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    obj{
    a: 10,
    a: 20
    }
    console.log(obj)
    //因为obj中属性名a冲突了,所以打印出来的结果会是
    // obj{ a: 20}

    //因此,为了避免这种情况,可以用symbol当作属性名

    obj{
    [Symbol()]:10,
    [Symbol()]:20
    }
  • 注:括号内可以加字符串,但只是对该Symbol进行解释说明(字符串相同时,两个symbol也不相等)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let s1 = Symbol('s1');
    let s2 = Symbol('s2');
    console.log(s1, s2) //Symbol(s1) Symbol(s2)
    console.log(s1.description) //s1
    console.log(s1 === s2) //false

    let s3 = Symbol("s3")
    let obj = {
    [Symbol()]: 10,
    [Symbol()]: 20,
    [s3]: 30
    }
    console.log(obj)//{Symbol(): 10, Symbol(): 20, Symbol(s3): 30}

  • 以 Symbol 值作为键名,不会被常规方法遍历得到(见7.3拓展)。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。

实例: 消除魔术字符串

  • 魔术字符串 在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或数值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function getArea(shape, options) {
    let area = 0;

    switch (shape) {
    case 'Triangle': // 魔术字符串
    area = .5 * options.width * options.height;
    break;
    /* ... more code ... */
    }

    return area;
    }

    getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串

    上面代码中,字符串Triangle就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。

  • 用Symbol值解决

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const shapeType = {
    triangle: Symbol()
    };
    function getArea(shape, options) {
    let area = 0;
    switch (shape) {
    case Symbol():
    area = .5 * options.width * options.height;
    break;
    }
    return area;
    }

    getArea(Symbol(), { width: 100, height: 100 });

Symbol的方法

s.description

获取Symbol()字符串的值

Symbol.for()

  • Symbol.for(‘’) 搜索是否有以该参数为名称的Symbol值

    • 有则返回这个Symbol值,否则新建一个以该字符串为名称的Symbol值,并将其注册到全局
    • 如果里面的字符串的内容相同,Symbol也是相同
    1
    2
    3
    4
    5
    6
    7
    8
    let s1 = Symbol.for("s1")
    let s2 = Symbol.for("s1")
    let s3 = Symbol.for('s4')
    console.log(s1) //Symbol(s1)
    console.log(s2) //Symbol(s1)
    console.log(s3) //Symbol(s4)
    console.log(s1 === s2) //true

  • Symbol.for()Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,

    • 前者会被登记在全局环境中供搜索,后者不会
    • Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值
    • 比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
    1
    2
    3
    4
    5
    6
    7
    Symbol.for("bar") === Symbol.for("bar")
    // true

    Symbol("bar") === Symbol("bar")
    // false

    //Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值

Symbol.keyFor()

  • 返回一个已登记的 Symbol 类型值的key

    1
    2
    3
    4
    5
    6
    let s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"

    let s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined
    //s2属于未登记的Symbol值

拓展 属性名的遍历

  • Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

  • Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const obj = {};
    const obj1 = {'a':10, 'b':20}
    let a = Symbol('a');
    let b = Symbol('b');

    obj[a] = 'Hello';
    obj[b] = 'World';

    for(let key in obj){
    console.log(key) //无输出
    }
    for(let key in obj1){
    console.log(key) //a b
    }

    const objectSymbols = Object.getOwnPropertySymbols(obj);
    console.log(objectSymbols)// [Symbol(a), Symbol(b)]
    const objNames = Object.getOwnPropertyNames(obj);
    console.log(objNames) //[]

set和map

set

  • Set()本身是一个构造函数,用来实例化Set数据类型(类似于数组的数据结构,特点:不允许有重复项。

  • set实例的操作方法—用来操作数据

    let s = new Set();

    • s.size : 返回长度

    • s.add() : 添加某个值,返回set结构本身

    • s.delete() : 删除值,删除成功返回true,否则返回false

    • s.has() : 判断是否是set的成员,返回布尔值

    • s.clear() : 清除所有的成员,没有返回值。

  • set实例的遍历方法—-遍历成员

map

  • Map对象保存键值对。是类似对象的数据解构。任何值(对象或者原始值) 都可以作为一个键或一个值

  • 常用方法:

    • m.size 成员的个数
    • m.set([1, 2, 3], “content3”) 添加一个内容
    • m.get(属性) 获取对应的值
    • m.has(属性) 判断属性是否存在
    • m.delete(属性) 删除属性
    • m.clear() 删除所有的属性
  • Map和Object的区别

    • 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
    • Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
    • Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
    • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

promise

promise概念,then、catch方法

  • 概念和理解:

    • 是ES6解决异步回调问题的一种解决方案

    由于js是单线程,很多异步操作都是依靠回调方法实现的,这种做法在逻辑比较复杂的回调嵌套中会相当复杂,也叫做回调地狱;

    • Promise对象可以理解为一次执行的异步操作,使用Promise对象之后,使用一种链式调用的方式来组织代码,让程序更具备可读性,可维护性

    =>可以将异步操作以同步的操作的流程表达出来,避免了层层嵌套的回调函数

    • promise和回调函数一样,都是要解决数据的传递和消息发送问题,promise中的then一般对应成功后的数据处理,catch一般对应失败后的数据处理。
  • 语法:

    promise是一个构造函数,使用时一般都需要new 一个实例对象。

    接受一个函数作为参数,函数中也有两个参数(resolve,reject),resolve将异步操作成功状态下的结果返回。reject将异步操作失败状态写的结果返回。

1
2
3
4
5
6
7
let p = new Promise(function(resolve,reject)=>{
if(ture){
resolve('成功')
}else{
reject('失败')
}
});//p就是实例化的promise对象
  • then方法:接受两个回调函数作为参数,第一个回调函数是promise状态为resolve状态下调用,第二个时reject状态下调用。
1
2
3
4
5
p.then(res=>{//第一个回调函数res是个参数,接收resolve括号里返回的结果
console.log(res)
},err=>{//第二个回调函数,res是个参数,接收resolve括号里返回的结果
console.log(err)
})
  • catch方法:promise失败状态调用。 同时也可以捕获到一些编写错误的代码,让错误以字符串的形式体现,不报错影响后续代码执行。
1
2
3
p.catch(err=>{
console.log(err)
})

Promise特点

  • 对象的状态不受外界影响

    • 三 个状态:进行中pending,已成功fulfilled,已失败rejected

      pending表示程序正在执行但未得到结果,即异步操作没有执行完毕,fulfilled表示程序执行完毕,且执行成功,rejected表示执行完毕但失败;

      这里的成功和失败都是逻辑意义上的;并非是要报错

    • 只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态

    • 一旦创建就会立即执行 ,处于进行中,内部直接执行的为同步,

    • resolve,reject—进行中转为了成功或失败,再使用then方法调用的是属于异步。

    • 没有调用resolve,reject时处理进行中状态,进行中下没有状态返回。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

    • Promise状态改变只有两种可能: pending =>fulfilled pending=>rejected
    • 一旦这两种情况发生,状态就凝固了,会一直保持这个结果,如果改变已经发生,再对Promise对象添加回调函数,也会立即得到这个结果
1
2
3
4
5
6
7
let p1 = new Promise((resolve,reject)=>{});
let p2 = new Promise((resolve,reject)=>{
resolve(p1)
})
p2.then(res=>{
console.log(res)//此时没有结果,因为p1没有resolve或者reject调用此时是进行中的状态,没有返回值,所有p2返会p1时也没有东西。
})
  • Promise里面同步和异步的判定

    • 里面是一个同步任务(状态为pending)
    • 调用resolve/reject方法,状态就会发生改变
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    console.log(1)
    let p = new Promise((resolve, reject) => {
    console.log("promise") //同步任务 => 进行中
    resolve("成功")
    })
    p.then(res => {
    console.log(123)
    console.log(res) // 异步任务 (微任务)
    })

    console.log(2)
    //输出顺序 1 promise 2 123 成功

promise的链式操作

  • 必须在前一个then有return返回值,如果return是一个Promise对象,then 就接收的是promise对象中成功状态下的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fn(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(n)
}, 1000)
})
}

fn(5).then(res => {
console.log(res)
return fn(++res)
}).then(res => {
console.log(res)
return fn(++res)
}).then(res => {
console.log(res)
})

Promise.all()和Promise.race()

  • Promise.all() 处理并发 ,所有值就一起返回
  • Promise.race() 返回请求完成最快的—–(一般用于设置请求超时提示)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一个请求如果超过500ms,返回请求超时提示信息
        let p1 = new Promise(resolve => {
            setTimeout(() => {
                resolve("我是正确的请求")
            }, 300)
        })
        let p2 = new Promise(resolve => {
            setTimeout(() => {
                resolve("请求超时")
            }, 500)
        })
        Promise.race([p1, p2]).then(res => {
            console.log(res)
        })
  • 错误信息的处理
    • 直接在promise.all().catch(),只能返回错误信息,其他正确的信息无法返回。
    • 谁报错谁自己处理,其他信息可以正确返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let p1 = new Promise(resolve => {
            setTimeout(() => {
                resolve("p1")
            }, 500)
        })
        let p2 = new Promise(resolve => {
            setTimeout(() => {
                resolve("p2")
            }, 3500)
        })
        let p3 = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject("p3")
            }, 1000)
        }).catch(err => {
            console.log(err)
        })

        // 要求所有的值是一起返回的
        Promise.all([p1, p2, p3]).then(res => {
                console.log(res)
            })
            .catch(err => {
                console.log(err)

iterator和for…of循环

概念和作用

  • 概念:

在集合的数据结构中,数组,对象,set,map 需要统一接口,来进行处理。Iterator就是这个机制,为不同的数据结构提供统一的接口,任何的数据结构,只要部署了iterator就可以进行循环遍历。

  • 作用
    • 1.为各种数据结构提供统一的、简便的访问接口,
    • 2.使数据结构的成员能够按某种次序排列,
    • 3.为for…of…提供消费
  • Iterator的原理
    • (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
    • (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
    • (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
    • (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束,值为true的时候表示遍历结束

  • 模拟Iterator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let arr = [1,2,3];
    function myIterator(arr){
    let index = 0;
    return {
    next(){
    return index < arr.length?{
    value:arr[index++],//因为闭包了,所以index++会储存在这个函数的作用域中
    done:false
    }:{
    value:undefined,
    done:true
    }
    }
    }
    }
    let arr_iter = myIterator(arr);
    console.log(arr_iter.next()) //{value: 1, done: false}
    console.log(arr_iter.next()) //{value: 2, done: false}
    console.log(arr_iter.next()) //review.html:28 {value: 3, done: false}
    console.log(arr_iter.next()) //review.html:29 {value: undefined, done: true}

部署Iterator接口

  • Symbol.iterator属性(默认Iterator接口)

    在数据结构上部署iterator接口表现形式为,给对象或数组等集合增加Symbol.iterator属性,属性的内容是一个根据iterator接口规范自行实现的方法

    => 一个数据结构只要有了Symbol.iterator属性,即是“可遍历的”

    1
    2
    3
    4
    5
    6
    let arr = [1, 2, 3];
    let arr_iterator = arr[Symbol.iterator]();
    console.log(arr_iter.next()) //{value: 1, done: false}
    console.log(arr_iter.next()) //{value: 2, done: false}
    console.log(arr_iter.next()) //review.html:28 {value: 3, done: false}
    console.log(arr_iter.next()) //review.html:29 {value: undefined, done: true}
  • 原生具备Iterator接口的数据结构

    Array
    Map
    Set
    String
    TypedArray
    函数的 arguments 对象
    NodeList 对象

    调用这个属性,就能得到遍历器对象

  • 对象部署Iterator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    obj[Symbol.iterator] = function(){
    let index = 0;
    return {
    next: () =>{ //如果是用function()则this输出的是next方法
    // console.log(this); //对象本身
    // console.log(Object.keys(this)) //对象的所有属性 => 数组
    // console.log(Object.keys(this)[index]) // 获取数组中对应索引下标的值
    // console.log(obj[Object.keys(this)[index]]) //获取对应的属性名的值
    if(index < Object.keys(this).length){

    return {value: obj[Object.keys(this)[index++]],
    done: false}
    }else{
    return {value: undefined,
    done: true}
    }
    }
    }
    }
    let y = obj[Symbol.iterator]()
    console.log(y.next())
    console.log(y.next())
    console.log(y.next())

for…in 和 for…of循环

for…of

  • 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。

  • 也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of 和 for…in的区别

  • for…in

    • 返回的是索引值,是以字符串的形式
    • 不一定是按照一定的顺序返回的
    • 不仅可以遍历本身的方法和属性,还可以遍历原型上的方法和属性
  • for…of

    • 返回的是
    • 是按照一定的顺序进行遍历
    • 只能遍历本身的属性和方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var myObject = {name:'zhangsan',age:100};
    Object.prototype.sayHello = function(){
    console.log('Hello');
    }
    Object.prototype.str = 'World';
    for(let key in myObject){
    console.log(key) //name age sayHello str
    }


    let arr = [3, 5, 7];
    arr.foo = 'hello';

    for (let i in arr) {
    console.log(i); // "0", "1", "2", "foo"
    }

    for (let i of arr) {
    console.log(i); // "3", "5", "7"
    //for...of方法不会返回arr的foo属性
    }

generator

概念和语法

概念

  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
  • 执行Generator函数会返回一个遍历器对象 => 是个遍历器对象生成函数。
    返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态
  • 形式上:Generator是一个普通函数

语法

  • function关键字与函数名之间有一个星号;

  • 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    1
    2
    3
    4
    5
    6
    7
    function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }

    var hw = helloWorldGenerator();
  • 调用该函数返回的是一个遍历器对象(指针指向内部对象),必须调用遍历器对象的next的方法,指针才能移向下一个对象;
    每调用一次next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function* fn() {
    console.log('a');
    console.log('b')
    yield 'hello';
    yield 'world';
    return 'ending';
    }
    let f_i = fn();
    console.log((f_i).next()) // a b {value:"hello",done:false}

Yield表达式

  • 暂停执行函数的关键字

    由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。

    yield表达式就是暂停标志。

  • 运行逻辑(案例详见11.3的拓展题)

    • (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    • (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
    • (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    • (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

    不用yield表达式时,就变成了一个单纯的暂缓执行函数(调用next方法才能执行)

next方法的参数

  • yield表达式本身没有返回值,或者说总是返回undefined

    1
    2
    3
    4
    5
    function*fn(){
    yield;
    }
    let fn_i = fn();
    console.log(fn_i.next()); //{value: undefined, done: false}
  • next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function* fn() {
    let a = yield "hello";
    console.log(a)
    }

    let f_i = fn();
    console.log(f_i.next()) //{value: "hello", done: false}
    f_i.next(10) //10

  • 拓展题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     function* fn1(x) {							//行1
    let y = 20 / (yield x + 100) //行2
    console.log(y) //5 //行3
    let z = yield y / 2 //行4
    console.log(z) //6 //行5
    return x + y + z //行6
    }
    let f_i = fn1(5)
    console.log(f_i.next()); //{value: 105, done: false}
    //第一次调用next,返回第一个yield(行2)后面的表达式结果(一个遍历器对象) {value: 5+100, done: false}

    console.log(f_i.next(4)); //{value: 2.5, done: false}
    //第二次调用next,继续执行行3,此时由于next的参数4传给了上一个yield表达式(行2)的返回值,因此 y = 20 / 4 = 5。而console.log(f_i.next(4))打印的是行4中yield的返回值 {value: 5/2, done: false}


    console.log(f_i.next(6)); //{value: 16, done: true}
    //第三次调用next,同理继续执行 行5,此时由于next的参数是6,传给了行4中yield后的返回值,因此 z = 6;而console.log(f_i.next(6))打印的是最后return的遍历器对象 value = x+y+z = 5+5+6 = 16
  • 作用:可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

async函数 async…await

概念

  • async函数是 Generator 函数的语法糖
  • 将generator函数的*换成了 async,将yield 替换成 await
  • async函数返回的是一个Promise对象,可以 使用promise对象的方法。可以看作多个异步操作,包装成的一个 Promise 对象

基本用法

  • async函数返回的是一个Promise对象,可以 使用promise对象的方法

    • 使用then方法添加回调函数获取return后面的值
    • 使用catch 来捕获错误信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    async function fn() {
    return "hello"
    }
    console.log(fn()); //Promise {<resolved>: "hello"} //返回的是一个Promise对象
    fn().then(res => {
    console.log(res) //"hellp"
    })

    //捕获错误信息
    async function fn() {
    let obj = {};
    obj.run()
    return "hello"
    }

    fn().then(res => {
    console.log(res)
    }).catch(err => {
    console.log(err)//TypeError: obj.run is not a function
    })

await()方法:一般情况下是结合promise对象使用。

  • 只能在async函数中使用;

  • 函数外面访问不到await及后面的表达式,如果需要在函数外部获取就需要在await前面加return。

  • await后面如果接的是promise对象,它拿到的是promise成功状态下的值(不需要再使用then),如果接的是其他类型就直接返回。

    • await命令就是内部then命令的语法糖。

    • 必须等待内部所有await命令后面的Promise对象执行完,才会发生状态改变,才会执行then方法指定的回调函数

    • 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

      1
      2
      3
      4
      5
      async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world'); // 不会执行
      }
      f().then(res=>{console.log(res)}).catch(err=>{console.log(err);}) //出错了
    • 如果希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      async function f() {
      try {
      await Promise.reject('出错了');
      } catch(er) {
      }
      return await Promise.resolve('hello world');
      }

      f().then(res => console.log(res))
      // hello world
  • async函数内部代码时同步执行的,执行时如果碰到await,就会先返回;等待await后面的表达式出结果之后再继续执行函数体内后面的语句,但是await不会阻塞async函数外的代码,await等待过程中函数外的代码正常向下同步执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     let p = new Promise((resolve) => {
    setTimeout(() => {
    resolve("promise")
    }, 2000)
    })


    console.log("async 开始")
    async function fn() {
    console.log(1);
    let a = await p;
    console.log(a);
    console.log(2);
    return 3;
    }

    fn().next(res=>{
    console.log(res)
    })
    console.log("async 结束")
    //async 开始
    //1
    //async 结束
    //(两秒后)promise
    //2
    //3

实例: 解决回调地狱(按顺序完成异步操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function http({
type = "get",
url,
data = {}
}) {
return new Promise((resolve, reject) => {
$.ajax({
type,
url,
data,
success: function(response) {
resolve(response)
}
});
})
}

async function demo() {
let res1 = await http({
url: "http://106.13.114.114:5000/api/firstCategory"
})
let firstId = res1.list[0][2].firstId;
let res2 = await http({
url: "http://106.13.114.114:5000/api/secondCategory",
data: {
firstId
}
})
console.log(res2)
}

demo()

class类

概念和语法

概念

  • 类和对象:

    类:对象的类型模板,确定对象中共有的属性和行为

    对象:类创建的实例,具体的某一个事物,一切皆为对象

  • ES6引入了Class(类)这个概念,通过class关键字,可以定义类

  • 一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法

语法

  • class关键字

    • 构造函数本身方法或者是属性要写在constructor方法里面
    • 原型上的方法或者是属性直接写在class里面即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Class Person{//定义类的构造函数
    construtor(name,age){
    this.name = name;
    this.age = age;
    }
    //定义一般的方法
    eat(){
    console.log('吃饭饭')
    }
    }

    let P = new Person('xx','18');
    p.eat()
  • constructor方法

    • 是类的默认方法,通过new命令生成对象实例时,自动调用该方法
    • 一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
    • 默认返回实例对象(即this)
  • 事实上,类的所有方法都定义在类的prototype属性上面。

  • 取值函数(getter)和存值函数(setter)(了解)

    与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class MyClass {
    constructor() {
    // ...
    }
    get prop() {
    return 'getter';
    }
    set prop(value) {
    console.log('setter: '+value);
    }
    }

    let inst = new MyClass();

    inst.prop = 123;
    // setter: 123

    inst.prop
    // 'getter'

静态方法

  • 实例化方法和属性:(成员)

    类相当于实例的原型(实例化对象的__proto__指向类的prototype原型),所有在类中定义的方法,都会被实例继承

        必须**通过实例化对象进行调用**
    
  • 静态方法和静态属性:

    在方法和属性前面加关键字static,表示该方法不会被实例继承,直接通过类进行调用

    对于一些通用性的属性或方法,可以考虑设置为静态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Foo {
    static classMethod() {
    return 'hello';
    }
    }

    Foo.classMethod() // 'hello'

    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
  • 注意:如果静态方法包含this关键字,这个this指的是类,而不是实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Foo {
    static bar() {
    this.baz();
    }
    static baz() {
    console.log('hello');
    }
    baz() {
    console.log('world');
    }
    }

    Foo.bar() // hello
    //静态方法bar调用了this.baz, 这里的this指的是Foo类,而不是Foo的实例;等同于调用Foo.baz
    //静态方法可以与非静态方法重名

继承

语法

  • extends 关键字

  • 子类里,如果不写constructor是可以,默认可以添加,但是如果写入constructor函数就必须要加入super();super调用父类的constructor;super必须写constructor的第一行;

  • 不但可以继承实例化的方法和属性,静态的方法和属性也是可以继承;

  • super

    一种是方法,一种是对象;

    • 作为方法是调用父类的constructor;
    • 作为对象来说,就是在子类的方法里面调用父类的方法;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Parent {
    constructor(name,age) {
    this.name = name;
    this.age = age
    }
    }

    class Me extends Parent {
    constructor(name,age,sex) {
    super(name,age) // 调用父类的constructor
    this.sex = sex
    }
    }
    let p = new Parent("rose",40)
    let m = new Me("joth",18,'male');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class  Parent {            
    constructor()  {                
    this.x  = 1;            
    }            
    print()  {                
    console.log(this.x);            
    }        
    }


    class  Me  extends  Parent  {            
    constructor()  {                
    super();                
    this.x  = 2;            
    }            
    m()  {   
    super.print();//在子类的方法里调用父类的方法            
    }        
    }


    let  b  =  new  Me();        
    b.m() //2
  • 父类的静态方法,可以被子类继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Foo {
    static classMethod() {
    return 'hello';
    }
    }

    class Bar extends Foo {
    }

    Bar.classMethod() // 'hello'

拓展:类的prototype属性和__proto__属性

  • 大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

    • 子类的__proto__属性,表示构造函数的继承,总是指向父类。
    • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A {
    }

    class B extends A {
    }

    B.__proto__ === A // true //作为一个对象,子类(B)的原型(__proto__属性)是父类(A)

    console.log(B.prototype) //A {constructor: ƒ}
    B.prototype.__proto__ === A.prototype // true //作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。
  • 实例的__proto__属性

    子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Parent {
    constructor(name,age) {
    this.name = name;
    this.age = age
    }
    }

    class Me extends Parent {
    constructor(name,age,sex) {
    super(name,age) // 调用父类的constructor
    this.sex = sex
    }
    }
    let p = new Parent("rose",40)
    let m = new Me("joth",18,'male');
    console.log(m.__proto__);//Parent {constructor: ƒ}
    console.log(m.__proto__ === Me.prototype); //true
    console.log(m.__proto__.__proto__ === p.__proto__); //true