zoukankan      html  css  js  c++  java
  • 详讲ES6中的class

    一、ES5中的近类结构

    //ES5中的近类结构
    function Person(name) {
        this.name = name;
    }
    Person.prototype.sayname = function(){
        console.log(this.name);
    }
    var person = new Person("Tom");
    person.sayname();
    console.log(person instanceof Person);//true
    console.log(person instanceof Object);//true

     二、ES6中的类

      ECMAScript2015中引入的JavaScript类实质上是JavaScript现有的基于原型的继承的语法糖。类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。

    (一)类声明

       函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它。

    class Person{
      //等价于Person构造函数
      constructor(name){
        this.name = name;
      }
      //等价于Person.prototype.sayname
      sayName(){
        console.log(this.name);
      } }

     类与自定义类型之间的差异:

      1.函数声明可以被提升,而类声明与let声明类似,不能被提升,真正执行声明语句之前,它们会一直存在于临时死区中。

      2.类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式执行。

      3.在自定义类型中,需要通过Object.defineProperty()方法手工指定某个方法为不可枚举;而在类中,所有方法都是不可枚举的。

      4.使用关键字new以外的方式调用类的构造函数会导致程序抛出错误。

      5.在类中修改类名会导致程序报错。

    模拟类声明代码:

    let Person = (function(){
        "use strict"
        const Person = function(name){
            //确保通过关键字new调用该函数
            if(typeof new.target === "undefined"){
              throw new Error("必须通过关键字new调用构造函数");
            }
            this.name = name;
        }
        Object.defineProperty(Peron.prototype,"sayName",{
          value:function(){
              //确保不会通过关键字new调用该方法
                if(typeof new.target !== "undefined"){
                  throw new Error("不可使用关键字new调用该方法")
                }
              console.log(this.name);
          },
          enumerable:false,
          writable:true,
           configurable:true
        })
        return Person
    })();

     (二)类表达式

      类表达式可以是具名的或匿名的,一个具名类表达式的名称是类内的一个局部属性,它可以通过类本身的name属性来获取。类表达式也同样受到类声明中提到的类型提升的限制。

    //匿名类
    let Person = class {
      constructor(height,width){
        this.height = height;
        this.width = width;
      }
    };
    console.log(Person.name);//Person
    //具名类
    let Person = class Person {
      constructor(height,width){
        this.height = height;
        this.width = width;
      }
    }
    console.log(Person.name);//Person

     (三)类体和方法定义

      一个类的类体是一对花括号{}中的部分,这是你定义类成员的位置,如方法或构造函数。类声明和类表达式的主体都执行在严格模式下。

      constructor 方法是类的构造函数,是一个默认方法,这种方法用于创建和初始化一个由class创建的对象,通过 new 命令创建对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。一般 constructor 方法返回实例对象 this ,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。

      一个构造函数可以使用super关键字来调用一个父类的构造函数。

      自由属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建,此例中的name就是一个自由属性,这里建议你在构造函数中创建所有自有属性,从而只通过一处就可以控制类中的所有自有属性。

      类声明仅仅是基于已有自定义类型声明的语法糖,typeof Person最终返回的结果是“function”,所以Person声明实际上创建了一个具有构造函数方法行为的函数。此示例中的sayName()方法实际上是Person.prototype上的一个方法

     (四)super关键字的作用

      super 这个关键字,既可以当做函数使用,也可以当做对象使用。这两种情况下,它的用法完全不同。

      1、当作函数使用

    class A {}
    class B extends A {
      constructor() {
        super();  // ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
      }
    }

       注:在 constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于 ```A.prototype.constructor.call(this, props)``。

    class A {
      constructor() {
        console.log(new.target.name); // new.target 指向当前正在执行的函数
      }
    }
    
    class B extends A {
      constructor() {
        super();
      }
    }
    
    new A(); // A
    new B(); // B

       可以看到,在 super() 执行时,它指向的是 子类 B 的构造函数,而不是父类 A 的构造函数。也就是说,super() 内部的 this 指向的是 B

       2、当做对象使用

         在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

    class A {
      c() {
        return 2;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        console.log(super.c()); // 2
      }
    }
    
    let b = new B();

       上面代码中,子类 B 当中的 super.c(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.c() 就相当于 A.prototype.c()

        通过 super 调用父类的方法时,super 会绑定子类的 this

    class A {
      constructor() {
        this.x = 1;
      }
      s() {
        console.log(this.x);
      }
    }
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      m() {
        super.s();
      }
    }
    
    let b = new B();
    b.m(); // 2
        上面代码中,super.s() 虽然调用的是 A.prototytpe.s(),但是 A.prototytpe.s()会绑定子类 B 的 this,导致输出的是 2,而不是 1。也就是说,实际上执行的是 super.s.call(this)
        由于绑定子类的 this,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。
    class A {
      constructor() {
        this.x = 1;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
        super.x = 3;
        console.log(super.x); // undefined
        console.log(this.x); // 3
      }
    }
    
    let b = new B();
        上面代码中,super.x 赋值为 3,这时等同于对 this.x 赋值为 3。而当读取 super.x 的时候,调用的是 A.prototype.x,但并没有 x 方法,所以返回 undefined。
        注意,使用 super 的时候,必须显式指定是作为函数,还是作为对象使用,否则会报错。
    class A {}
    class B extends A {
      constructor() {
        super();
        console.log(super); // 报错
      }
    }

         上面代码中,console.log(super); 的当中的 super,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这是,如果能清晰的表明 super 的数据类型,就不会报错。

    (五)静态方法

      在ECMAScript5及早期版本中,直接将方法添加到构造函数中来模拟静态成员是一种常见的模式,例如:

    function Person(name){
        this.name = name;
    }
    //静态方法
    Person.create = function(name){
        return new Person(name);
    }
    //实例方法
    Person.prototype.sayName = function(){
        console.log(this.name);
    }
    var person = Person.create("top")
    console.log(person);

      由于工厂方法,Person.create()使用的数据不依赖Person的实例,因此其会被认为是一个静态方法。ECMAScript6的类语法简化了创建静态成员的过程,在方法或访问器属性名前使用正式的静态注释即可。static关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。不需要实例化类,即可直接通过该类来调用的方法,即称之为“静态方法”。这样该方法不会被实例继承!

    class Person{
      //等价于Person构造函数   constructor(name){   this.name = name; }   //等价于Person.prototype.sayName   sayName(){     console.log(this.name)
      }   
    //等价于Person.create   static create(name){     return new Person(name)
      }
      static fund(){
        return "我是person中的静态方法,无需实例化,可直接调用!"
      }
        static b(){
            //通过静态方法b来调用静态方法fund
            console.log(this.a());//
        }
      //通过实例方法调用会报错
       c(){
            console.log(this.a());//TypeError: this.a is not a function
        }
     
    } var person = new Person('tom');
    var person = Person.create('qin'); console.log(person)
    //类Box的a方法前有static关键字, 表明该方法是一个静态方法, 可以直接在Box类上调用。静态方法只能在静态方法中调用,不能在实例方法中调用。
    console.log(Person.fund());//
    我是person中的静态方法,无需实例化,可直接调用!
    Person.b();//我是person中的静态方法,无需实例化,可直接调用!

       类中的所有方法和访问器属性都可以用static关键字来定义,唯一的限制是不能将static用于定义构造函数方法。不可在实例中访问静态成员,必须要直接在类中访问静态成员。

     父类的静态方法, 可以被子类继承:

    class Box {
        static a() {//父类Box的静态方法
            return '我是父类的静态方法a';
        }
    }
    class Desk extends Box {}
    //子类Desk可以直接调用父类的静态方法a
    console.log(Desk.a()); 

     倘若想通过子类的静态方法调用父类的静态方法,需要从super对象上调用:

    class Box {
        static a() {
            return '我是通过super来调取出来的';
        }
    }
    class Desk extends Box {
        static a(){
            return super.a();
        }
    }
    console.log(Desk.a()); 

    (六)静态属性

    静态属性指的是 Class 本身的属性, 即Class.propname, 而不是定义在实例对象( this) 上的属性。

    class Box{
       constructor(){
           this.name="实例属性"
       }
    }
    Box.prop1="静态属性1";
    Box.prop2="静态属性2";
    console.log(Box.prop1,Box.prop2);//静态属性1  静态属性2

    (七)继承

      在ECMAScript6之前,需要多个步骤实现继承。Son继承自Father,为了这样做,必须用一个创建来Father.prototype的新对象重写Son.prototype并调用Father.call()方法。

    function Father(length,width){
      this.length = length;
       this.width = width;
    } Father.prototype.getArea
    = function(){   return this.length * this.width } function Son(length,width){   Father.call(this,length,width) } Son.prototype = Object.create(Father.prototype,{ constructor:{ value:Son, enumerable:true,   writable:true,   configurable:true   } }) var son = new Son(3,4); console.log(son.getArea());//12 console.log(son instanceof Son);//true console.log(son instanceof Father);//true

       类的出现让我们可以更轻松的实现继承功能,使用熟悉的extends关键字可以指定类继承的函数。原型会自动调整,通过调用super()方法即可访问基类的构造函数。

    class Father{
      constructor(length,width){
         this.length = length;
          this.width = width;
       }
       getArea(){
         return this.length * this.width
       }
      static create(length,width){
        return new Father(length,width)
      } } class Son extends Father {   constructor(length,width){   super(length,width) }
      //派生类中的方法总会覆盖基类中的同名方法
      getArea(){
        //如果你想调用基类中的同名方法,则可以调用super.getArea()方法
        super.getArea()
        return this.length * this.width
      }
    }
    var son = new Son(3,4); console.log(son.getArea());//12 console.log(son instanceof Son)//true console.log(son instanceof Father);//true
    var obj = Son.create(3,4);

       继承自其他类的类被称为派生类,如果派生类中指定了构造函数则必须要调用super(),如果不调用程序就会报错。如果不使用构造函数,则当创建新的类实例时会自动调用super()并传入所有参数。

    class Son extends Father{
      //没有构造函数
    }
    //等价于
    class Son extends Father{
      constructor(...args){
        super(...args)
      }
    }

    Talk is cheap,show me the code
  • 相关阅读:
    git常用指令 github版本回退 reset
    三门问题 概率论
    如何高效的学习高等数学
    数据库6 关系代数(relational algebra) 函数依赖(functional dependency)
    数据库5 索引 动态哈希(Dynamic Hashing)
    数据库4 3层结构(Three Level Architecture) DBA DML DDL DCL DQL
    梦想开始的地方
    java String字符串转对象实体类
    java 生成图片验证码
    java 对象之间相同属性进行赋值
  • 原文地址:https://www.cnblogs.com/qc-one/p/12389846.html
Copyright © 2011-2022 走看看