• ES6 入门教程 22 Class 的基本语法 22.9 静态属性 & 22.10 私有方法和私有属性


    ES6 入门教程

    ECMAScript 6 入门

    作者:阮一峰

    本文仅用于学习记录,不存在任何商业用途,如侵删

    22 Class 的基本语法

    22.9 静态属性

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

    class Foo {
    }
    
    Foo.prop = 1;
    Foo.prop // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

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

    目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。

    class MyClass {
      static myStaticProp = 42;
    
      constructor() {
        console.log(MyClass.myStaticProp); // 42
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    这个新写法大大方便了静态属性的表达。

    // 老写法
    class Foo {
      // ...
    }
    Foo.prop = 1;
    
    // 新写法
    class Foo {
      static prop = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。

    另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。

    22.10 私有方法和私有属性
    22.10.1 早期解决方案

    私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。

    这是常见需求,有利于代码的封装,但早期的 ES6 不提供,只能通过变通方法模拟实现。

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

    class Widget {
    
      // 公有方法
      foo (baz) {
        this._bar(baz);
      }
    
      // 私有方法
      _bar(baz) {
        return this.snaf = baz;
      }
    
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。

    class Widget {
      foo (baz) {
        bar.call(this, baz);
      }
    
      // ...
    }
    
    function bar(baz) {
      return this.snaf = baz;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar()实际上成为了当前类的私有方法。

    还有一种方法是利用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;
      }
    
      // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上面代码中,barsnaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。

    但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。

    const inst = new myClass();
    
    Reflect.ownKeys(myClass.prototype)
    // [ 'constructor', 'foo', Symbol(bar) ]
    
    • 1
    • 2
    • 3
    • 4

    上面代码中,Symbol 值的属性名依然可以从类的外部拿到。

    22.10.2 私有属性的正式写法

    ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。

    class IncreasingCounter {
      #count = 0;
      get value() {
        console.log('Getting the current value!');
        return this.#count;
      }
      increment() {
        this.#count++;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。

    const counter = new IncreasingCounter();
    counter.#count // 报错
    counter.#count = 42 // 报错
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    上面示例中,在类的外部,读取或写入私有属性#count,都会报错。

    另外,不管在类的内部或外部,读取一个不存在的私有属性,也都会报错。这跟公开属性的行为完全不同,如果读取一个不存在的公开属性,不会报错,只会返回undefined

    class IncreasingCounter {
      #count = 0;
      get value() {
        console.log('Getting the current value!');
        return this.#myCount; // 报错
      }
      increment() {
        this.#count++;
      }
    }
    
    const counter = new IncreasingCounter();
    counter.#myCount // 报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    上面示例中,#myCount是一个不存在的私有属性,不管在函数内部或外部,读取该属性都会导致报错。

    注意,私有属性的属性名必须包括#,如果不带#,会被当作另一个属性。

    class Point {
      #x;
    
      constructor(x = 0) {
        this.#x = +x;
      }
    
      get x() {
        return this.#x;
      }
    
      set x(value) {
        this.#x = +value;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上面代码中,#x就是私有属性,在Point类之外是读取不到这个属性的。由于井号#是属性名的一部分,使用时必须带有#一起使用,所以#xx是两个不同的属性。

    这种写法不仅可以写私有属性,还可以用来写私有方法。

    class Foo {
      #a;
      #b;
      constructor(a, b) {
        this.#a = a;
        this.#b = b;
      }
      #sum() {
        return this.#a + this.#b;
      }
      printSum() {
        console.log(this.#sum());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面示例中,#sum()就是一个私有方法。

    另外,私有属性也可以设置 getter 和 setter 方法。

    class Counter {
      #xValue = 0;
    
      constructor() {
        console.log(this.#x);
      }
    
      get #x() { return this.#xValue; }
      set #x(value) {
        this.#xValue = value;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面代码中,#x是一个私有属性,它的读写都通过get #x()set #x()操作另一个私有属性#xValue来完成。

    私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性。

    class Foo {
      #privateValue = 42;
      static getPrivateValue(foo) {
        return foo.#privateValue;
      }
    }
    
    Foo.getPrivateValue(new Foo()); // 42
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    上面代码允许从实例foo上面引用私有属性。

    私有属性和私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。

    class FakeMath {
      static PI = 22 / 7;
      static #totallyRandomNumber = 4;
    
      static #computeRandomNumber() {
        return FakeMath.#totallyRandomNumber;
      }
    
      static random() {
        console.log('I heard you like random numbers…')
        return FakeMath.#computeRandomNumber();
      }
    }
    
    FakeMath.PI // 3.142857142857143
    FakeMath.random()
    // I heard you like random numbers…
    // 4
    FakeMath.#totallyRandomNumber // 报错
    FakeMath.#computeRandomNumber() // 报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上面代码中,#totallyRandomNumber是私有属性,#computeRandomNumber()是私有方法,只能在FakeMath这个类的内部调用,外部调用就会报错。

    22.10.3 in 运算符

    前面说过,直接访问某个类不存在的私有属性会报错,但是访问不存在的公开属性不会报错。这个特性可以用来判断,某个对象是否为类的实例。

    class C {
      #brand;
    
      static isC(obj) {
        try {
          obj.#brand;
          return true;
        } catch {
          return false;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面示例中,类C的静态方法isC()就用来判断,某个对象是否为C的实例。它采用的方法就是,访问该对象的私有属性#brand。如果不报错,就会返回true;如果报错,就说明该对象不是当前类的实例,从而catch部分返回false

    因此,try...catch结构可以用来判断某个私有属性是否存在。但是,这样的写法很麻烦,代码可读性很差,ES2022 改进了in运算符,使它也可以用来判断私有属性。

    class C {
      #brand;
    
      static isC(obj) {
        if (#brand in obj) {
          // 私有属性 #brand 存在
          return true;
        } else {
          // 私有属性 #foo 不存在
          return false;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面示例中,in运算符判断某个对象是否有私有属性#foo。它不会报错,而是返回一个布尔值。

    这种用法的in,也可以跟this一起配合使用。

    class A {
      #foo = 0;
      m() {
        console.log(#foo in this); // true
        console.log(#bar in this); // false
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意,判断私有属性时,in只能用在类的内部。

    子类从父类继承的私有属性,也可以使用in运算符来判断。

    class A {
      #foo = 0;
      static test(obj) {
        console.log(#foo in obj);
      }
    }
    
    class SubA extends A {};
    
    A.test(new SubA()) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    上面示例中,SubA从父类继承了私有属性#fooin运算符也有效。

    注意,in运算符对于Object.create()Object.setPrototypeOf形成的继承,是无效的,因为这种继承不会传递私有属性。

    class A {
      #foo = 0;
      static test(obj) {
        console.log(#foo in obj);
      }
    }
    const a = new A();
    
    const o1 = Object.create(a);
    A.test(o1) // false
    A.test(o1.__proto__) // true
    
    const o2 = {};
    Object.setPrototypeOf(o2, a);
    A.test(o2) // false
    A.test(o2.__proto__) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面示例中,对于修改原型链形成的继承,子类都取不到父类的私有属性,所以in运算符无效。

  • 相关阅读:
    c++day4
    洛谷-收集邮票-(期望dp+期望的平方+平方的期望)
    MySQL 中LIMIT的使用详解
    安卓JNI使用OpenCV
    C++学习笔记(二十三)
    面试的朋友听我说,18 个 MyBatis 高频知识及学习笔记,双手奉上
    Windows 10 启用windows功能.NET Framework3.5 时 windows无法完成请求的更改 错误代码:0x80072F8F解决方案
    【英语:基础高阶_经典外刊阅读】L1.阅读理解—读题定位法
    【智能算法】象群算法(EHO)原理及实现
    如何拦截响应内容并修改响应头
  • 原文地址:https://blog.csdn.net/weixin_44226181/article/details/127993353