出现原因:
由于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函数
5:this指向
类的方法内部如果含有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,子类调用,也会返回子类。而不是父类。