如何在 JavaScript 中安全地继承私有数据
通常,人们使用下划线来表示一个属性或方法应该被认为是私有的。这是个坏主意。
为什么下划线是一个坏主意
下划线并不能保证数据隐私,它们会产生几个重要问题:
- 新手不知道下划线是什么意思,所以就忽略了。
- 高级用户认为他们知道自己在做什么,因此下划线不适用于他们。
- 实现细节可能会改变,这可能会破坏使用下划线属性的用户代码。
这些都是问题,因为封装是面向对象设计的一个重要特征。对象的存在是为了解决特定的问题。私有方法可以解决仅作为实现细节相关的问题。实现细节比公共接口更容易发生变化,因此依赖于实现细节的代码可能会在实现细节发生变化时中断。
仅公开您的公共接口会隐藏可能更改的实现细节,从而引导用户依赖支持的功能,而不是不支持的实现细节。
使用功能继承实现真正的数据隐私
可以使用功能继承来继承私有数据。
功能继承是通过将对象增强功能应用于对象实例来继承特征的过程。该函数提供了一个闭包范围,其作用是将私有数据隐藏在函数的闭包中。
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。
参考: