7. Class

作者: 羊烊羴 | 来源:发表于2018-01-11 15:17 被阅读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

    相关文章

      网友评论

        本文标题:7. Class

        本文链接:https://www.haomeiwen.com/subject/scxtoxtx.html