ES6学习系列——class 语法

class总览:
  • ES6 中的 class 可以看做是一个语法糖,它的功能用ES5 也可以实现,只是写法更加清晰明了(因为更像面向对象编程的语法);
  • ES6 中类和模块内部都是默认使用严格模式,无需再加上”use strict”;
  • class 的数据类型是”function”,因为类本身其实就是个构造函数;
上实例:
class Test {
    constructor (x, y) { this.x= x; this.y = y; }

    mix () { return '('+ this.x+ '-'+ this.y+ ')'; }

    toStr () {} //方法间不需要用逗号隔开

    toNum () {}
}

typeof Test //"function"  说明类的数据类型是函数
Test === Test.prototype.constructor //true 说明类本身就指向构造函数

let test = new Test(1,3); //必须用new 来调用,否则报错

//在类的实例上调用方法,实际上就是调用原型上的方法:
test.mix(); //(1-3)

test.constructor === Test.prototype.constructor 
//true  说明实例的constructor 方法就是Test 类原型的constructor 方法
另外,可以用Object.assign 方法一次向类的原型上添加多个方法
class Test {
    constructor (x, y) {
        this.x= x;
        this.y = y;
    }
    Object.assign (Test.prototype, {
        toStr () {}, //需要加上逗号隔开各个方法
        toNum () {}
    });
}
1、constructor 方法

如果不加constructor 方法,引擎会帮你加一个空的constructor 方法;
这个constructor 方法默认返回实例对象(也就是this),如果手动去返回另一个对象,会导致实例对象不是 这个类的实例;

class Test {
    constructor () {
        return Object.create(null);
    }
}
new Test() instanceof Test  // false

let test = new Test(); //必须用new 来调用,否则报错
2、类的实例对象

上面已经说过,生成实例必须要用 new,如果像调用函数一样就会报错;
实例对象的属性除非是显式定义在this 对象上(也就是定义在constructor 方法内),否则都是在类的原型上;

class Test {
    constructor (x, y) {
        this.x= x;
        this.y = y;
    }
    Object.assign (Test.prototype, {
        toStr () {
        return '('+ this.x+ '-'+ this.y+ ')';
      },
        toNum () {}
    });
}

let test = new Test(3,2);

test.toStr(); //(3-2)

test.hasOwnProperty('x'); //true
test.hasOwnProperty('y'); //true
test.hasOwnProperty('toStr'); //false
test._proto_.hasOwnProperty('toStr'); //true
//类的某些方法是定义在类的原型上,而原型链式共享的,所以通过 实例的 _proto_ 属性可以改写的类的方法,不过要慎用
3、class的表达式形式

声明类的时候,可以用类的表达式形式:

let ClassTest = class Test {
  getClass () {
    return Test.name;  //类的 name 属性:总是返回class 后面的类名,跟构造函数一样
  }
};
let test = new ClassTest();
test.getClass(); //Test
Test.name; //ReferenceError:Test is not defined

由上面的例子可以知道,Test只有在类的内部才存在,在外部上不存在的;
如果内部用不到,可以这样写:

let ClassTest = class {};

如果要马上执行类,可以这样写:

let test = new class {};
4、类的特性探究
(1)类不存在变量提升

也就是说,必须要先声明类,才能去生成实例,否则会报错;

let test = new Test();

class Test {}
(2)私有方法

原生ES6 不提供私有方法,只能自己想办法来实现:

①可以将私有方法移出类的内部,达到方法的私有化
class Widget {
  foo (baz) {
    bar.call(this, baz);
  }
}

function bar(baz) {
  return this.snaf = baz;
}

上面例子中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。

②利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

};

上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。

③利用私有属性来实现:
class Foo {
  #a;
  #b;
  #sum() { return #a + #b; } //私有方法
  printSum() { console.log(#sum()); } //共有方法
  constructor(a, b) { #a = a; #b = b; }
}
(3)私有属性

原生ES6 也不支持,但是现在有个提案,在属性名字之前加井号来表示私有属性;

class Point {
  #x;

  constructor(x = 0) {
    #x = +x; // 写成 this.#x 亦可
  }

  get x() { return #x } // 私立属性和实例属性允许同名
  set x(value) { #x = +value }
}
(4)this 指向问题

尤其需要注意,类的内部的this 默认指向实例对象,所以如果脱离实例来操作有this 的属性方法会报错,除非修改this 对象的指向;

class Logger {
  printName(name = 'Troyes') {
    this.print(`Hello ${name}`); // this 指向实例对象,下面调用这个方法的时候,this 指向 windows 所以报错
    }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

解决这个问题,可以把这个属性方法写在构造方法 constructor 里面:

class Logger {
  constructor () {
    this.printName = this.printName.bind(this); // 把这个方法绑定到实例对象身上
  }
}

// 或者利用箭头函数(箭头函数默认 this 就是绑定到声明的环境中)
class Logger {
  constructor () {
    this.printName = (name = 'Troyes') => {
      this.print(`Hello ${name}`);
    }
  }
}
(5)class 的存取值函数

类的存值函数(getter)和取值函数(setter)都是定义在属性的Descriptor 对象上;

Test class {
  constructor () {

  }
  get name () {
    return 'getter:Troyes'
  }
  set name (val) {
    return 'setter: val'
  }
};
let test = new Test();
test.name; // getter:Troyes
test.name = 'Teemo'; //setter: Teemo

let descriptor = Object.getOwnPropertyDescriptor(Test.prototype, 'name');
"get" in descriptor; //true
"set" in descriptor; //true
(6)class 的静态方法

在定义类的时候,在一个方法前加上static关键字,那么该方法不会被实例继承,而是直接通过类来调用,这就是“静态方法”;

class Test {
  constructor () { // do something}

  static say () {
    console.log('Hello World');
  }

  static sayHello () {
    this.say(); //静态方法中,this 指向的是类,而不是实例
  }

  sayHello () {
    console.log('Hello Troyes');
  } // 静态方法可以和共有方法同名
}
let test = new Test();

Test.sayHello(); //Hello World
test.sayHello(); //Hello Troyes
test.say(); //TypeError: test.say is not a function

后面将会谈到类的继承问题,我们可以发现,子类可以继承父类的静态方法,同时静态方法还可以在super 对象上调用;

class NewTest extends Test {
  static sayWel () {
    return super.say() + 'again';
  }
}
NewTest.sayHello(); //Hello World
NewTest.sayWel(); //Hello World again
5、类的继承

类的继承通过 extends 关键字来实现,语法如下:

class Person {
  constructor (age, gender) {
    this.age = age;
    this.gender = gender;
  }
  printMsg() {
    console.log(this.age, this.gender);
  }
}

class NewPerson extends Person {
  constructor (age, gender, job) {
    super(age, gender); // 子类必须通过super() 方法才能构建出父类的 this 对象
    this.job = job; // 子类 this 对象特有的部分,必须要写在 super() 方法后面,因为不调用super方法根本就构建不出this对象
  }
  printOtherMsg () {
    console.log(this.job);
  }
}
let person = new NewPerson (23, 'male', 'webDev');
person.printMsg(); // 23, male
person.printOtherMsg(); // webDev
(1)类的 prototype 属性和_proto_属性

类作为构造函数的语法糖,理所当然同时有着这两个属性,也就是有着两条继承链:
prototype属性指向对应的构造函数;
_proto_属性是每个对象都有的;
- 子类的__proto__属性表示构造函数的继承,总是指向父类。
- 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

NewPerson._proto_ === Person // true
NewPerson.prototype._proto_ === Person.prototype // true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值