JS-ES6

177 阅读9分钟

class(ES6)

  • ES6(ECMAScript2015)新标准使用class定义类
  • 本质构造函数原型链的语法糖

定义类的方法:

  • 类声明class Person {}
  • 类表达式 var Animal = class {}

类的构造函数

  • 每个有且仅有一个自己的构造函数constructor
  • 当通过new操作一个的时候会调用这个构造函数
    1. 在内存中创建一个新对象(空对象)
    2. 这个对象内部的[[prototype]]属性会被赋值为该prototype属性
    3. 构造函数内部this,会指向创建出来的新对象
    4. 执行构造函数内部代码(函数体代码
    5. 如果构造函数没有返回对象,则将创建出的新对象返回

类的实例方法

  • 通过创建出来的对象进行访问

    • ...
          constructor(name, age) {
              this.name = name;
              this.age = age;
              this._address = "广州市";
          }
      	// 实例方法
        	// 通过创建出来的对象进行访问 p1.eating()
          eating() {
              console.log(this.name + "在吃饭");
          }
      ...
      
      var p1 = new Person("zzy", 22);
      p1.eating() // zzy在吃饭
      

类的访问器方法

  • 通过创建出来的对象进行访问

    • ...
          constructor(name, age) {
              this.name = name;
              this.age = age;
              this._address = "广州市";
          }
        	// 实例方法
      	// 通过创建出来的对象进行访问 p1.address
          get address() {
              console.log("拦截访问操作");
              return this._address;
          }
      ...
      
      console.log(p1.address); // 广州市
      p1.address = "北京市";
      console.log(p1.address); // 北京市
      

类的静态方法(类方法)

  • 通过类名进行访问

    • var names = ['aa', 'bb', 'cc']
      ...   
      	constructor(name, age) {
              this.name = name;
              this.age = age;
              this._address = "广州市";
          }
          // 类的静态方法(类方法)
          // 通过类名进行访问 Person.randomPerson()
          static randomPerson() {
              var nameIndex = Math.floor(Math.random() * names.length)
              var age = Math.ceil(Math.random() * 100)
              var name = names[nameIndex]
              return new Person(name, age)
           }
      ...
      
      for(var i = 0; i < 50; i++) {
        console.log(Person.randomPerson());
      }
      

类的继承(extends)

  • ES6中新增了extends关键字,可以方便我们实现继承

继承内置类

  • class MyArray extends Array {
        firstItem() {
            return this[0]
        }
        lastItem() {
            return this[this.length - 1]
        }
    }
    
    var arr = new MyArray(1,2,3)
    console.log(arr.firstItem()); // 1
    console.log(arr.lastItem()); // 3
    

类的混入(mixin)

  • JS的类只支持单继承:也就是只能有一个父类

  • 混入mixin)可以在一个类中添加更多相似的功能

  • class Person {}
    
    function mixinRunning(BaseClass) {
        class NewClass extends BaseClass {
            running() {
                console.log("running");
            }
        }
        return NewClass;
    }
    
    function mixinEater(BaseClass) {
        return class extends BaseClass {
            eating() {
                console.log("eating");
            }
        };
    }
    
    // JS中只能有一个父类: 单继承
    class Student extends Person {}
    
    var NewStudent = mixinEater(mixinRunning(Student));
    var ns = new NewStudent();
    ns.running(); // running
    ns.eating(); // eating
    
    

react中class的使用技巧(高阶组件)

  • image.png

super关键字

  • *在子(派生)类构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数

  • super使用位置

    • 子类的构造函数

      • // 调用父对象/父类的构造函数
        super([arguments])
        
    • 实例方法

      • // 调用父对象/父类上的方法
        super.functionOnParent([arguments])
        
    • 静态方法

      • 静态方法不是放到原型上的,而是和原型属性同级

        // 调用父对象/父类上的方法
        super.functionOnParent([arguments])
        
    • class Person {
          constructor(name, age) {
              this.name = name;
              this.age = age;
          }
          running() {
              console.log(this.name + 'running~');
          }
          personMethod() {
              console.log('逻辑1');
          }
          static personStaticMethod() {
              console.log('personStaticMethod');
          }
      }
      
      class Student extends Person {
          // 实现继承的话,必须在this或者return前调用super()
          constructor(name, age, sno) {
              super(name, age)
              this.sno = sno
          }
          studying() {
              console.log(this.name + 'studying~');
          }
          // 子类对父类方法的重写,不使用父类中的方法
          running() {
              console.log(this.name + '子类running~');
          }
      
          //重写personMethod方法
          personMethod() {
              // 复用父类中实例方法的处理逻辑
              super.personMethod()
              console.log('逻辑2');
          }
      
          // 重写静态方法
          static personStaticMethod() {
              // 复用父类中静态方法的处理逻辑
              super.personStaticMethod()
              console.log('子类的personStaticMethod');
          }
      }
      
      var stu = new Student('aaa', 22, 3171242)
      console.log(stu); // Student { name: 'aaa', age: 22, sno: 3171242 }
      console.log(Object.getOwnPropertyDescriptors(stu.__proto__)); // 只有studying方法
      console.log(Object.getOwnPropertyDescriptors(stu.__proto__.__proto__)); // 只有running方法
      stu.running() // aaarunning~
      stu.studying() // aaastudying~
      stu.personMethod() // 逻辑1  逻辑2
      Student.personStaticMethod() // personStaticMethod 子类的personStaticMethod
      

babel(ES6->ES5)

  • 在线网站:babeljs.io
  • 本地下载babel

ES6->ES5(class)

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    running() {
        console.log(this.name + "running~");
    }
}

// babel转换(IE10)
"use strict";

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", { writable: false });
    return Constructor;
}

// 立即执行函数,结果返回Person,相当于Person,为了防止与全局变量产生冲突
// /*#__PURE__*/纯函数  webpack中会通过tree-shaking进行压缩,如果这段代码没有被使用,就会被删掉
// 纯函数没有副作用
var Person = /*#__PURE__*/ (function () {
    function Person(name, age) {
        _classCallCheck(this, Person);

        this.name = name;
        this.age = age;
    }

    _createClass(Person, [
        {
            key: "running",
            value: function running() {
                console.log(this.name + "running~");
            }
        }
    ]);

    return Person;
})();

ES6->ES5(extends)

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    running() {
        console.log(this.name + "running~");
    }
}

class Students extends Person {
    constructor(name, age, sno) {
        super(name, age);
        this.sno = sno;
    }
    learning() {
        console.log(this.name + "learning");
    }
}

// babel转换(IE10)
("use strict");

function _typeof(obj) {
    "@babel/helpers - typeof";
    return (
        (_typeof =
         "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
         ? function (obj) {
            return typeof obj;
        }
         : function (obj) {
            return obj &&
                "function" == typeof Symbol &&
                obj.constructor === Symbol &&
                obj !== Symbol.prototype
                ? "symbol"
            : typeof obj;
        }),
        _typeof(obj)
    );
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    Object.defineProperty(subClass, "prototype", {
        value: Object.create(superClass && superClass.prototype, {
            constructor: { value: subClass, writable: true, configurable: true },
        }),
        writable: false,
    });
    // 静态方法的继承
    // Student.__proto__ = Person
    if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
    _setPrototypeOf =
        Object.setPrototypeOf ||
        function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();
    return function _createSuperInternal() {
        var Super = _getPrototypeOf(Derived),
            result;
        if (hasNativeReflectConstruct) {
            var NewTarget = _getPrototypeOf(this).constructor;
            result = Reflect.construct(Super, arguments, NewTarget);
        } else {
            result = Super.apply(this, arguments);
        }
        return _possibleConstructorReturn(this, result);
    };
}

function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    } else if (call !== void 0) {
        throw new TypeError(
            "Derived constructors may only return object or undefined"
        );
    }
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
    if (self === void 0) {
        throw new ReferenceError(
            "this hasn't been initialised - super() hasn't been called"
        );
    }
    return self;
}

function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;
    try {
        Boolean.prototype.valueOf.call(
            Reflect.construct(Boolean, [], function () {})
        );
        return true;
    } catch (e) {
        return false;
    }
}

function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf
        ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
}

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", { writable: false });
    return Constructor;
}

var Person = /*#__PURE__*/ (function () {
    function Person(name, age) {
        _classCallCheck(this, Person);

        this.name = name;
        this.age = age;
    }

    _createClass(Person, [
        {
            key: "running",
            value: function running() {
                console.log(this.name + "running~");
            },
        },
    ]);

    return Person;
})();

var Students = /*#__PURE__*/ (function (_Person) {
    // 实现寄生式继承
    _inherits(Students, _Person);

    var _super = _createSuper(Students);

    function Students(name, age, sno) {
        var _this;

        _classCallCheck(this, Students);

        // 创建出来的对象
        _this = _super.call(this, name, age);
        _this.sno = sno;
        return _this;
    }

    _createClass(Students, [
        {
            key: "learning",
            value: function learning() {
                console.log(this.name + "learning");
            },
        },
    ]);

    return Students;
})(Person);

  • image.png

多态

维基百科定义多态

  • 多态polymorphism)指为不同数据类型实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型

总结多态的定义

  • 不同的数据类型进行同一个操作表现不同的行为,就是多态的体现

  • function calcArea(foo) {
      console.log(foo.getArea());
    }
    
    var obj1 = {
      name: 'zzy',
      getArea: function() {
        return 100
      }
    }
    
    class Person {
      getArea() {
        return 100
      }
    }
    
    var p = new Person()
    
    calcArea(obj1)
    calcArea(p)
    
    // 也是多态的体现
    function sum(n, m) {
      return m + n
    }
    sum(20, 30)
    sum('a', 'b')
    

传统的面向对象的多态

  • 必须有继承(继承是多态的前提)

  • 必须有重写(子类重写父类的方法)

  • 必须有父类引用指向子类对象

  • class Shape {
        getArea() {}
    }
    
    class Rectangle extends Shape {
        getArea() {
            return 100;
        }
    }
    
    class Circle extends Shape {
        getArea() {
            return 200;
        }
    }
    
    var r = new Rectangle()
    var c = new Circle()
    
    // 多态:当不同的数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是堕胎的体现
    function calcArae(shape: Shape) {
        console.log(shape.getArea());
    }
    
    calcArae(r)
    calcArae(c)
    

字面量的增强(ES6)

  • ES6中对对象字面量进行了增强,增强对象字面量(Enhanced object literals)

    • 属性简写Property Shorthand

    • 方法简写Method Shorthand

    • 计算属性名Computed Property Names

    • var name = 'zzy'
      var age = 22
      
      var obj = {
          // 1.property shorthand(属性简写)
          // 当key与value相同时,可只写一个
          // name: name,
          // age: age,
          name,
          age,
      
          // 2.method shorthand(方法简写)
          // fn: function() {} => fn() {}
          // foo: function () {
          //   console.log(this);
          // }
          foo() {
              console.log(this);
          },
          // 与箭头函数不一样
          bar: () => {
              console.log(this);
          },
      
          // 3.computed property names(计算属性名)
          [name + 123]: 'aaa'
      }
      console.log(obj); // { name: 'zzy', age: 22, foo: [Function: foo] }
      obj.foo() // { name: 'zzy', age: 22, foo: [Function: foo] }
      obj.bar() // {}
      // 在ES6中可以直接在对象内计算属性名
      console.log(obj); // 会多出一个 zzy123: 'aaa'
      // 在ES5中这样写
      obj[name + 231] = 'bb'
      console.log(obj); // 会多出一个 zzy231: 'bb'
      

解构(ES6)

  • ES6中新增了一个从数组对象获取数据的方法,称之为解构Destructuring

数组解构

  • var names = ['aa', 'bb', 'cc']
    
    // ES5这样写
    var item1 = names[0]
    var item2 = names[1]
    var item3 = names[2]
    
    // 1.ES6对数组的解构
    var [i1, i2, i3] = names
    console.log(i1, i2, i3); // aa bb cc
    
    // 2.解构后面的元素
    var [, b, c] = names
    var [, , C] = names
    console.log(b, c); // bb cc
    console.log(C); //  cc
    
    // 3.解构出一个元素,后面的元素放到一个新数组中
    var [ia, ...newArr] = names
    console.log(ia); // aa
    console.log(newArr); // [ 'bb', 'cc' ]
    
    // 4.解构的默认值
    var [ita, itb, itc, itd = 'dd'] = names
    console.log(itd); // dd
    

对象解构

  • var obj = {
        name: "zzy",
        age: 22,
        height: 1.75,
    };
    
    // 1.对象的解构:{}
    var { name, age, height } = obj;
    console.log(name, age, height); // zzy 22 1.75
    // 2.根据key去获取value,所以没有顺序限制
    var { age, height, name } = obj;
    console.log(age, height, name); // 22 1.75 zzy
    // 3.修改key名
    var {name: newName} = obj
    console.log(newName); // zzy
    // 4.默认值
    var {address = '广州市'} = obj
    console.log(address); // 广州市
    // 5.默认值并给新的名字
    var {gender: sex = '男'} = obj
    console.log(sex); // 男
    

应用场景

  • 获取到变量时,自动对其解构

    image.png image.png

  • 对函数的参数解构

    // 函数参数中解构
    function foo(info) {
        console.log(info.name, info.age);
    }
    foo(obj) // zzy 22
    function bar({name, age}) {
        console.log(name, age);
    }
    bar(obj) // zzy 22
    

let/const(ES6)

注意事项

  • const定义后的变量不能修改,
  • 但是如果传递的是一个引用类型(内存地址),可以通过引用找到对应的对象,去修改对象内部属性
  • 通过let/const定义的变量名可以重复定义的

作用域提升

  • var声明的变量会进行作用域提升

  • let/const声明的变量不会进行作用域提升

  • let/const声明的变量会被创建包含他们的词法环境实例化时,但是是不可以访问它们的,直到词法绑定被求值

  • // var声明的变量作用域会提升
    console.log(foo); // undefined
    var foo = 'foo'
    
    // let/const声明的变量作用域不会提升
    // console.log(foo1); // 会报错
    // console.log(foo2); // 会报错
    let foo1 = 'foo1'
    const foo2 = 'foo2'
    

作用域提升定义

  • 声明变量作用域中,如果这个变量可以在声明之前被访问,那么我们就可以称之为作用域提升
  • let/const虽然在执行上下文创建阶段被创建出来,但是不能被访问,所以不能称之为作用域提升

window对象添加属性

  • 全局通过var声明一个变量window添加一个属性

  • 全局通过let/const声明一个变量不会window添加一个属性

变量被保存到VariableMap

  • let/const声明的变量环境记录是被添加到变量环境VE)中的
    • v8中通过VariableMap的一个hashmap来实现存储
    • window对象是早期的GO对象,在最新的实现中是浏览器添加的全局对象,并且一直保持了windowvar之间相等性

块级作用域

ES6之前的块级作用域

  • JSES6之前只会形成两个作用域

    • 全局作用域
    • 函数作用域
  • ES5中放到一个代码中定义的变量外面可以访问

  • if/switch/for代码块内部也是块级作用域

    • // if代码块是块级作用域
      if (true) {
          var foo = "foo";
          let bar = "bar";
      }
      console.log(foo); // foo
      // console.log(bar); // 报错
      
      // switch代码块是块级作用域
      var color = "red";
      switch (color) {
          case "red":
              var foo2 = "foo2";
              let bar2 = "bar2";
      }
      console.log(foo2); // 2
      // console.log(bar2); // 报错i
      
      // for代码块是块级作用域
      for (var i = 0; i < 2; i++) {}
      console.log(i); // 2
      
      for (let j = 0; i < 2; i++) {}
      // console.log(j); // 报错
      
      

ES6中的块级作用域

  • 对于let/const/function/class声明的类型是有效的,
  • var声明的类型无效
  • 函数拥有块级作用域,但是外面依然可以访问
    • 不同浏览器不同实现大部分浏览器为了兼容以前的代码,引擎会对函数的声明进行特殊处理,允许像var那样进行提升,让function没有块级作用域

块级作用域的应用

  • <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    
    <script>
        const btns = document.getElementsByTagName("button");
    
        // ES6之前
        // for (var i = 0; i < btns.length; i++) {
        //   btns[i].onclick = function () {
        //     console.log(`第${i}个按钮被点击`); // 第4个按钮被点击 全局作用域中的i是最终的i
        //   };
        // }
    
        // ES6之前通过立即执行函数解决问题,函数会形成作用域
        // for (var i = 0; i < btns.length; i++) {
        //   (function(i) {
        //     btns[i].onclick = function () {
        //       console.log(`第${i}个按钮被点击`); // 第0-3个按钮被点击 这里的i是每次遍历的i
        //     };
        //   })(i);
        // }
    
        // ES6之后,通过let,let会形成快级作用域
        for (let i = 0; i < btns.length; i++) {
            btns[i].onclick = function () {
                console.log(`第${i}个按钮被点击`); // 第0-3个按钮被点击 这里的i是每次遍历的i
            };
        }
    
    </script>
    

for...of(ES6)

  • 遍历可迭代数组 / 对象

    • const names = ["a", "b", "c"];
      
      // for...of:ES6新增的遍历数组/对象
      for(var item of names) {
          console.log(item); // a b c
      }
      console.log(item); // c 可以访问var定义的变量
      
      for(let item of names) {
          console.log(item); // a b c
      }
      for(const item of names) {
          console.log(item); // a b c
      }
      

暂时性死区(ES6)

  • 在代码中,使用let/const声明的变量,在声明之前,变量不可访问,称为暂时性死区temporal dead zone,TED

  • var foo = "a";
    if(true) {
        console.log(foo); // 由于let声明的变量不会作用于提升,会出现暂时性死区
        let foo = 'b'
        }
    
    function bar() {
        console.log(foo); // 由于let声明的变量不会作用于提升,会出现暂时性死区
        let foo = 'c'
        }
    bar()
    

var,let,const的选择

  • var使用
    • 作用域提升window全局对象,没有块级作用域等都是历史遗留问题
    • 是JS设计之初的一种语言缺陷
    • 开发中不推荐使用var定义变量
  • let/const使用
    • 优先推荐const,可以保证数据的安全性,不会被随意修改
    • 只有当我们明确知道一个变量后续会需要被重新赋值时,再使用let

模板字符串(ES6)

基本使用

  • ES6之前,将字符串动态变量拼接起来,非常麻烦

  • ES6允许使用模板字符串嵌入JS变量表达式来进行拼接

    • 使用单反引号(`)来编写字符串,称之为模板字符串
    • 模板字符串中,通过**${expression}嵌入动态的内容**
  • const name = 'zzy'
    const age = 22
    
    // 拼接字符串和其他标识符
    // ES6之前 (代码丑陋,可读性差)
    console.log("my name is " + name); // my name is zzy
    // ES6模板字符串(`${}`)
    console.log(`my name is ${name}`); // my name is zzy
    //模板字符串可以进行计算
    console.log(`my age is ${age * 2}`); // my age is 44
    // 模板字符串进行函数调用
    function doubleAge() {
      return age * 2
    }
    console.log(`my age is ${doubleAge()}`); // my age is 44
    

标签模板字符串

  • Tagged Template Literals

  • 第一个参数依然是模板字符串中整个字符串,只是被${}分割成多块,放到了一个数组中

  • 第二个参数是模板字符串中第一个${}

  • ...

  • // - 第一个参数依然是模板字符串中整个字符串,只是被${}分割成多块,放到了一个数组中
    // - 第二个参数是模板字符串中第一个${}
    function foo(m, n, x) {
        console.log(m, n, x, '---'); // 20 30 40 ---
    }
    // 普通调用
    foo(20, 30, 40);
    // 标签模板字符串方式调用
    const name = 'zzy'
    const age = 22
    foo`Hello${name}Wo${age}rld` // [ 'Hello', 'Wo', 'rld' ] zzy 22 ---
    

React的styled-components库

  • image.png

函数的默认参数(ES6)

ES6之前

  • 缺点

    • 写起来麻烦可读性差
    • 这种写法有bug, 如果传入0/“ ”/null/false会选择默认值
  • function foo(m, n, q) {
      m = m || "a";
      n = n || "b";
      q = q || "c";
      console.log(m, n, q);
    }
    foo(0, null, ""); // a b c
    

ES6

  • 可以给函数参数提供默认值

  • function bar(m = "a", n = "b") {
        console.log(m, n);
    }
    bar(0, null); // 0, null
    
  • 函数参数对象

    • 可以参数的属性设置默认值并解构

    • function printInfo({ name, age } = { name: "zzy", age: 22 }) {
          console.log(name, age);
      }
      printInfo(); // zzy 22
      printInfo({ name: "aaa", age: 12 }); // aaa 12
      
    • 可以给解构后的属性设置默认值

    • function printInfo2({ name, age } = { name: "zzy", age: 22 }) {
        console.log(name, age);
      }
      printInfo2(); // zzy 22
      printInfo2({ name: "bbb", age: 21 }); // bbb 21
      
  • 有默认值形参最好放到最后

  • function baz(x, y, z = 30) {
        console.log(x, y, z);
    }
    baz(10, 20); // 10 20 30
    
  • 默认值改变函数的length的个数,默认值以及后面的参数不计算length之内

  • function boo(x, y, z = 30, m, n) {
        console.log(x, y, z, m, n);
    }
    console.log(boo.length); // 2
    

函数的剩余参数(ES6)

  • rest parameter

    • 如果最后一个参数是**...前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组**

    • 剩余参数必须放到最后一个位置

    • function foo(m, n, ...args) {
          console.log(m, n, args);
      }
      foo(20, 30, 40, 50); // 20 30 [ 40, 50 ]
      
  • 剩余参数arguments区别

    • 剩余参数只包含那些没有对应的实参,而arguments对象包含了传给函数的所有实参
    • arguments对象不是一个真正数组,而rest参数是一个真正数组,可以进行数组的所有操作
    • arguments早期ECMAScript中为了方便去获取所有的参数提供的一个数据结构rest参数是ES6中提供并且希望以此来替代arguments

函数的箭头参数(ES6)

  • 箭头函数没有显式原型的,所以不能作为构造函数,使用new创建对象

展开语法(ES6)

  • Spread syntax

    • 可以在函数调用/数组构造时,将数组表达式或者string语法层面展开
    • 可以在构造字面量对象时,将对象表达式key-value的方式展开
  • 展开语法场景

    • 函数调用

      const names = ['aa', 'bb', 'cc']
      const name = 'zzy'
      function foo(x, y, z) {
          console.log(x, y ,z);
      }
      // apply一般在this绑定时使用
      // foo.apply(null, names) // aa bb cc
      foo(...names) // aa bb cc
      foo(...name) // z z y
      
    • 数组构造

      const names = ['aa', 'bb', 'cc']
      const name = 'zzy'
      const newNames = [...names, ...name]
      console.log(newNames); // [ 'aa', 'bb', 'cc', 'z', 'z', 'y' ]
      
    • 构建对象字面量时(ES2018/ES9

      • 数组会按照索引值: 元素的格式添加到对象
      const names = ['aa', 'bb', 'cc']
      const info = {name: 'zzy', age: 22}
      // 数组会按照索引值和元素的格式添加到对象中
      const obj = {...info, address: '广州市', ...names}
      console.log(obj); // {'0': 'aa', '1': 'bb', '2': 'cc', name: 'zzy', age: 22, address: '广州市' }
      
  • 展开运算符本质是进行浅拷贝

    • const info = {
          name: 'zzy',
          friends: {
              name: 'aaa'
          }
      }
      
      const obj = {...info, name: 'lll'} // zzy被覆盖
      console.log(obj); // { name: 'lll', friends: { name: 'aaa' } }
      obj.friends.name = 'bbb' // aaa被覆盖
      console.log(info.friends.name); // bbb
      

数值的表示(ES6)

  • const num1 = 100 // 10进制
    const num2 = 0b100 // 2进制 binary
    const num3 = 0o100 // 8进制 octonary
    const num4 = 0x100 // 16进制 hexodecimal
    const num = 1_000_000_000 // 大的数值的连接符(ES2021/ES12)
    
    console.log(num1, num2, num3, num4, num); // 100 4 64 256 1000000000
    

Symbol(ES6)

  • 新增的基本数据类型符号

  • ES6之前,对象的属性名都是字符串形式,容易造成属性名的冲突

  • 添加新的属性和值容易覆盖掉之前内部存在

  • Symbol可以生成一个独一无二

    • 通过Symbol函数来生成,生成后可以作为属性名
    • ES6中,对象的属性名可以使用字符串,也可以使用Symbol值
  • Symbol即使多次创建,他们也是不同的:Symbol函数执行后每次创建出来的都是独一无二

  • // 1.ES6之前,对象的属性名(key)
    var obj = {
        name: "zzy",
        friends: {
            name: "aaa",
        },
    };
    
    console.log(Object.keys(obj)); // [ 'name', 'friends' ]
    
    // 2.ES6中Symbol
    const s1 = Symbol();
    const s2 = Symbol();
    console.log(s1 === s2); // false
    
    // ES2019/ES10中,Symbol还有一个描述(description)
    const s3 = Symbol("aa");
    console.log(s3.description); // aa
    
    // 3.Symbol值作为key
    // 3.1 定义对象字面量时
    const obj1 = {
        [s1]: "abc",
        [s2]: "cba",
    };
    // 3.2 新增属性时
    obj1[s3] = "nba";
    // 3.3 Object.defineProperty方式
    const s4 = Symbol();
    Object.defineProperty(obj1, s4, {
        enumerable: true,
        configurable: true,
        writable: true,
        value: "mba",
    });
    // 不能通过.语法获取属性值
    console.log(obj1[s1], obj1[s2], obj1[s3], obj1[s4]); // abc cba nba mba
    
    
    // 4.使用Symbol作为key的属性名,在遍历/Object.keys等中获取不到这些Symbol值,需要通过Object.getOwnPropertySymbols()来获取所哟Symbol的key
    console.log(Object.keys(obj1)); // []
    console.log(Object.getOwnPropertyNames(obj1)); // []
    console.log(Object.getOwnPropertySymbols(obj1)); // [ Symbol(), Symbol(), Symbol(aa), Symbol() ]
    const sKeys = Object.getOwnPropertySymbols(obj1)
    for(const sKey of sKeys) {
        console.log(obj1[sKey]); // abc cba nba mba
    }
    
    // 5.Symbol.for(key) / Symbol.keyFor(symbol)
    const sa = Symbol.for("aaa")
    const sb = Symbol.for("aaa")
    console.log(sa === sb); // true
    const key = Symbol.keyFor(sa)
    console.log(key); // aaa
    const sc = Symbol.for(key)
    console.log(sa === sc); // true
    

Set(ES6)

基本使用

  • ES6之前存储数据的结构:数组对象

  • ES6新增两种数据结构Set,Map,WeakSet,WeakMap

  • Set可以用来保存数据,类似于数组,但是元素不能重复

    • 创建Set通过Set构造函数

      const set = new Set()
      set.add(10)
      set.add(20)
      set.add(10) // 元素不能重复,不会被添加
      set.add({})
      set.add({}) // 两个对象的内存地址不同,都会被添加
      const obj = {}
      set.add(obj)
      set.add(obj) // 引用内存地址相同,是同一个对象,不会被添加
      
      console.log(set); // Set(5) { 10, 20, {}, {}, {} }
      
  • 使用Set数组去重

    // 1.遍历方法
    const arr = [1,2,3,3,4,1]
    const newArr = []
    for(const item of arr) {
        // 检索的字符串值没有出现过,返回-1
        if(newArr.indexOf(item) === -1) {
            newArr.push(item)
        }
    }
    console.log(newArr); // [ 1, 2, 3, 4 ]
    
    // 2.set方法
    const arrSet = new Set(arr)
    console.log(arrSet); // Set(5) { 1, 2, 3, 4 }
    // Array.from()将set结构变为数组结构
    const newArr1 = Array.from(arrSet)
    console.log(newArr1); // [ 1, 2, 3, 4 ]
    // ...展开运算符将set结构变为数组结构
    const newArr2 = [...arrSet]
    console.log(newArr2); // [ 1, 2, 3, 4 ]
    

Set常见属性

  • size:返回Set中的元素个数

Set常见方法

  • add(value)添加某个元素,返回Set本身

  • delete(value)删除和这个值相等的元素,返回布尔类型,成功移除返回true

  • has(value)判断set中是否存在某个元素,返回布尔类型

  • clear()清空set中所有元素,没有返回值

  • forEach(callback, [,thisArg])遍历set

  • for...of遍历set

    const arr = [1, 2, 3, 3, 4, 1];
    const arrSet = new Set(arr);
    console.log(arrSet); // Set(4) { 1, 2, 3, 4 }
    // 4.size属性
    console.log(arrSet.size); // 4
    
    // 5.常见方法
    arrSet.add(100)
    console.log(arrSet); // Set(5) { 1, 2, 3, 4, 100 }
    
    arrSet.delete(4)
    console.log(arrSet); // Set(4) { 1, 2, 3, 100 }
    
    console.log(arrSet.has(2)); // true
    
    arrSet.clear()
    console.log(arrSet); // Set(0) {}
    
    // 6.遍历Set
    arrSet.forEach(i => {
        console.log(i);
    })
    
    for(const i of arrSet) {
        console.log(i);
    }
    

WeakSet

基本使用

  • Set类似的数据结构内部元素不能重复
  • 和Set区别
    • WeakSet中只能存放对象类型不能存放基本数据类型
    • WeakSet元素的引用是弱引用,如果没有其他引用对这个对象进行引用,那么GC可以对该对象进行回收

WeakSet常见方法

  • add(value)添加某个元素,返回WeakSet本身
  • delete(value)删除和这个值相等的元素,返回布尔类型,成功移除返回true
  • has(value)判断WeakSet中是否存在某个元素,返回布尔类型

注意

  • WeakSet不能遍历
    • WeakSet只是对对象的弱引用,如果可以遍历获取其中元素,会影响GC回收
    • 所以WeakSet中的对象不能获取

应用场景

  • 如果用Set的话会对this(即对象)有强引用销毁对象的时候(将对象置为null)该对象无法回收,还需要delete才能销毁
const personSet = new WeakSet()
class Person {
    constructor() {
        personSet.add(this)
    }
    running() {
        if(!personSet.has(this)) {
            throw new Error('不能通过非构造函数创建出来的对象调用running方法')
        }
        console.log('running', this);
    }
}
const p = new Person()
p.running() // running Person {}
p.running.call({name: 'zzy'}) // 报错

Map(ES6)

  • 用于存储映射关系

  • JS中对象中不允许使用对象作为key

    • 对象存储映射关系只能用字符串ES6新增Symbol)作为属性key
    • 不会对象作为key,而是将对象转成字符串作为key
  • Map允许对象使用对象其他类型作为key

    const map = new Map()
    map.set(obj1, 'aaa')
    map.set(obj2, 'bbb')
    map.set(1, 'ccc')
    console.log(map); // Map(3) {{ name: 'zzy' } => 'aaa', { name: 'lll' } => 'bbb', 1 => 'ccc' }
    
    const map2 = new Map([[obj1, 'aaa'], [obj2, 'bbb'], [2, 'ccc']])
    console.log(map2); // Map(3) {{ name: 'zzy' } => 'aaa', { name: 'lll' } => 'bbb', 2 => 'ccc' }
    

Map常见属性

  • size:返回Map中的元素个数

Map常见方法

  • set(key,value)添加某个元素,返回Map本身

  • get(key)获取某个元素,返回返回某个Map对象中的一个指定元素。

  • delete(key)删除和这个值相等的元素,返回布尔类型,成功移除返回true

  • has(key)判断Map中是否存在某个元素,返回布尔类型

  • clear()清空Map中所有元素,没有返回值

  • forEach(callback, [,thisArg])遍历Map

  • for...of遍历Map

    const map2 = new Map([[obj1, 'aaa'], [obj2, 'bbb'], [2, 'ccc']])
    
    // size
    console.log(map2.size); // 3
    
    // set
    map2.set({height: 1.22}, 1.88)
    console.log(map2);
    
    // get(key)
    console.log(map2.get(2)); // ccc 获取key为2的value值
    
    // has(key)
    console.log(map2.has(2)); // true 判断属性2是否存在
    
    // delete(key) 
    map2.delete(2)
    console.log(map2); // 移除了属性2
    
    // clear
    map2.clear() // 清空map2的所有属性
    console.log(map2); // Map(0) {}
    
    // 遍历
    map2.forEach((value, key)=> {
      console.log(value, key);
    })
    
    for(const value of map2) {
      console.log(value); // 打印出数组,键值,以逗号分割
      console.log(value[0], value[1]); // 打印键值
    }
    
    // 对数组解构
    for(const [key, value] of map2) {
      console.log(key, value); // 打印键值
    }
    

WeakMap

基本使用

  • Map类似的数据结构以键值对的形式存在
  • 和Map区别
    • WeakMapkey只能使用对象接受其他的类型作为key
    • WeakMapkey对象引用弱引用,如果没有其他引用对这个对象进行引用,那么GC可以对该对象进行回收

WeakMap常见方法

  • get(key):获取WeakSet中的某个属性
  • delete(obj)删除和这个值相等的属性,返回布尔类型,成功移除返回true
  • has(obj)判断WeakSet中是否存在某个属性,返回布尔类型

注意

  • 不支持size属性
  • 不支持clear
  • WeakMap不能遍历
    • WeakMap只是对对象弱引用,如果可以遍历获取其中元素,会影响GC**回收
    • 所以WeakMap中的对象不能获取**

应用场景

  • Vue3响应式原理

    // Vue3响应式原理
    const obj1 = {
        name: 'zzy',
    }
    function obj1Name1() {
        console.log('obj1Name1~');
    }
    function obj1Name2() {
        console.log('obj1Name2~');
    }
    
    const obj2 = {
        name: 'lll',
    }
    function obj2Name1() {
        console.log('obj2Name1~');
    }
    function obj2Name2() {
        console.log('obj2Name2~');
    }
    
    // 1.创建WeakMap
    const weakMap = new WeakMap()
    
    // 2.收集依赖结构
    // obj1
    const obj1Map = new Map()
    obj1Map.set('name', [obj1Name1,obj1Name2])
    // WeakMap中只能使用对象作为key
    weakMap.set(obj1, obj1Map)
    
    // obj2
    const obj2Map = new Map()
    obj1Map.set('name', [obj2Name1,obj2Name2])
    // WeakMap中只能使用对象作为key
    weakMap.set(obj2, obj2Map)
    
    // 3.obj1.name发生了改变
    // 通过Proxy/Object.defineProperty监听数据是否改变
    obj1.name = 'aaa'
    const targetMap = weakMap.get(obj1)
    const fns = targetMap.get('name')
    fns.forEach(i => i()) // obj2Name1~ obj2Name2~
    

    image.png