美文网首页
class 基本语法

class 基本语法

作者: 无迹落花 | 来源:发表于2018-02-22 17:41 被阅读55次

    1.简介

    JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。


    1.jpg

    上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

    ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

    基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

    2.jpg

    上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而 this关键字则代表实例对象也就是说,ES5 的构造函数Person,对应 ES6 的Person类的构造方法。

    Person类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

    上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

    使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

    构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

    在类的实例上面调用方法,其实就是调用原型上的方法。

    prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。

    1.jpg

    另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

    1.jpg

    上面代码中,toString方法是Person类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。

    1.jpg

    上面代码采用 ES5 的写法,toString方法就是可枚举的。



    2.严格模式

    类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

    考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。


    3.constructor 方法

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。

    // 定义类
    
    class Person{
        constructor(name,age){
            this.name=name;
            this.age=age;
            console.log(2213)   // 2213
        }
    }
    
    let p1=new Person('Andy',31)
    
    

    可以看到控制台打印了2213, 证明通过new命令生成实例对象时,自动调用constructor方法

    另外,类里面的this指代的都是实例对象,请看下面这个例子

    // 定义类
    
    class Person{
        constructor(name,age){
            this.name=name;
            this.age=age;
        }
    
        toString(){
            console.log(this) 
        }
    }
    
    let p1=new Person('Andy',31)   //  Person {name: "Andy", age: 31}
    
    
    p1.toString()
    

    一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

    1.jpg

    上面代码中,定义了一个空的类Person,JavaScript 引擎会自动为它添加一个空的constructor方法。

    constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

    1.jpg

    上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

    类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。


    4.类的实例对象

    生成类的实例对象的写法,与 ES5 完全一样,也是使用new命令。

    需要注意的是,需要加上new,如果忘记加上new,像函数那样调用Class,将会报错。

    1.jpg

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

    toString

    上面代码中,定义了一个 Person类,constructor中的sexagenew出来的实例对象的属性,打印 Person.hasOwnProperty('sex')结果是false, 打印 p1.hasOwnProperty('sex')结果是true 。 这也证明了 constructor中的this指代的是实例对象。



    与 ES5 一样,类的所有实例共享一个原型对象。

    1.jpg

    上面代码中,p1和p2都是Person的实例,它们的原型都是Person.prototype,所以 _proto_属性是相等的。

    这也意味着,可以通过实例的_proto_属性为“类”添加方法。

    1.jpg

    上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,
    因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。
    这意味着,使用实例的_proto
    属性改写原型,必须相当谨慎,
    不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。




    5.class表达式

    与函数一样,类也可以使用表达式的形式定义。

    const MyClass = class Me {
      getClassName() {
        return Me.name;
      }
    };
    
    let inst = new MyClass();
    inst.getClassName() // Me
    Me.name // ReferenceError: Me is not defined
    

    上面代码使用表达式定义了一个类。

    需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。



    6.不存在变量提升

    类不存在变量提升(hoist),这一点与 ES5 完全不同

    new Foo(); // ReferenceError
    class Foo {}
    

    上面代码中,Foo类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

    {
      let Foo = class {};
      class Bar extends Foo {
      }
    }
    

    上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。


    7.私有方法和私有属性

    私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。




    8.this的指向

    类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

    1.jpg

    上面的例子说明,在单独执行printName这个函数的时候,由于this的指向问题,函数中的代码 this.print()当前上下文环境中找不到print这个函数,所以报错。

    一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

    请看下面这个更为复杂的情况。

    2.jpg

    这个例子中,在class Logger的constructor中我们使用了bind方法,把printName这个方法的执行上下文环境绑定到了class Logger的实例上。

    bind方法是新创建一个函数,然后把它的上下文绑定到bind()括号中的参数上,然后将它返回。

    所以,bind后函数不会执行,而只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数。

    后面的代码中,我们new了一个 Logger的实例,打印出来。
    发现实例中有userName passwords 和 printName三个属性,前面两个是key value属性,最后的一个 printName是Logger实例的一个方法。

    通过var {printName}=logger把printName单独拿出来,打印printName发现它是一个函数,它是实例logger的一个OwenProperty,其原型proto指向Function的prototype属性。

    注意到printName._proto与实例 logger._proto显然是不等的。

    它与Function的原型才相等!

    前面提到,我们使用bind方法,将printName的上下文环境this改变成了class Logger的实例对象上,所以直接调用 printName方法可行了,它会打印出hello lv的信息。


    关于bind()函数

    1.jpg

    这个例子中,我们在constructor中使用了bind方法,将实例对象logger的上下文环境this绑定到了一个新的对象上面,新的对象上面也有print方法

    我们把实例对象logger的print方法赋值给printName方法,单独调用printName方法时,发现打印的都是 敌法师,新的对象里面的name,sex属性值。



    9.name 属性

    由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

    class Point {}
    Point.name // "Point"
    

    前面有提到过。

    Person.hasOwnProperty('name');打印的是true,是因为巧合就是这个原因。实际上打印 Person.hasOwnProperty('age');打印的就是false




    10.Class 的取值函数(getter)和存值函数(setter)

    与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    1.jpg

    上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

    存值函数和取值函数是设置在属性的 Descriptor 对象上的。

    12.class的静态方法

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    1.jpg

    上面代码中,Person类有个静态方法hello(),可以在Person类上调用,但是不能在Person的实例上调用。

    尝试 p.hello()是报错的。

    注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

    1.jpg

    上面代码中,静态方法bar调用了this.baz,这里的this指的是Person类,而不是Person的实例,等同于调用Person.baz。

    另外,从这个例子还可以看出,静态方法可以与非静态方法重名。



    父类的静态方法,可以被子类继承。


    静态方法也是可以从super对象上调用的。

    1.jpg




    13.class的静态属性和实例属性

    静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

    // 定义类
    
    class Person{
        
    }
    
    Person.prop=1;
    
    console.log(Person.prop);  //  1
    

    上面的写法为Person类定义了一个静态属性prop。

    目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。

    // 以下两种写法都无效
    class Person {
      // 写法一
      prop: 2
    
      // 写法二
      static prop: 2
    }
    
    Person.prop // undefined
    

    相关文章

      网友评论

          本文标题:class 基本语法

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