ES6--Class基础语法

294 阅读5分钟

出现原因:

由于Js通过使用构造函数的写法与其他面向对象语言差异很大。所以在ES6中引入class的概念,作为对象的模板,通过class关键字,定义类。

class可以看成是语法糖,它的绝大部分功能,ES5都可以做到,引入这种写法,只是让对象原型的写法更清晰,更像面向对象编程语言而已。

// 可以看成构造函数的另一种写法(Point === Point.prototype.constructor 
                                    // true 表明类本身就指向构造函数)
class Point {
	// constructor是构造函数(可以理解成这是ES5的构造函数Point)
    // 如果不指定constructor,默认是一个空的constructor方法
	constructor(x,y){
    	// 这里的this关键字代表实例对象
    	this.x = x;
        this.y = y;
        // 默认return实例对象,也可以指定返回另外一个对象
        return
    }
    // 方法前面不需要加function,而且也不需要加逗号分割
    toString(){
    	return `(${this.x},${this.y})`
    }
}

// 使用时直接对类使用new命令,注意,类区别于ES5的构造函数,必须使用new调用,否则报错
var p = new Point(1,2)

tips:
	1:只能使用new对类进行调用
	2:类本身指向构造函数
    3:类所有的方法都定义在类的prototype属性上,所以调用实例的方法,就是调用prototype上的方法
    4:类内部定义的方法,都是不可枚举的(non-enumerable)

类的实例

上面说到生成类的实例是通过new关键字,实例化出一个实例的。

const p = new Point(1,2)

这样我们就实例化出一个实例,因为一个类的所有实例都公用一个原型,(即Class的显示原型prototype指向的对象)所有实例的隐式原型__proto__都指向Class的显示原型。

所以,所有的实例都可以调用class定义的方法,那属性呢?

其实,正常来说,class中定义的属性都是直接定义在实例中的,而不是定义在原型上。

先抛出一个概念吧:

实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

所以,在class顶部与构造函数中使用this.xxx,定义属性,都是在定义在实例上的,而不是原型上。

方法也同样适用,

如果我们在类中的构造函数定义:this.fun = function(){console.log('fun')}

那么fun这个方法,就会定义在实例上,而不是原型上。

getter与setter

这一部分与ES5中一致,

都是通过get xxx(){} 与 set xxx(){} 进行拦截

// 例子
class MyClass {
  constructor() {
      // ...
  }
  get prop() {
      return 'getter';
  }
  set prop(value) {
      console.log('setter: '+value);
  }
}

属性表达式

类的属性名,可以采用表达式。

let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}

上面代码中,Square类的方法名getArea,是从表达式得到的。

class的表达式

类也可以使用表达式的形式定义。

// 这里的Me只在Class的内部可用,指代当前类,在class外部,这个类只能用MyClass引用
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
// 当然,也可以省略Me,改写成下面形式
const MyClass = class { /* ... */ };

采用 Class 表达式,可以写出立即执行的 Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"

上面代码中,person是一个立即执行的类的实例。(注意在class前加new,与class后面加调用构造函数)

几个注意点

tips:
	1:严格模式
    	类和模块的内部,默认都是严格模式
    2:不存在变量提升
    	类使用在前,定义在后,会报错(为了考虑子类继承问题)
    3:name属性
    	本质上,ES6的类只是ES5的构造函数的一层包装,
        所以函数的特性都会被Class继承,包括name属性,
        name属性总是返回紧跟在class关键字后面的类名
    4:Generator方法
    	如果在某个方法前面加上星号(*),就表示改方法是一个Generator函数
    5this指向
    	类的方法内部如果含有this,则默认指向类的实例,
        所以,如果单独使用类中的方法,很可能因为this指向导致抛错
        解决方法:
        	1):通过绑定this
            2):使用箭头函数
            3):通过Proxy在获取方法时,自动绑定this

静态方法

在类中,如果一个方法前面加上static关键字,则该方法不会被实例继承,而是直接通过类调用,成为静态方法。

class Foo {
  // 静态方法只能通过以下方式调用。
  		该类,
        或者该类的子类(子类会继承父类的静态方法),
        或者子类的super(此时父类已经存在)
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
// 静态方法不可以在实例中调用,因为没被继承
foo.classMethod()
// TypeError: foo.classMethod is not a function

Tips:
	注意的是静态方法中的this指向类,而不是实例,
    此外,静态方法和普通方法可以同名

静态属性

应为ES6中明确规定,Class内部只有静态方法,没有静态属性,现有一个提案是,在属性前面加上static关键字。

但现在的写法是,直接在类上添加静态属性。

比如:Class Fun(){}
		Fun.prop = 1;

私有属性

在ES2020,已经规定了,私有属性的语法。

在属性或方法前面加#号,代表该属性或方法是私有的,外部访问不到。

new.target

new 是从构造函数生成实例对象的命令。

ES6为命令引入new.target的属性,该属性用于构造函数中,在函数外部使用,会报错

如果构造函数不是通过new命令或者Reflect.constructor()调用,则会返回undefined。

如果在Class内部调用new.target,则会返回当前Class,子类调用,也会返回子类。而不是父类。

文章参考:es6.ruanyifeng.com/#docs/class