Class 基本语法

前面的话

es6之前js都不支持类和类的继承,直到es6引入了类的特性。

es5的语法

js语言的传统方法通过构造函数定义并生成新对象。

function Person (name) {
    this.name = name;
}
Person.prototype.sayName = function() {
    console.log(this.name);
}
let person = new Person('xiaoqi');
person.sayName();// 'xiaoqi'
console.log(person instanceof Person);// true
console.log(person instanceof Object); // true

这段代码Person是一个构造函数,给其创建一个name属性,给Person的原型添加一个sayName()方法, 所有Person实例共享这个方法。使用new操作符创建person实例,由于存在原型继承的特性,所以person也是Object的实例。

Class 基本形式

es6引入了Class这个概念作为对象的模板,es6中的class可以看做只是一个语法糖,它的绝大部分的功能,ES5都能做到,新的class写法只是让对象原型的写法更清晰。

上面的例子改写为“类”的形式:

class Person {
    // 等价于Person 构造器
    constructor (name) {
        this.name = name;
    }
    // 等价于Person.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}
let person = new Person('xiaoqi');
person.sayName();// 'xiaoqi'
console.log(person instanceof Person);// true
console.log(person instanceof Object);// true
console.log(typeof Person);// function
console.log(person.constructor === Person);// true
console.log(typeof Person.prototype.sayName);// function
console.log(Person === Person.prototype.constructor);// true
  • 通过类声明的Person与创建构造函数Person的过程相似,只是在类中通过特殊的constructor方法名来定义构造函数,不需要加function这个保留字。
  • 方法之间不需要逗号分隔,加了会报错。
  • 私有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建,上例中name就是一个私有属性。
  • es6的类完全可以看作构造函数的另一种写法,类本身就指向构造函数。并且构造函数的prototype属性在es6上继续存在,类的所有方法(除constructor以外)都定义在类的prototype属性上。

[注意]: 与函数不同的是,类属性不可被赋予新值,Person.prototype就是一个只读类属性

差异

es6的类与es5的构造函数之间的差异:

  • 类的内部定义的原型上的所有方法都是不可枚举的
  • 类和模块内部默认使用严格模式,不需要使用use strict指定运行模式
  • constructor方法是类的默认方法,通过new命令生成的对象实例时自动调用该方法。一个类必须有constructor方法,如果没有显示的定义,一个空的constructor方法会被默认添加。
class Person {

}
// 等同于
class Point {
    constructor() {}
}

constructor方法默认返回实例对象(即this),不过完全可以像es5那样,返回一个新的对象

     class Foo{
            constructor() {
                return Object.create(null)
            }
        }
    var foo = new Foo();
    console.log(foo.constructor === Foo);// false
    console.log(foo.constructor);// undefined  

constructor函数返回了一个新的对象,导致实例对象不是Foo的实例

  • 类必须使用new操作符来调用,否则会报错

  • 类不存在变量提升,这与es5完全不同

    new Foo() // ReferenceError
    class Foo {}
    
  • es6的类只是es5构造函数的一层包装,所以函数的许多特性都被从class继承,例如name属性

        class Point {}
        Point.name // Point

name属性总是返回紧跟在class关键字后面的类名

Class表达式

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

    const MyClass = class Me {  
        getClassName () {
            return Me.name
        }
    }
    let inst = new MyClass();
    console.log(inst.getClassName());// Me
    console.log(MyClass.name);// Me

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

如果class内部没有用到,那么可以省略Me,也可以写成下面的形式:

   const MyClass = class{  } 

采用Class表达式,可以得到立即执行的class实例:

    let person = new class {
            constructor(name) {
                this.name = name;
            }
            sayName() {
                console.log(this.name);
            }
        }('xiaoqi');
        person.sayName();// 'xiaoqi'
私有方法

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

方法1: 在命名上加以区别

        class Widget {

            // 共有方法
            foo(baz) {
                this._bar(baz);
            }

            // 私有方法
            _bar(baz) {
                return this.snaf  = baz;
            }
        }

_bar方法前面的下划线表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部还是可以调用这个方法

法2:将私有方法移除模块,因为模块内的所有方法都是对外可见的

          class Widget {
            foo (baz) {
                bar.call(this, baz);
            }

        }
        function bar (baz) {
            return this.snaf = baz;
        }
    let instance = new Widget();
    instance.foo('xiao');
    console.log(instance.snaf); // 'xiao'

foo是共有的方法,内部调用bar.call(this, baz),使得bar实际上成为当前模块的私有方法

方法3: 利用Symbol值的唯一性将私有方法的名字命名为一个Symbol值

    const bar = Symbol('bar');
    const snaf = Symbol('snaf');
    export default class myClass {
        // 公有方法
        foo(baz) {
            this[bar](baz);
        }
        // 私有方法
        [bar](baz) {
            return this[snaf] = baz;
        }
    }

上面的代码中,bar和sanf都是Symbol值,导致第三方无法获取它们,因此达到私有方法与私有属性的效果。

私有属性

与私有方法一样,es6不支持私有属性。目前,有一个提案为给class加私有属性。方法是在属性名前面使用#来表示

        class Point {
            #x;
            constructor (x = 0) {
                #x = +x;
            }
            get x() {
                return #x;
            }
            set x(value) {
                #x = +value
            }
        }

#x表示私有属性x,在Point类之外是读取不到这个属性的。

该提案只规定了私有属性的写法。但是很自然的,它也可以用来编写私有方法

        class Foo{
            #a;
            #b;
            #sum() {
                return #a + #b;
            }
            printSum() {
                console.log(#sum());
            }
            constructor(a,b) {
                #a = a;
                #b = b;
            }
        }
访问器属性

与es5一样,在类的内部可以使用get和set关键字对某个属性设置存值函数和取值函数

    class CustomHTMLElement {
        constructor(element) {
            this.element = element;
        }
        get html() {
            return this.element.innerHTML;
        }
        set html(value) {
            this.element.innerHTML = value;
        }
    }
    var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, 'html');
    console.log('get' in descriptor); // true
    console.log('set' in descriptor); // true
    console.log(descriptor.enumerable);// false

这段代码中的CustomHTMLElement类是一个针对现有DOM元素的包装器,并通过getter和setter方法将这个元素的innerHTML方法委托给HTML属性,这个访问器属性是在CustomHTMLElement.prototypr上创建的。与其他方法一样,这两个方法都是不可枚举的。

Generator生成器方法

在某个方法之前加上星号(*),就表示该方法是一个Generator函数

        class MyClass {
            *createIterator() {
                yield 1;
                yield 2;
                yield 3;
            }
        }
        let instance = new MyClass();
        let iterator = instance.createIterator();
        console.log( iterator.next());// {value: 1, done: false}

这段代码创建一个MyClass的类,它有一个生成器方法createIterator(),其返回值为一个迭代器。

尽管生成器方法很实用,但如果类是用来表示值的集合的,那么为它定义一个默认迭代器会更有用。

通过Symbol.iterator定义生成器方法即可为类定义默认的迭代器。

        class Collection {
            constructor() {
                this.items = [];
            }
            *[Symbol.iterator]() {
                yield *this.items.values();
            }
        }
        var collection = new Collection();
        collection.items.push(1);
        collection.items.push(2);
        collection.items.push(3);
        for(let x of collection) {
            // 1
            // 2
            // 3
            console.log(x);
        }

上面的代码 Collection类的Symbol.iterator方法返回一个默认的遍历器。任何管理一系列值的类都应该引入默认的迭代器,因为一些与特定集合有关的操作需要所操作的集合含有一个迭代器。现在可以将collection实例直接用于for-of循环中或用展开运算符操作它。

静态成员

[es5中,模拟静态方法]

        function Person(name) {
                this.name = name;
            }
            // 静态方法
            Person.create = function (name) {
                console.log('xiao');
            }
            // 实例方法
            Person.prototype.sayName = function() {
                console.log(this.name);
            }
            Person.create();// 'xiao'

[class的静态方法]

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

            class PersonClass {
                // 等价于构造器
                constructor (name) {
                    this.name = name;
                }
                sayName() {
                    console.log(this.name);
                }
                static create(name) {
                    return new PersonClass(name);
                }
            }
            let person = PersonClass.create('xiaoqi');
            console.log(person.name);// 'xiaoqi'

静态方法在类上直接调用,不能再实例上调用

[class静态属性和实例属性]

实例属性:可以用等式直接写入类的定义中.

静态属性: 在实例属性上加static关键字.

        class MyClass {
            myProp = 42;
            static myStaticProp = 43; 
            constructor() {
                console.log(this.myProp);// 42
                console.log(MyClass.myStaticProp);//43
            }
        }
        let instance = new MyClass();
    }
new.target属性

new.target这个属性可以用来确定构造函数是怎么调用的。

在构造函数中返回new命令所作用的构造函数。如果构造函数不是通过new命令调用的,那么new.target会返回undefined。

        function Person(name) {
            if(new.target !== undefined) { // 或者 new.target === Person
                this.name = name
            }else {
                throw new Error('必须使用new生成实例')
            }
        }
        var person = new Person('xiaoqi');
        //var notPerson = Person.call(person, 'xiaoqi');// Uncaught Error: 必须使用new生成实例

子类继承父类时new.target会返回子类:

             class Rectangle {
            constructor(length, width) {
                console.log(new.target === Rectangle) ;
            }
        }
        class Square extends Rectangle {
            constructor(length) {
                super(length, length);
            }
        }
        let obj = new Square(3);// false

利用这个特点写出不能独立使用而必须继承之后才能使用的类:

        class Shape {
            constructor () {
                if(new.target === Shape){
                    throw new Error('本类不能实例化');
                }
            }
        }
        class Rectangle extends Shape {
            constructor (length, width){
                super();
            }
        }
        let x = new Shape();// Uncaught Error: 本类不能实例化
        let y = new Rectangle(4,4);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值