zoukankan      html  css  js  c++  java
  • Class 基本语法《ES6标准入门(第3版)》

    Class

    • constructor()
    • 实例对象
    • 表达式
    • 提升
    • 私有方法和私有属性
    • this
    • name属性
    • 取值函数和存值函数
    • Generator方法
    • 静态方法
    • 静态属性和实例属性
    • new.target属性
    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        toString() {
            return '(' + this.x + ',' + this.y + ')';
        }
    }

    1.constructor是构造方法

    2.this关键字则代表实例对象

    3.定义“类”的方法的时候,前面不需要加上function关键字,直接把函数定义放进去了就可以了。

    4.方法之间不需要逗号分隔

    class Point {
        
    }
    
    typeof Point // "function"
    Point === Point.prototype.constructor // true

    5.类的数据类型就是函数,类本身就指向构造函数

    Point.protype = {
        constructor() {},
        toString() {},
        toValue() {}
    };

    6.事实上,类的所有方法都定义在类的prototype属性上面。

    b.constructor === B.prototype.constructor // true

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

    Object.assign(Point.prototype, {
        toString() {},
        toValue() {}
    });

    8.Object.assign方法可以很方便地一次向类添加多个方法

    Point.prototype.constructor === Point // true

    9.prototype对象的constructor属性,直接指向“类”的本身。

    Object.keys(Point.prototype); // []
    Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]

    10.类的内部所有定义的方法,都是不可枚举的。

    let methodName = 'getArea';
    class Square {
        constructor(length) {
            
        }
        [methodName]() {
            
        }
    }

    11.类的属性名,可以采用表达式


    类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式。

    只要你的代码写在类或模块之中,就只有严格模式可用。

    ES6实际上把整个语言升级到了严格模式。


    constructor()

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

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

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

    class Foo {
        constructor() {
            return Object.create(null);
        }
    }
    new Foo() instanceof Foo // false

    3.constructor方法默认返回实例对象,即this,完全可以指定返回另一个对象

    class Foo {
        constructor() {
            return Object.create(null);
        }
    }
    Foo(); // TypeError

    4.类必须使用new调用,否则会报错。

    这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。


    实例对象

    class Point {
        ...
    }
    var point = Point(2, 3); // 报错
    var point = new Point(2, 3); // 正确

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

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        toString() {
            return '(' +this.x + ', ' + this.y + 'y';
        }
    }
    var point = new Point(2, 3);
    point.toString(); // (2, 3)
    point.hasOwnProperty('x'); // true
    point.hasOwnProperty('y'); // true
    point.hasOwnProperty('toString'); // false
    point._proto_.hasOwnProperty('toString'); // true

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

    hasOwnProperty用来检测是不是实例的属性。

    var p1 = new Point(2, 3);
    var p2 = new Point(3, 2);
    p1._proto_ === p2._proto_ // ture

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

    var p1 = new Point(2, 3);
    var p2 = new Point(3, 2);
    p1._proto_.printName = function() { return 'Oops' };
    p1.printName() // "Oops"
    p2.printName() // "Oops"
    var p3 = new Point(4,2);
    p3.printName() // "Oops"

    4.可以通过实例的_proto_属性为“类”添加方法。

    生产环境中,我们可以使用Object.getPrototypeOf方法来获取实例对象的原型。


    表达式

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

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

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

    如果类的内部没用到的话,可以省略Me。

    let person = new class {
        constructor(name) {
            this.name = name;
        }
        sayName() {
            console.log(this.name);
        }
    }('张三');
    person.sayName(); // "张三"

    2.采用Class表达式,可以写出立即执行的Class


    提升

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

    1.类不存在变量提升。

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

    2.必须保证子类在父类之后定义。


    私有方法和私有属性

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

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

    一种做法是在命名上加以区别。

    _bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。

    class Widget {
        foo(baz) {
            bar.call(this, baz);
        }
        ...
    }
    function bar(baz) {
        return this.snaf = baz;
    }

    2.另一种方法就是索性将私有方法移除模块,因为模块内部的所有方法都是对外可见的。

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

    3.还有一种方法就是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。


    this

    class Logger {
        printName(name = 'there') {
            this.print(`Hello ${name}`);
        }
        print(text) {
            console.log(text);
        }
    }
    const logger = new Logger();
    const { printName } = logger;
    printName(); // TypeError

    1.类的方法内部如果含有this,它默认指向类的实例

    但是,必须非常小心,一点单独使用该方法,很可能报错。

    如果将这个方法提取出来单独使用,this会指向该方法运行时所作的环境,因为找不到print方法而导致报错。

    class Logger {
        constructor() {
            this.printName = this.printName.bind(this);
        }
        ...
    }

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

    class Logger {
        constructor() {
            this.printName = (name = 'there') => {
                this.print(`Hellow ${name}`);
            };
        }
        ...
    }

    3.另一种解决方法是使用箭头函数。

    function selfish(target) {
        const cache = new WeakMap();
        const handler = {
            get(target, key) {
                const value = Refkect.get(target, key);
                if (typeof value !== 'function') {
                    return value;
                }
                if (!cache.has(value)) {
                    cache.set(value, value.bind(target));
                }
                return cache.get(value);
            }
        };
        const proxy = new Proxy(target, handler);
        return proxy
    }
    const logger = selfish(new Logger());

    4.还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this。


    name属性

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

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

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


    取值函数和存值函数

    class MyClass {
        constructor() {
            ...
        }
        get prop() {
            return 'getter';
        }
        set prop(value) {
            console.log('setter: ' + value);
        }
    }
    let inst = new MyClass();
    inst.prop = 123; // setter: 123
    inst.prop // 'getter'

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

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

    class CustomHTMLElement {
        constructor(element) {
            this.element = element;
        }
        get html() {
            return this.element.innerHTML;
        }
        set html(value) {
            this.element.innerHTML = value;
        }
    }
    var decriptor = Object.getOwnPropertyDescroptor(CustomHTMLElement.prototype, "html");
    "get" in decriptor // true
    "set" in decriptor // true

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

    上面代码中,存值函数和取值函数是定义在html属性的描述对象上面。这与ES5完全一致。


    Generator方法

    class Foo {
        constructor(...args) {
            this.args = args;
        }
        * [Symbol.iterator]() {
            for (let arg of this.args) {
                yield arg;
            }
        }
    }
    for (let x of new Foo('hello', 'world')) {
        console.log(x);
    }
    // hello
    // world

    1.如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。

    Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。

    Symbol.iterator方法返回一个Foo类的默认遍历器,for...of 循环会自动调用这个遍历器。


    静态方法

    class Foo {
        static classMethod() {
            return 'hello';
        }
    }
    Foo.classMethod(); // "hello"
    var foo = new Foo();
    foo.classMethod(); // TypeError

    1.类相当于实例的原型,所有在类中定义的方法,都会被实例继承。

    如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    class Foo {
        static bar() {
            this.baz();
        }
        statoc baz() {
            console.log('hello');
        }
        baz() {
            console.log('world');
        }
    }
    Foo.bar(); // hello

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

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

    class Foo {
        static classMethod() {
            return 'hello';
        }
    }
    class Bar extends Foo {
    }
    Bar.classMethod(); // 'hello'

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

    class Foo {
        static classMethod() {
            return 'hello';
        }
    }
    class Bar extends Foo {
        static classMethod() {
            return super.classMethod() + ', too';
        }
    }
    Bar.classMehtod(); // "hello, too"

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


    静态属性和实例属性

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

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

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

    class ReactCounter extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                count: 0
            };
        }
    }

    2.以前,我们定义实例属性,只能写在类的constructor方法里面。


    new.target属性

    function Person(name) {
        if (new.target !== undefined) {
            this.name = name;
        } else {
            throw new Eooro('必须使用new命令生成实例');
        }
    }
    // 另一种写法
    function Person(name) {
        if (new.target === Person) {
            this.name = name;
        } else {
            throw new Error('');
        }
    }
    var person = new Person('张三'); // 正确
    var notAPerson = Person.call(person, '张三'); // 报错

    1.new是从构造函数生成实例对象的命令。

    ES6为new命令引入了一个new.target属性,该属性一般用在构造函数之中返回new命令作用于的那个构造函数

    如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

    class Rectangle {
        constructor(lenght, width) {
            console.log(new.target === Rectangle);
            this.length = length;
            this.width = width;
        }
    }
    var obj = new Rectangle(3, 4); // true
    class Rectangle {
        constructor(length, width) {
            console.log(new.target === Rectangle);
            ...
        }
    }
    class Square extends Rectangle {
        constructor(length) {
            super(legnth, length);
        }
    }
    var obj = new Square(3); // false

    2.Class内部调用new.target,返回当前Class

    需要注意的是,子类继承父类时,new.target会返回子类

    class Shape {
        constructor() {
            if (new.target === Shape) {
                throw new Error('本类不能实例化');
            }
        }
    }
    class Rectangle extends Shape {
        constructor(length, width) {
            super();
            ...
        }
    }
    var x = new Shape(); // 报错
    var y = new Rectangle(3, 4); // 正确

    3.利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

    注意,在函数外部,使用new.target会报错。

  • 相关阅读:
    BZOJ 3531[Sdoi2014]旅行
    BZOJ4998 星球联盟
    BZOJ2959 长跑
    【北京集训D2T3】tvt
    [Bzoj]5343: [Ctsc2018]混合果汁
    HGOI20190810 省常中互测3
    HGOI20190809 省常中互测2
    HGOI20190808 省常中互测1
    组合排列和组合数 学习笔记
    2-SAT (two-statisfiability) 算法 学习笔记
  • 原文地址:https://www.cnblogs.com/linxian95/p/10038547.html
Copyright © 2011-2022 走看看