【问题标题】:Why can I access TypeScript private members when I shouldn't be able to?为什么我不能访问 TypeScript 私有成员?
【发布时间】:2012-09-24 16:27:49
【问题描述】:

我正在研究 TypeScript 中私有成员的实现,我发现它有点令人困惑。 Intellisense 不允许访问私有成员,但在纯 JavaScript 中,它就在那里。这让我觉得 TS 没有正确实现私有成员。 有什么想法吗?

class Test{
  private member: any = "private member";
}
alert(new Test().member);

【问题讨论】:

  • 您想知道为什么 IntelliSense 没有在带有 alert() 的行中为您提供私有成员?
  • 不。我想知道为什么 TS 有一个私有的,而这只是智能感知的糖,而不是它编译成的 JavaScript。在typescriptlang.org/Playground 中执行的这段代码会提醒私有成员值。
  • 如前所述,您必须将项目声明为私有上下文中的变量以使其私有。我猜打字稿不会这样做,因为它与添加到原型相比效率低下。它还与类型定义混淆(私有成员实际上不是类的一部分)
  • 如果你想要原型上存在真正的私有变量,确实需要一些开销,但我已经编写了一个名为 ClassJS 的库,它在 GitHub 上就是这样做的:github.com/KthProg/ClassJS
  • 值得注意的是,这些天 typescript 支持 javascript 提出的 #privateClassFields

标签: javascript typescript


【解决方案1】:

就像类型检查一样,成员的隐私只在编译器内强制执行。

私有属性被实现为常规属性,类外的代码不允许访问它。

要在类中真正私有化,它不能是类的成员,而是在创建对象的代码内的函数范围内创建的局部变量。这意味着您不能像类成员一样访问它,即使用 this 关键字。

【讨论】:

  • JavaScript 程序员将局部变量放入对象构造函数并将其用作私有字段并不罕见。我很惊讶他们不支持这样的东西。
  • @Eric:由于 TypeScript 使用方法的原型而不是在构造函数中添加方法作为原型,因此无法从方法中访问构造函数中的局部变量。可能可以在类的函数包装器中创建一个局部变量,但我还没有找到一种方法来做到这一点。但是,这仍然是一个局部变量,而不是私有成员。
  • 这是我一直在提供反馈的内容。我相信它应该提供创建显示模块模式的选项,因此私有成员可以保持私有,而公共成员可以在 JavaScript 中访问。这是一种常见的模式,将在 TS 和 JS 中提供相同的可访问性。
  • 有一个解决方案可以用于私有静态成员:web.archive.org/web/20140521231105/https://basarat.com/2013/03/…
  • @BasaratAli:这是一个静态变量,在类的方法中可用,但它不是类的成员,即你不能使用this 关键字访问它。
【解决方案2】:

JavaScript 确实支持私有变量。

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

在 TypeScript 中,这会这样表达:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

编辑

这种方法只能谨慎在绝对需要的情况下使用。例如,如果您需要临时缓存密码。

使用这种模式会产生性能成本(与 Javascript 或 Typescript 无关),并且只能在绝对必要的情况下使用。

【讨论】:

  • 打字稿不是一直通过设置var _this 来在作用域函数中使用吗?为什么你会对在课堂范围内这样做感到不安?
  • 没有。 var _this 只是对 this 的引用。
  • 更准确地称它们为构造函数变量,而不是私有的。这些在原型方法中是不可见的。
  • 哦,是的,抱歉问题出在其他问题上,事实上,对于您创建的每个实例,都会再次创建 doSomething,因为它不是原型链的一部分。
  • @BarbuBarbu 是的,我同意。这是这种方法的一个大问题,也是应该避免的原因之一。
【解决方案3】:

一旦对WeakMap 的支持得到更广泛的使用,示例#3 here 中详细介绍了一种有趣的技术。

它允许私有数据并通过允许从原型方法而不是实例方法访问数据来避免 Jason Evans 示例的性能成本。

链接的 MDN WeakMap 页面列出了 Chrome 36、Firefox 6.0、IE 11、Opera 23 和 Safari 7.1 的浏览器支持。

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

【讨论】:

  • 我喜欢它!基本上,这意味着将私有属性隐藏到聚合类中。最有趣的是......添加对protectedparameters 的支持怎么样? :D
  • @RamtinSoltani 链接的文章指出,由于弱映射的工作方式,这不会阻止垃圾收集。如果有人想在使用这种技术时更加安全,他们可以实现自己的处理代码,从每个弱映射中删除类实例键。
  • 来自 MDN 页面:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…。相比之下,本机 WeakMap 持有对关键对象的“弱”引用,这意味着它们不会阻止垃圾回收,以防没有其他对关键对象的引用。这也避免了阻止地图中值的垃圾收集。
  • @RyanThomas 没错,这是我不久前留下的旧评论。 WeakMaps 与 Maps 不同,不会导致内存泄漏。所以使用这种技术是安全的。
  • @RamtinSoltani 所以删除你的旧评论?>
【解决方案4】:

在 TypeScript 中,私有函数只能在类内部访问。喜欢

当您尝试访问私有成员时,它会显示错误。示例如下:

注意:使用 javascript 就可以了,并且两个函数都可以访问 外面。

【讨论】:

  • OP:“但在纯 JavaScript 中,一切都在那里” - 我认为您没有解决生成的 JavaScript 公开公开“私有”函数的问题
  • @alexanderbird 我认为他想说 TypeScript 通常就足够了。当我们使用 TypeScript 进行开发时,我们会在项目范围内使用它,因此来自 JavaScript 的隐私并不是什么大问题。因为首先,原始代码对开发人员很重要,而不是转译 (JavaScript) 代码。
  • 除非您正在编写和发布 JavaScript 库,否则转译的代码确实很重要
  • 你的回答离题了。
【解决方案5】:

感谢 Sean Feldman 提供有关此问题的官方讨论的链接 - 请参阅 his answer 获取链接。

我阅读了他链接的讨论,以下是要点摘要:

  • 建议: 构造函数中的私有属性
    • 问题:无法从原型函数访问
  • 建议: 构造函数中的私有方法
    • 问题: 与属性相同,而且您失去了在原型中为每个类创建一次函数的性能优势;而是为每个实例创建函数的副本
  • 建议: 添加样板以抽象属性访问并强制可见性
    • 问题: 主要的性能开销; TypeScript 专为大型应用程序设计
  • 建议: TypeScript 已经将构造函数和原型方法定义包装在一个闭包中;将私有方法和属性放在那里
    • 将私有属性放入闭包的问题:它们变成了静态变量;每个实例没有一个
    • 将私有方法放入该闭包的问题:如果没有某种解决方法,他们无法访问this
  • 建议: 自动修改私有变量名
    • 反参数:这是一个命名约定,而不是一种语言结构。自己弄坏它
  • 建议: 使用@private 注释私有方法,以便识别该注释的缩小器可以有效地缩小方法名称
    • 对此没有明显的反驳论据

在发出的代码中添加可见性支持的总体反驳:

  • 问题在于 JavaScript 本身没有可见性修饰符 - 这不是 TypeScript 的问题
  • 在 JavaScript 社区中已经建立了一种模式:在私有属性和方法前加上下划线,表示“风险自负”
  • 当 TypeScript 设计者说真正私有的属性和方法不是“可能的”时,他们的意思是“在我们的设计约束下不可能”,具体来说:
    • 发出的 JS 是惯用的
    • 样板文件极少
    • 与普通的 JS OOP 相比没有额外的开销

【讨论】:

【解决方案6】:

我意识到这是一个较早的讨论,但对于 TypeScript 中所谓的私有变量和方法“泄漏”到已编译 JavaScript 类的公共接口中的问题,分享我的解决方案可能仍然有用。

对我来说,这个问题纯粹是装饰性的,也就是说,当在 DevTools 中查看实例变量时,这完全是关于视觉混乱的问题。我的解决方法是将私有声明组合在另一个类中,然后在主类中实例化并分配给 private(但在 JS 中仍然公开可见)变量,其名称类似于 __(双下划线)。

例子:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

当在 DevTools 中查看 myClass 实例时,您会看到它们整齐地分组在折叠__ 属性:

【讨论】:

  • 我喜欢。看起来很干净。
【解决方案7】:

由于 TypeScript 3.8 将发布,您将能够声明 无法在包含的类之外访问甚至检测到的私有字段

class Person {
    #name: string

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

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

let jeremy = new Person("Jeremy Bearimy");

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

私有字段以# 字符开头

请注意,这些私有字段将不同于标有 private 关键字的字段

参考。 https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/

【讨论】:

    【解决方案8】:

    这是添加适当私有属性的可重用方法:

    /**
     * Implements proper private properties.
     */
    export class Private<K extends object, V> {
    
        private propMap = new WeakMap<K, V>();
    
        get(obj: K): V {
            return this.propMap.get(obj)!;
        }
    
        set(obj: K, val: V) {
            this.propMap.set(obj, val);
        }
    }
    

    假设你有一个类 Client 需要两个私有属性:

    • prop1: string
    • prop2: number

    下面是你如何实现它:

    // our private properties:
    interface ClientPrivate {
        prop1: string;
        prop2: number;
    }
    
    // private properties for all Client instances:
    const pp = new Private<Client, ClientPrivate>();
    
    class Client {
        constructor() {
            pp.set(this, {
                prop1: 'hello',
                prop2: 123
            });
        }
    
        someMethod() {
            const privateProps = pp.get(this);
    
            const prop1 = privateProps.prop1;
            const prop2 = privateProps.prop2;
        }
    }
    

    如果您只需要一个私有属性,那么它会变得更加简单,因为在这种情况下您不需要定义任何 ClientPrivate

    值得注意的是,在大多数情况下,Private 类只是提供了一个可读性很好的签名,而直接使用 WeakMap 则没有。

    【讨论】:

      【解决方案9】:

      其实这个问题的答案很简单。

      你有这个代码:

      class Test{
        private member: any = "private member";
      }
      alert(new Test().member);
      

      在该代码中,您混合了两种不同的语言。这部分是 TypeScript:

      class Test{
        private member: any = "private member";
      }
      

      这部分是 JavaScript:

      alert(new Test().member);
      

      Test 类中 member 字段中的 private 关键字用于 TypeScript。因此 TypeScript 中的其他类无法访问 member 字段,因为 TypeScript 编译器不允许这样做。例如,如果您尝试这样做,它将无法正常工作:

      class Test2 {
          constructor() {
              var test = new Test();
              test.member = "Cannot do this";
          }
      }
      

      privatepublic 放在生成的JavaScript 上没有区别。生成的 JavaScript 代码将始终为:

      var Test = (function () {
          function Test() {
              this.member = "private member";
          }
          return Test1;
      }());
      

      因此,您可以这样做,因为 JavaScript 允许这样做:

      alert(new Test().member);
      

      这不是一成不变的规则,可能有一些我不知道的情况,但如果你使用的是 TypeScript,那么为什么要担心你被允许使用 JavaScript 做什么呢?这个想法是您不再编写 JavaScript,因此您在 JavaScript 中可以/不能做的事情不再需要担心。对我来说,这种担心就像编写 C# 代码然后说我为什么能够更改 CIL 或汇编语言中的私有字段一样。我不确定 CIL 是否允许,但这不是重点。关键是,在用 C# 编写代码之后,您就不要再四处寻找可以使用 CIL 做什么了。

      在某些情况下,您使用 TypeScript 编写代码,但公众可能会使用生成的 JavaScript,也许是一个插件,并且您不希望他们破坏事物,那么在这种情况下您会担心它。对于这些情况,您将编写 TypeScript 代码以使字段 private 甚至在 JavaScript 端。其他答案已经涵盖了如何做到这一点。

      【讨论】:

        猜你喜欢
        • 2021-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-05
        • 2014-03-13
        相关资源
        最近更新 更多