【问题标题】:Observations for compiling classes with TypeScript [duplicate]使用 TypeScript 编译类的观察 [重复]
【发布时间】:2014-01-17 06:55:37
【问题描述】:

在我继续之前,我想指出我已经问了一些关于 TypeScript、它的编译器以及它在其整个生命周期中已经实现和无法实现的目标以及通向 1.0 版的路线图的一些问题 em>

这个问题与 TypeScript 中 publicprivate 关键字的使用有关,以及它们与编译后的 JavaScript 有何关系。

考虑以下 TypeScript 类:

class Example {
    private messageA: string;
    public messageB: string;

    constructor(message?: string) {
        this.messageA = "private: " + message;
        this.messageB = "public: " + message;
    }

    public showMessageA(): void {
        alert(this.messageA);
    }

    private showMessageB(): void {
        alert(this.messageB);
    }
}

var example = new Example("Hello World");

现在,当我输入 example 时。 intellisense (TypeScript) 告诉我我可以访问messageBshowMessageA,因为它们都是public。但是,这种行为(虽然可能)在编译后的 JavaScript 中并不明显。

这是我的班级的 JavaScript 编译:

var Example = (function () {
    function Example(message) {
        this.messageA = "private: " + message;
        this.messageB = "public: " + message;
    }
    Example.prototype.showMessageA = function () {
        alert(this.messageA);
    };

    Example.prototype.showMessageB = function () {
        alert(this.messageB);
    };
    return Example;
})();

var example = new Example("Hello World");

现在,如果我将此示例粘贴到我的浏览器控制台(我使用的是 Chrome),我可以访问 messageAmessageBshowMessageAshowMessageB,这意味着在 JavaScript 中,所有访问修饰符都是忽略。

我个人认为这是错误的! JavaScript 能够对访问修饰符进行建模,因此我认为 TypeScript 应该效仿。

考虑以下手写 JavaScript,它正确地模拟了 privatepublic 变量和函数:

var Example = (function() {
    return function Example(message) {
        var messageA = "private: " + message;
        this.messageB = "public: " + message;

        this.showMessageA = function() {
            alert(messageA);
        }

        var showMessageB = function() {
            alert(this.messageB);
        }
    }
})();

var example = new Example("Hello World");

现在,如果我将此示例粘贴到我的浏览器控制台中,我只能访问 messageBshowMessageA,根据我尝试使用 TypeScript 实现的目标是正确的。

问题

  1. 为什么 TypeScript 编译器在编译成 JavaScript 时会忽略访问修饰符?
  2. 为什么 TypeScript 将所有方法绑定到原型,而不是基于每个实例?
  3. 如果 TypeScript 编译类的方式与我的自定义实现相比有什么好处,那是什么,为什么?

【问题讨论】:

  • 1) 已解决 2) 和 3) 语言作者的设计决策。 TypeScript 是关于开发人员体验的,因此,如果您使用 TypeScript,您将不会调用任何私有函数,因为编译器会阻止它。 prototype 方式不需要关闭。

标签: javascript class compiler-construction typescript access-modifiers


【解决方案1】:

使用闭包来模拟私有访问的问题是每个实例都需要自己的每个方法的副本。这意味着每次创建实例时,都必须编译每个方法函数,并且必须为新函数保留内存空间。这并不理想,也不是 TypeScript 试图实现的目标。

【讨论】:

  • 酷,这让事情更清楚了。好的,从 .NET 和 JVM 等其他运行时来看,我假设您的陈述:“必须编译每个方法函数,并且必须为新函数保留内存空间”对于这些语言来说是真的吗?
  • 编译的、基于类的语言和解释的、基于原型的语言之间有很大的不同。在 JavaScript 中,我们通过在原型对象上存储方法来节省内存,所有具有该原型的对象在内存中调用相同的函数,只是使用不同的上下文。使用闭包来模拟隐私意味着您没有利用原型,并且每个对象都有自己的每个方法的副本。基于类的语言不使用原型,也没有 JavaScript 的上下文概念,所以很难类比。
  • 每个实例都需要重新编译每个方法是不正确的。它只需要分配一个新的闭包,即函数对象——仍然足够糟糕,但还没有那么糟糕。
  • @AndreasRossberg 有趣,出于某种原因,我的印象是每次实例化都会编译函数对象。
  • 哦,这些问题不适适用于 C# 或 Java 语言。在他们的对象模型中,您不能像在 JavaScript 中那样“提取”方法(即执行 this-stealing)。因此,与其分配单独的闭包,不如将所有闭包环境作为隐藏状态放在新对象本身上(永远不能与 JavaScript 中的方法解除关联)。
【解决方案2】:

从语义上讲,您描述的方法即闭包模型在许多方面都优于 JavaScript 将方法放在原型上的模型——即使对于公共方法也是如此。 EcmaScript 6 委员会(TypeScript 所基于的类系统)中的一些人会更喜欢这种类系统。事实上,TypeScript 的早期内部版本实现了这样的模型。

不幸的是,这样的模型在 JavaScript 中无法高效。它通常要求将每个方法作为每个对象实例的单独闭包分配——即,创建单个对象实例将涉及创建许多函数对象。并且由于 JavaScript 对 this 的处理极其薄弱,以及它的函数标识概念,一般来说,没有简单的方法可以优化这些分配。

因此,EcmaScript 6 选择了当前的模型(尽管它目前只有 public),TypeScript 遗憾地跟进(对于公共和私有)。然后,私有成员仅在 TypeScript 中提供静态检查,没有封装保证。

FWIW,在此模型中提供私有符号的正确方法是私有符号(又名private names),但不幸的是,这些符号没有进入 ES6,并且无论如何都不是 TypeScript 的选项。

【讨论】:

  • 另一个很好的答案,谢谢。不幸的是,再次在我试图实现的工作中投入了一把扳手,但我现在知道的更好,而不是更进一步,我将有 100 行或 1000 行代码来重构!叹息!
  • @AndreasRossberg 您能否提供一些关于为什么类系统比适当的原型系统更可取的见解?似乎对Object.create() 的优化可以允许以纯粹基于原型的方式完成继承,这将比经典继承更具表现力和灵活性。到目前为止,它根本不是一个选项,因为 new 运算符的速度要快几个数量级。
  • @Cuberto,很难发表评论。仅2条评论。首先,类并不重要,重要的是如何处理this:作为随机方法参数或作为明确引用。后者提供了更强的不变量,因此提供了更好的封装和优化特性。一个真正的原型语言(其中实例实际上是克隆)可以做到后者,但不是 JavaScript 的原型概念。其次,“灵活”并不总是更好——表达能力不仅与您的代码可以做什么有关,而且还与您的代码可以阻止其他代码做什么有关。
猜你喜欢
  • 2019-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-25
  • 2020-12-22
相关资源
最近更新 更多