ECMAScript 私有字段

ECMAScript 私有字段

一. ES 私有字段简介

在介绍 ECMAScript 私有字段前,我们先目睹一下它的 “芳容”:

class Counter extends HTMLElement {
  #x = 0;

  clicked() {
    this.#x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.#x.toString();
  }
}

window.customElements.define('num-counter', Counter);

第一眼看到 #x 是不是觉得很别扭,目前 TC39 委员会以及对此达成了一致意见,并且该提案已经进入了 Stage 3。那么为什么使用 # 符号,而不是其他符号呢?

TC39 委员会解释道,他们也是做了深思熟虑最终选择了 # 符号,而没有使用 private 关键字。其中还讨论了把 private 和 # 符号一起使用的方案。并且还打算预留了一个 @ 关键字作为 protected 属性 。

在 TypeScript 3.8 版本就开始支持ECMAScript 私有字段,使用方式如下:

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

let semlinker = new Person("Semlinker");

semlinker.#name;
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:

  • 私有字段以 # 字符开头,有时我们称之为私有名称;

  • 每个私有字段名称都唯一地限定于其包含的类;

  • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);

  • 私有字段不能在包含的类之外访问,甚至不能被检测到。

说到这里使用 # 定义的私有字段与 private 修饰符定义字段有什么区别呢?现在我们先来看一个 private 的示例:

class Person {
  constructor(private name: string){}
}

let person = new Person("Semlinker");
console.log(person.name);

在上面代码中,我们创建了一个 Person 类,该类中使用 private 修饰符定义了一个私有属性 name,接着使用该类创建一个 person 对象,然后通过 person.name 来访问 person 对象的私有属性,这时 TypeScript 编译器会提示以下异常:

Property 'name' is private and only accessible within class 'Person'.(2341)

那如何解决这个异常呢?当然你可以使用类型断言把 person 转为 any 类型:

console.log((person as any).name);

通过这种方式虽然解决了 TypeScript 编译器的异常提示,但是在运行时我们还是可以访问到 Person 类内部的私有属性,为什么会这样呢?我们来看一下编译生成的 ES5 代码,也许你就知道答案了:

var Person = /** @class */ (function () {
    function Person(name) {
      this.name = name;
    }
    return Person;
}());

var person = new Person("Semlinker");
console.log(person.name);

这时相信有些小伙伴会好奇,在 TypeScript 3.8 以上版本通过 # 号定义的私有字段编译后会生成什么代码:

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

以上代码目标设置为 ES2015,会编译生成以下代码:

"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) 
  || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
      throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};

var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) 
  || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
      throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};

var _name;
class Person {
    constructor(name) {
      _name.set(this, void 0);
      __classPrivateFieldSet(this, _name, name);
    }
    greet() {
      console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);
    }
}
_name = new WeakMap();

通过观察上述代码,使用 # 号定义的 ECMAScript 私有字段,会通过 WeakMap 对象来存储,同时编译器会生成 __classPrivateFieldSet__classPrivateFieldGet 这两个方法用于设置值和获取值。介绍完单个类中私有字段的相关内容,下面我们来看一下私有字段在继承情况下的表现。

二. ES 私有字段继承

为了对比常规字段和私有字段的区别,我们先来看一下常规字段在继承中的表现:

class C {
  foo = 10;

  cHelper() {
    return this.foo;
  }
}

class D extends C {
  foo = 20;

  dHelper() {
    return this.foo;
  }
}

let instance = new D();
// 'this.foo' refers to the same property on each instance.
console.log(instance.cHelper()); // prints '20'
console.log(instance.dHelper()); // prints '20'

很明显不管是调用子类中定义的 cHelper() 方法还是父类中定义的 dHelper() 方法最终都是输出子类上的 foo 属性。接下来我们来看一下私有字段在继承中的表现:

class C {
  #foo = 10;

  cHelper() {
    return this.#foo;
  }
}

class D extends C {
  #foo = 20;

  dHelper() {
    return this.#foo;
  }
}

let instance = new D();
// 'this.#foo' refers to a different field within each class.
console.log(instance.cHelper()); // prints '10'
console.log(instance.dHelper()); // prints '20'

通过观察上述的结果,我们可以知道在 cHelper() 方法和 dHelper() 方法中的 this.#foo 指向了每个类中的不同字段。关于 ECMAScript 私有字段的其他内容,我们不再展开,感兴趣的读者可以自行阅读相关资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端梦工厂+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值