详讲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)
  }
}

猜你喜欢

转载自www.cnblogs.com/qc-one/p/12389846.html