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
  • 相关阅读:
    Python 运算符优先级
    一起谈.NET技术,HubbleDotNet 和 Lucene.Net 匹配相关度的比较 狼人:
    一起谈.NET技术,Asp优化,asp缓存技术 狼人:
    一起谈.NET技术,MonoTouch中的MVC简介 狼人:
    一起谈.NET技术,如何成为人尽皆知的C#开发人员 狼人:
    一起谈.NET技术,如何解决分布式系统中的跨时区问题[原理篇] 狼人:
    一起谈.NET技术,WCF使用NetTcp传输文件 狼人:
    一起谈.NET技术,ASP.NET 项目安装包制作(二)数据库安装、其他组件的安装 狼人:
    一起谈.NET技术,如何解决分布式系统中的跨时区问题[实例篇] 狼人:
    一起谈.NET技术,模拟IIS向Silverlight输出策略文件 狼人:
  • 原文地址:https://www.cnblogs.com/qc-one/p/12389846.html
Copyright © 2011-2022 走看看