1

我阅读了 Douglas Crockford 的《JavaScript : The Good Parts》一书以及许多其他资源,我对在 Javascript 中实现继承和隐私感到有些困惑。

我来自 Java World,我知道我可以通过闭包来模拟隐私,或者通过原型进行一些继承,但我想以 javascript 方式进行。

我知道我可以使用原型/寄生模式执行一些继承。这对性能有好处,但是无法正确使用某些隐私成员(每次实例化新对象时都不会创建一些闭包函数)

我知道我可以通过功能/寄生模式从对象继承并使用隐私成员,例如 douglas Crockford 建议的,但是存在明显的性能/内存问题,因为每次实例化对象时都会再次创建函数。

最后,我想知道其他语言的良好实践(例如隐私封装)在 JavaScript 中是否有意义。我在这里看到过一些帖子,人们说“我们不在乎隐私,只是告诉世界不应从外部访问此属性,这就足够了”。

我是否应该考虑将 Javascript 中的良好实践简化为具有公共接口的原型/寄生继承,并希望开发人员能够按预期使用该库?或者也许从继承和封装的角度思考是一种“java”的思考方式,而不是 javascript 的思考方式?如何在 javascript 中使用鸭子编程的力量来实现这些目标?

4

2 回答 2

2

如何在 JavaScript 中安全地继承私有数据

通常,人们使用下划线来表示一个属性或方法应该被认为是私有的。这是个坏主意。

为什么下划线是一个坏主意

下划线并不能保证数据隐私,它们会产生几个重要问题:

  1. 新手不知道下划线是什么意思,所以就忽略了。
  2. 高级用户认为他们知道自己在做什么,因此下划线不适用于他们。
  3. 实现细节可能会改变,这可能会破坏使用下划线属性的用户代码。

这些都是问题,因为封装是面向对象设计的一个重要特征。对象的存在是为了解决特定的问题。私有方法可以解决仅作为实现细节相关的问题。实现细节比公共接口更容易发生变化,因此依赖于实现细节的代码可能会在实现细节发生变化时中断。

仅公开您的公共接口会隐藏可能更改的实现细节,从而引导用户依赖支持的功能,而不是不支持的实现细节。

使用功能继承实现真正的数据隐私

可以使用功能继承来继承私有数据。

功能继承是通过将对象增强功能应用于对象实例来继承特征的过程。该函数提供了一个闭包范围,其作用是将私有数据隐藏在函数的闭包中。

Douglas Crockford 在“JavaScript: The Good Parts”中创造了这个术语。在 Crockford 的示例中,子工厂知道如何从现有的基础工厂实例化对象,这具有创建继承层次结构的效果。然而,这是个坏主意。我们应该始终支持对象组合而不是类继承

您可以通过稍微修改模式以将基础对象作为参数来创建和组合功能混合。

功能性 mixin 增强了提供的对象实例。函数的闭包范围可能包含私有方法和数据。然后,您可以在该函数中公开特权方法。它是这样工作的:

const withFlying = instance => {
  let canFly = true; // private data
  let isFlying = false;

  // Privileged method
  instance.fly = () => {
    isFlying = canFly ? true : isFlying;
    return instance;
  };

  // Privileged method
  instance.land = () => {
    isFlying = false;
    return instance;
  }

  // Privileged method
  instance.getFlightStatus = () => isFlying ? 'Flying' : 'Not flying';

  return instance;
};

// Create a new object and mix in flight capability:
const bird = withFlying({});
console.log(bird.fly().getFlightStatus()); // true

bird.land();
console.log(bird.getFlightStatus()); // false

函数式 mixin 可以使用标准函数组合与任何其他基础对象和任何其他特性集组合在一起。首先,您需要一个撰写功能。您可以compose()从 Lodash、Ramda 或任何提供标准 compose 函数的函数式编程库中使用——或者只编写自己的:

// Function composition: Function applied to the result of another function application, e.g., f(g(x))
// compose(...fns: [...Function]) => Function
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

现在,您可以使用标准函数组合将任意数量的 mixin 组合在一起:

// This function returns a function which can be used
// as a functional mixin.
// `text` here is private data that determines the sound
// `quack()` will log to the console.
const withQuacking = text => instance => {
  // Privileged method
  instance.quack = () => console.log(text);
  return instance;
};

// Compose mixins:
// ('Quack!' is private data)
const createDuck = compose(withFlying, withQuacking('Quack!'));

const malard = createDuck({});

console.log(malard.fly().getFlightStatus()); // Flying
malard.quack(); // "Quack!"

有关使用各种继承技术组合工厂函数的更通用方法,请参阅Stamp Specification

参考:

于 2013-09-10T08:42:54.043 回答
-1

我个人不同意那本书中的大多数内容,并且发现它们的论点很差。如果不是这样,由于严格的模式和像 jshint 这样的现代工具,它们中的大多数都完全无关紧要。但我想这只是这本书过时的问题,因为当时还没有这些。

该语言的对象模型在 2009 年得到了扩展,增加了许多新的特性和方法,这使得变量和属性之间的差异变得更大。如果 10 年前可以假装变量是属性,那么现在肯定不行。

传达某些东西不是已发布 API 的一部分,使用下划线前缀,例如:

this._age = 5;
this._method();

这对于现代 IDE 可能具有特殊意义,但对于语言中的反射方法(如for..ingetOwnPropertyNames.

有很多关于如何使用 natural* 构造函数原型模式来表达类概念的教程和指南。

*我使用的自然定义是指javascript引擎识别和优化的内容。

于 2013-08-16T14:28:21.750 回答