7. Class
在JS中,生成实例对象的传统方法是通过构造函数,这样的写法和传统的面向对象语言差异很大,在ES6中提供了更接近传统语言的写法,引入了class(类)的概念,作为对象的模板,通过class关键字可以定义类
基本上class可以看作是一个语法糖,它的绝大部分功能,es5都可以做到,新的写法只是为了让对象的原型写法更加清晰,更像面向对象变成的语法而已
//es6
class Point{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return '('+this.x+','+this.y+')';
}
}
//es5
function Point(x,y) {
this.x=x;
this.y=y;
}
Point.prototype.toString=function () {
return '('this.x+','+this.y+')';
}
在使用class时,constructor方法就是构造方法,而this关键字则代表实例对象,可以这么理解,就是说我们在es6中在函数体内写的代码在es6的class语法中都移植到constructor中
在上面的代码中除了构造类的方法,还定义了一个toString方法,注意,在定义类的方法时不需要加上function这个关键字,直接把函数定义放进去就可以了,另外,方法之间不要加逗号分隔,否则会报错
在使用的时候也是直接对类使用new命令,和构造函数的用法一致
class Point{
constructor(name){
this.name=name;
}
sayName(){
console.log(this.name);
}
}
var point=new Point('tom');
point.sayName();
事实上,类的所有方法都是定义在类的prototype属性上面,在类的实例上调用方法,其实就是在调用原型上的方法
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
所以如果我们想要为一个类添加方法,只需要在该类的prototype上添加即可,或者使用Object.assign会更加方便
class Point{
constructor(name){
this.name=name;
}
}
Point.prototype.sayName=function(){
console.log(this.name);
}
var point=new Point('tom');
point.sayName();
//使用Object.assign();
Object.assign(Point.prototype,{
toString(){},
toValue(){}
})
注意一个与es5写法的区别,在class中定义的方法都是不可枚举的,类的属性名可以使用表达式
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法,一个类必须有constructor方法,如果没有显示定义,一个空的constructor会被默认添加
class Point {
}
var point = new Point();
console.log(point);
/*
Point {}
__proto__:
constructor:class Point
*/
constructor方法默认返回实例对象,完全可以指定返回另一个对象
//es6
class Foo {
constructor(name) {
this.name = name;
return {}
}
}
var foo = new Foo('tom');
console.log(foo.name); //undefined
console.log(foo instanceof Foo) //false
//es5
function Foo(name) {
this.name=name;
return {}
}
var foo = new Foo('tom');
console.log(foo.name); //undefined
console.log(foo instanceof Foo); //false
和函数一样,类也可以使用表达式的形式,并且在定义完成后可以立即执行
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(person.name);
}
}('张三');
person.sayName(); // "张三"
在上面的代码中person只在Class内部有定义,表示当前类,在外部使用会报错
不存在变量提升
类不存在变量提升,这一点与es5不同,必须保证子类在父类之后定义
new Foo(); // ReferenceError
class Foo {}
私有方法
es6不提供私有方法,只能变相模拟,例如在定义时在名字上做区别,如将私有方法都定义以_开头,我个人觉的比较好的是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass {
//公有方法
foo(baz) {
this[bar](baz)
}
//私有方法
[baz](baz) {
return this[sanf] = baz
}
}
由Symbol定义的都是Symbol值,导致第三方无法获取,因此达到了私有方法和私有属性的效果
this的指向
类的方法内部包含有this,默认指向类的实例,但是如果在外部带调用时需要格外注意,在不同环境下,this的指向发生改变,可能会报错,推荐使用箭头函数
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承,如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法",静态方法不能在实例上调用,否则会报错
class Foo{
static classMethod(){
return 'hello'
}
}
Foo.classMethod() //hello
var foo=new Foo();
foo.classMethod(); //foo.classMethod is not a function
Class的getter和setter
和es5一样,在类的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
class MyClass{
constructor(){}
get prop(){
return 'getter'
}
set prop(value){
console.log('setter'+value)
}
}
let init=new MyClass();
init.prop=123;
console.log(init.prop);
继承
class可以通过extends关键字实现继承,这笔es5的通过修改原型链实现继承要更加方便
class Point{}
class ColorPoint extends Point{ }
父类的静态方法也会被子类继承,在子类的构造函数中,只有调用了super之后,才可以使用this关键字,否则会报错,因为子类实例的构建是基于父类实例的加工,只有super方法才能返回父类的实例
super关键字
super是es6新增加的关键字,指向当前对象的原型对象
super既可以当作函数使用,也可以当作对象使用,在这两种情况下,用法完全不同
第一种情况,super作为函数调用时,代表父类的构造函数,es6要求,子类的构造函数必须执行一次super函数
class A{
constructor(){
console.log(new.target.name);
}
}
class B extends A{
constructor(){
super()
}
}
new A() //A
new B() //B
new.target指向当前正在执行的函数,可以看到在super执行的时候,它指向的是子类B的构造函数,而不是父类A的构造函数,也就是说,虽然super代表了父类A的构造函数,但是返回的是子类B的实例,super内部的this指向的是B,调用super()相当于
A.prototype.constructor.call(this)
作为函数时,super()只能用在子类的constructor中,用在其他地方就会报错
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
class A{
p(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.p());
}
}
var b=new B(); //2
console.log(b.p()); //2
上面的代码中,子类B当中的super.p(),就是将super当作一个对象使用,super在普通方法之中,此时指向的是A.prototype,所以super.p()就相当于A.prototype.p();
es6规定,通过super调用父类方法时,方法内部的this指向子类
class A{
constructor(){
this.x=1;
}
print(){
console.log(this.x);
}
}
class B extends A{
constructor(){
super();
this.x=2;
}
m(){
super.print();
}
}
let b=new B();
b.m(); //2
在上面的代码中,虽然我们使用了super作为对象,可以直接使用到父类A的prototype的方法print,但是在print方法中使用了this,而在子类B中调用print方法时,此时的this指向是指向B,所以获取的是子类B本身的x属性
如果作为对象,用在静态方法中时,此时super将指向父类,而不是父类的原型对象
class Parent{
static myMethod(msg){
console.log('static',msg);
}
myMethod(msg){
console.log('instance',msg);
}
}
class Child extends Parent{
static myMethod(msg){
super.myMethod(msg);
}
myMethod(msg){
super.myMethod(msg);
}
}
Child.myMethod(1) //static 1
var child=new Child();
child.myMethod(2); //instance 2
注意,使用super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。也就是说必须是对象或方法的格式,不能是直接写一个super