ES6

7. Class

2018-01-11  本文已影响0人  羊烊羴

在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

上一篇下一篇

猜你喜欢

热点阅读